Skip to content

Downloads API - Error Handling

Downloads API - Error Handling & Best Practices

Section titled “Downloads API - Error Handling & Best Practices”

Learn how to handle errors gracefully and follow best practices when using the Downloads API.

The API uses standard HTTP status codes to indicate success or failure:

  • 200 OK - Request successful (file download or JSON response)
  • 400 Bad Request - Invalid request parameters
  • 404 Not Found - No version found matching criteria
  • 429 Too Many Requests - Rate limit exceeded
  • 500 Internal Server Error - Server-side error occurred

Error responses are returned as JSON:

error.json
{
"error": "No plugin version found for PaperMC"
}

Cause: No plugin version available for the specified server software or Java version.

Solution:

  • Check if versions exist for your server software
  • Try without specifying Java version
  • Verify server software name spelling
  • Check available versions

Cause: Too many requests in a short time period.

Solution:

  • Implement exponential backoff
  • Reduce request frequency
  • Cache responses
  • Wait for rate limit reset

Cause: Invalid or malformed request parameters.

Solution:

  • Verify parameter format
  • Check parameter values
  • Review API documentation
  • Validate inputs before sending
error-handling.js
async function downloadPlugin(serverSoftware, javaVersion) {
try {
const url = `https://api.statsly.org/api/plugin-versions/download/latest/${serverSoftware}/${javaVersion}`;
const response = await fetch(url);
if (!response.ok) {
switch (response.status) {
case 404:
throw new Error('No plugin version found for your server software');
case 429:
const retryAfter = response.headers.get('Retry-After');
throw new Error(`Rate limit exceeded. Retry after ${retryAfter} seconds`);
case 400:
const errorData = await response.json();
throw new Error(`Invalid request: ${errorData.error}`);
default:
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
}
const blob = await response.blob();
const filename = response.headers.get('Content-Disposition')?.split('filename=')[1]?.replace(/[""]/g, '') || 'Statsly.jar';
return { success: true, filename, blob };
} catch (error) {
console.error('Download failed:', error);
return { success: false, error: error.message };
}
}
error-handling.py
import requests
from requests.exceptions import RequestException
def download_plugin(server_software, java_version):
url = f"https://api.statsly.org/api/plugin-versions/download/latest/{server_software}/{java_version}"
try:
response = requests.get(url, stream=True)
response.raise_for_status()
filename = response.headers.get('Content-Disposition', '').split('filename=')[1].strip('"') or 'Statsly.jar'
with open(filename, 'wb') as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
return {'success': True, 'filename': filename}
except requests.HTTPError as e:
if e.response.status_code == 404:
return {'success': False, 'error': 'No plugin version found'}
elif e.response.status_code == 429:
retry_after = e.response.headers.get('Retry-After', 'unknown')
return {'success': False, 'error': f'Rate limit exceeded. Retry after {retry_after} seconds'}
else:
return {'success': False, 'error': f'HTTP {e.response.status_code}'}
except RequestException as e:
return {'success': False, 'error': str(e)}
error-handling.sh
#!/bin/bash
download_plugin() {
local server_software=$1
local java_version=$2
local output_file=$3
http_code=$(curl -s -o "$output_file" -w "%{http_code}" \
"https://api.statsly.org/api/plugin-versions/download/latest/$server_software/$java_version")
case $http_code in
200)
echo "Download successful"
return 0
;;
404)
echo "Error: No plugin version found" >&2
return 1
;;
429)
echo "Error: Rate limit exceeded" >&2
return 1
;;
*)
echo "Error: HTTP $http_code" >&2
return 1
;;
esac
}

Implement exponential backoff for retryable errors:

retry.js
async function downloadWithRetry(url, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const response = await fetch(url);
if (response.ok) {
return await response.blob();
}
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60');
const delay = Math.min(retryAfter * 1000, Math.pow(2, attempt) * 1000);
await new Promise(resolve => setTimeout(resolve, delay));
continue;
}
throw new Error(`HTTP ${response.status}`);
} catch (error) {
if (attempt === maxRetries - 1) throw error;
const delay = Math.pow(2, attempt) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
  • Cache version metadata for 5-10 minutes
  • Store latest version IDs to detect updates
  • Implement cache invalidation strategies
  • Use ETags if available
  • Respect rate limits (100 requests per 15 minutes)
  • Implement exponential backoff
  • Batch requests when possible
  • Monitor rate limit headers
  • Always check HTTP status codes
  • Handle network errors gracefully
  • Provide meaningful error messages
  • Log errors for debugging
  • Implement retry logic for transient errors
  • Use specific version IDs for reproducibility
  • Verify file integrity after download
  • Check file size matches expected size
  • Handle partial downloads
  • Implement resume capability for large files
  • Validate server software names
  • Sanitize file paths
  • Verify downloaded files
  • Use HTTPS only
  • Check file signatures if available

Log important events:

  • Download attempts
  • Success/failure rates
  • Error types and frequencies
  • Rate limit hits
  • Performance metrics

Set up alerts for:

  • Repeated download failures
  • Rate limit violations
  • Unexpected error codes
  • Performance degradation

If you encounter persistent errors: