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

NameTypeDescription
400Bad RequestThe request body or query parameters failed validation. Check the message for which field is invalid.
401UnauthorizedThe JWT token or API key is missing, expired, or invalid. Re-authenticate and retry.
403ForbiddenThe authenticated identity does not have permission for this operation (e.g. API key missing 'delete' scope, member trying to access billing).
404Not FoundThe requested resource (bucket, object, session, plan) does not exist.
409ConflictA resource with the same identifier already exists (e.g. duplicate bucket name).
413Payload Too Large / Quota ExceededThe upload exceeds the user's storage quota or the maximum allowed file size for their plan.
429Too Many RequestsRate limit exceeded. The response may include a Retry-After header.

Server Errors

NameTypeDescription
500Internal Server ErrorAn unexpected error occurred on the server. Retry with exponential backoff. If the issue persists, contact support.
503Service UnavailableThe service is temporarily unavailable (deployment, maintenance). Retry after a brief delay.

Common Error Messages#

MessageStatusCause
Access token is missing or invalid401No/expired JWT
API key is invalid or revoked401Bad API key
Insufficient permissions403API key scope too narrow
Storage quota exceeded413Plan storage limit hit
Invalid or expired OTP401OTP wrong/expired
Bucket name already exists409Duplicate bucket name
Session not found404Upload session expired or wrong ID
Too many OTP requests429OTP 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");
}