Error Codes

All API errors follow the same format. Here is how to handle each one.

Error Response Format

Every error response contains an error object with a machine-readable code and a human-readable message:

{
  "error": {
    "code": "insufficient_credits",
    "message": "Not enough credits. Required: 20, balance: 5."
  }
}

Error Reference

HTTPError CodeMeaningWhat To Do
400bad_requestInvalid request body or missing required fields.Check the message for which field failed validation. Fix the request and retry.
401unauthorizedMissing or invalid API key.Verify your Authorization: Bearer masko_... header is correct and the key has not been revoked.
402insufficient_creditsNot enough credits for this operation. Response includes required and balance.Top up credits at app.masko.ai/billing or reduce the request scope.
403forbiddenYou do not have access to this resource.Verify the resource belongs to your account. Check that your API key has the necessary permissions.
404not_foundThe requested resource does not exist.Check the resource ID in the URL. Use the list endpoints to find valid IDs.
429rate_limitedToo many requests. Response includes Retry-After header.Wait for the number of seconds in the Retry-After header, then retry.
500internal_errorSomething went wrong on our side.Retry after a short delay. If persistent, contact support at paul@masko.ai.

Handling Insufficient Credits

The 402 response includes required and balance fields so you can show users exactly how many credits they need:

// HTTP 402
{
  "error": {
    "code": "insufficient_credits",
    "message": "Not enough credits. Required: 140, balance: 50.",
    "required": 140,
    "balance": 50
  }
}

Rate Limit Recovery

When you hit a 429, the response includes a Retry-After header with the number of seconds to wait. Use exponential backoff for robustness:

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

    if (res.status !== 429) {
      return res;
    }

    if (attempt === maxRetries) {
      throw new Error('Rate limited after max retries');
    }

    // Use Retry-After header, or exponential backoff
    const retryAfter = res.headers.get('Retry-After');
    const delay = retryAfter
      ? parseInt(retryAfter, 10) * 1000
      : Math.pow(2, attempt) * 1000;

    console.log(`Rate limited. Retrying in ${delay / 1000}s...`);
    await new Promise((r) => setTimeout(r, delay));
  }
}

// Usage:
const res = await fetchWithRetry(
  'https://api.masko.ai/v1/collections/COLLECTION_ID/generate',
  {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer masko_YOUR_API_KEY',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      type: 'image',
      name: 'Hello',
      image_prompt: 'waving hello',
    }),
  }
);
Tip

Parse the error.code field programmatically to decide how to handle each error. Use error.message for logging and debugging - it may change between versions, but the code will stay stable.