Error Codes
All Stashly API errors follow a consistent response envelope format.
Error Response Format#
{
"success": false,
"message": "Human-readable description of what went wrong",
"data": []
}
The message field is safe to display to end-users in most cases.
HTTP Status Codes#
Client Errors
| Name | Type | Description |
|---|---|---|
400 | Bad Request | The request body or query parameters failed validation. Check the message for which field is invalid. |
401 | Unauthorized | The JWT token or API key is missing, expired, or invalid. Re-authenticate and retry. |
403 | Forbidden | The authenticated identity does not have permission for this operation (e.g. API key missing 'delete' scope, member trying to access billing). |
404 | Not Found | The requested resource (bucket, object, session, plan) does not exist. |
409 | Conflict | A resource with the same identifier already exists (e.g. duplicate bucket name). |
413 | Payload Too Large / Quota Exceeded | The upload exceeds the user's storage quota or the maximum allowed file size for their plan. |
429 | Too Many Requests | Rate limit exceeded. The response may include a Retry-After header. |
Server Errors
| Name | Type | Description |
|---|---|---|
500 | Internal Server Error | An unexpected error occurred on the server. Retry with exponential backoff. If the issue persists, contact support. |
503 | Service Unavailable | The service is temporarily unavailable (deployment, maintenance). Retry after a brief delay. |
Common Error Messages#
| Message | Status | Cause |
|---|---|---|
Access token is missing or invalid | 401 | No/expired JWT |
API key is invalid or revoked | 401 | Bad API key |
Insufficient permissions | 403 | API key scope too narrow |
Storage quota exceeded | 413 | Plan storage limit hit |
Invalid or expired OTP | 401 | OTP wrong/expired |
Bucket name already exists | 409 | Duplicate bucket name |
Session not found | 404 | Upload session expired or wrong ID |
Too many OTP requests | 429 | OTP rate limit (5 per 15 min) |
Retry Strategy#
For 429 and 5xx errors, use exponential backoff:
async function requestWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const res = await fetch(url, options);
if (res.ok) return res.json();
if (res.status === 429 || res.status >= 500) {
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
await new Promise((r) => setTimeout(r, delay));
continue;
}
// 4xx errors are not retryable
const body = await res.json();
throw new Error(body.message);
}
throw new Error("Max retries exceeded");
}