Error Reference

All API errors follow a consistent format with machine-readable codes and human-friendly messages.

Error Response Format

{
  "code": 400,
  "status": "error",
  "message": "Validation failed",
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The title field is required",
    "details": {
      "field": "title",
      "constraint": "required"
    }
  }
}
FieldTypeDescription
codenumberHTTP status code
statusstring”OK” or “error”
messagestringBrief description
error.codestringMachine-readable error code
error.messagestringHuman-readable explanation
error.detailsobjectAdditional context (optional)

HTTP Status Codes

CodeMeaningWhen It Happens
400Bad RequestInvalid parameters, malformed JSON
401UnauthorizedMissing or invalid API key/token
403ForbiddenKey doesn’t have access to resource
404Not FoundResource doesn’t exist or is private
429Too Many RequestsRate limit exceeded
500Server ErrorInternal error (contact support)
503Service UnavailableTemporary outage

Error Codes by Category

Authentication Errors (401)

CodeMessageSolution
MISSING_API_KEYX-API-Key header requiredAdd X-API-Key header to token request
INVALID_API_KEYAPI key not foundCheck key is correct and not revoked
EXPIRED_API_KEYAPI key has expiredGenerate a new key in Studio
REVOKED_API_KEYAPI key was revokedGenerate a new key in Studio
MISSING_TOKENAuthorization header requiredGet token from POST /auth/token
TOKEN_INVALIDToken is malformedGet a fresh token
TOKEN_EXPIREDToken has expiredRefresh token via POST /auth/refresh

Handling 401 errors:

async function handleRequest(path) {
  const res = await fetch(path, {
    headers: { 'Authorization': `Bearer ${token}` }
  });

  if (res.status === 401) {
    const { error } = await res.json();

    switch (error.code) {
      case 'TOKEN_EXPIRED':
        // Refresh and retry
        token = await refreshToken();
        return handleRequest(path);

      case 'REVOKED_API_KEY':
      case 'INVALID_API_KEY':
        // Need new API key - show error to user
        throw new Error('API key invalid. Please reconfigure.');

      default:
        throw new Error(error.message);
    }
  }

  return res.json();
}

Authorization Errors (403)

CodeMessageSolution
STORE_ACCESS_DENIEDKey doesn’t have access to this storeCheck key scope in Studio
WRITE_ACCESS_DENIEDPublic keys are read-onlyUse secret key (sk_) for writes
INSUFFICIENT_PERMISSIONSMissing required permissionCheck key permissions
FORBIDDENAccess deniedResource may be private

Resource Errors (404)

CodeMessageSolution
STORE_NOT_FOUNDStore does not existVerify store ID
LISTING_NOT_FOUNDListing does not existVerify listing ID
PUBLICATION_NOT_FOUNDPublication does not existVerify publication ID
NOT_FOUNDResource not foundCheck the ID and visibility

Note: Private resources return 404 (not 403) to avoid leaking information about their existence.

Rate Limit Errors (429)

CodeMessageSolution
RATE_LIMIT_EXCEEDEDToo many requestsWait for Retry-After period
DAILY_QUOTA_EXCEEDEDDaily limit reachedWait until tomorrow or upgrade

Rate limit responses include helpful headers:

HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1707480000
Retry-After: 60
Content-Type: application/json

{
  "code": 429,
  "status": "error",
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Retry after 60 seconds.",
    "tier": "public_key"
  }
}

Handling rate limits:

async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    const res = await fetch(url, options);

    if (res.status === 429) {
      const retryAfter = res.headers.get('Retry-After') || 60;
      console.log(`Rate limited. Retrying in ${retryAfter}s`);
      await sleep(retryAfter * 1000);
      continue;
    }

    return res;
  }

  throw new Error('Max retries exceeded');
}

Validation Errors (400)

CodeMessageSolution
INVALID_REQUESTInvalid request formatCheck JSON syntax
VALIDATION_ERRORRequest validation failedCheck required fields
FIELD_REQUIREDThis field is requiredProvide the missing field
FIELD_INVALIDField value is invalidCheck field format
FIELD_TOO_LONGField value is too longReduce field length
INVALID_UUIDInvalid UUID formatUse valid CUID/UUID

Server Errors (500, 503)

CodeMessageSolution
INTERNAL_ERRORInternal server errorRetry later, contact support if persists
SERVICE_UNAVAILABLEService temporarily unavailableRetry with exponential backoff
DATABASE_CONNECTION_ERRORDatabase connection errorRetry later
DATABASE_TIMEOUTDatabase operation timed outRetry later

Handling server errors:

async function fetchWithBackoff(url, options) {
  const delays = [1000, 2000, 4000, 8000]; // Exponential backoff

  for (const delay of delays) {
    try {
      const res = await fetch(url, options);

      if (res.status >= 500) {
        await sleep(delay);
        continue;
      }

      return res;
    } catch (err) {
      await sleep(delay);
    }
  }

  throw new Error('Service unavailable');
}

Complete Error Code List

For programmatic error handling, here are all error codes:

type ErrorCode =
  // General
  | 'UNKNOWN_ERROR'
  | 'INTERNAL_ERROR'
  | 'INVALID_REQUEST'
  | 'UNAUTHORIZED'
  | 'FORBIDDEN'
  | 'NOT_FOUND'
  | 'CONFLICT'
  | 'VALIDATION_ERROR'
  | 'RATE_LIMIT_EXCEEDED'
  | 'SERVICE_UNAVAILABLE'

  // Authentication
  | 'INVALID_CREDENTIALS'
  | 'TOKEN_EXPIRED'
  | 'TOKEN_INVALID'
  | 'INSUFFICIENT_PERMISSIONS'
  | 'ACCOUNT_SUSPENDED'
  | 'ACCOUNT_NOT_VERIFIED'

  // Resources
  | 'ACCOUNT_NOT_FOUND'
  | 'LISTING_NOT_FOUND'
  | 'PUBLICATION_NOT_FOUND'
  | 'MEDIA_NOT_FOUND'
  | 'STORE_NOT_FOUND'

  // Validation
  | 'FIELD_REQUIRED'
  | 'FIELD_INVALID'
  | 'FIELD_TOO_LONG'
  | 'FIELD_TOO_SHORT'
  | 'INVALID_EMAIL'
  | 'INVALID_UUID'
  | 'INVALID_URL'

  // Business Logic
  | 'LIMIT_EXCEEDED'
  | 'OPERATION_NOT_ALLOWED'
  | 'RESOURCE_LOCKED'
  | 'DEPENDENCY_EXISTS'
  | 'DUPLICATE_OPERATION';