> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zupertry.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Codes

> Every error code returned by the Zupertry API, with description and fix.

# Error Codes

All API errors return a consistent JSON envelope:

```json theme={null}
{
  "error": {
    "code": "insufficient_credits",
    "message": "Your account has 0 credits remaining. Top up at https://app.zupertry.com/billing."
  }
}
```

Use `error.code` for programmatic handling and `error.message` for display.

## Authentication errors

| Code                 | HTTP | When                                               | Fix                                              |
| -------------------- | ---- | -------------------------------------------------- | ------------------------------------------------ |
| `invalid_api_key`    | 401  | API key not found in the system                    | Check the key is correct and hasn't been deleted |
| `api_key_revoked`    | 401  | API key was manually revoked                       | Generate a new key in the console                |
| `api_key_disabled`   | 401  | API key is disabled (not revoked)                  | Re-enable the key in the console under API Keys  |
| `invalid_token`      | 401  | Firebase ID token is invalid or expired            | Re-authenticate and obtain a fresh ID token      |
| `unauthorized`       | 401  | Request is missing authentication entirely         | Add `Authorization: Bearer <key>` header         |
| `email_not_verified` | 403  | Firebase user's email is not verified              | Complete email verification first                |
| `forbidden`          | 403  | Authenticated but not authorised for this resource | Check you're using the right org/workspace       |

## Account errors

| Code                           | HTTP | When                                             | Fix                                                                    |
| ------------------------------ | ---- | ------------------------------------------------ | ---------------------------------------------------------------------- |
| `account_suspended`            | 403  | The organisation is suspended                    | Contact support at [support@zupertry.com](mailto:support@zupertry.com) |
| `onboarding_already_completed` | 409  | Tried to run onboarding again on an existing org | Redirect to `/dashboard` instead                                       |
| `api_key_limit_reached`        | 429  | Organisation has 10 API keys (the maximum)       | Revoke unused keys before creating a new one                           |
| `workspace_limit_reached`      | 429  | Organisation has 25 workspaces (the maximum)     | Delete unused workspaces first                                         |

## Credit and billing errors

| Code                     | HTTP | When                                                       | Fix                                                 |
| ------------------------ | ---- | ---------------------------------------------------------- | --------------------------------------------------- |
| `insufficient_credits`   | 402  | Not enough credits to process the job                      | Top up credits at `/billing`                        |
| `no_payment_method`      | 422  | Tried to enable auto-reload without a saved payment method | Add a card first at `/billing`                      |
| `payment_provider_error` | 502  | Razorpay returned an error                                 | Retry after a moment; contact support if persistent |

## Job errors

| Code                  | HTTP | When                                                | Fix                            |
| --------------------- | ---- | --------------------------------------------------- | ------------------------------ |
| `job_not_found`       | 404  | Job ID does not exist or belongs to a different org | Check the job ID               |
| `output_not_ready`    | 400  | Job exists but is still processing                  | Poll again or wait for webhook |
| `job_not_cancellable` | 409  | Job is already completed, failed, or cancelled      | Nothing to cancel              |

## Resource errors

| Code                  | HTTP | When                                               | Fix                                                  |
| --------------------- | ---- | -------------------------------------------------- | ---------------------------------------------------- |
| `workspace_not_found` | 404  | Workspace ID invalid or belongs to a different org | Check the workspace ID                               |
| `key_not_found`       | 404  | API key ID not found                               | Check the key ID                                     |
| `member_not_found`    | 404  | User UID not found in the org                      | Check the member list                                |
| `invite_not_found`    | 404  | Invite ID not found                                | The invite may have already been accepted or expired |
| `invite_expired`      | 410  | Invite link has expired (7-day TTL)                | Request a new invite from your admin                 |
| `endpoint_not_found`  | 404  | API route doesn't exist                            | Check the API reference for the correct path         |

## Validation errors

| Code                | HTTP | When                                                          | Fix                                            |
| ------------------- | ---- | ------------------------------------------------------------- | ---------------------------------------------- |
| `validation_error`  | 422  | Request body is missing required fields or has invalid values | See `error.message` for the specific field     |
| `invalid_mime_type` | 400  | Image MIME type is not supported                              | Use `image/jpeg`, `image/png`, or `image/webp` |
| `file_too_large`    | 400  | Uploaded file exceeds 10 MB                                   | Resize or compress the image before uploading  |

## Rate limiting

| Code                  | HTTP | When                                        | Fix                                                                                     |
| --------------------- | ---- | ------------------------------------------- | --------------------------------------------------------------------------------------- |
| `rate_limit_exceeded` | 429  | Too many API requests in the current window | Wait and retry. Check `Retry-After` header if present. Contact sales for a higher limit |

## Internal errors

| Code             | HTTP | When                         | Fix                                                                                |
| ---------------- | ---- | ---------------------------- | ---------------------------------------------------------------------------------- |
| `internal_error` | 500  | Unexpected server-side error | Retry after a moment. If persistent, contact support with the job ID and timestamp |

## Handling errors in code

```javascript theme={null}
async function submitTryOn(modelUrl, garmentUrl) {
  const res = await fetch('https://app.zupertry.com/v1/tryon', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.ZUPERTRY_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model_image_url: modelUrl,
      garment_image_url: garmentUrl,
    }),
  });

  if (!res.ok) {
    const { error } = await res.json();

    switch (error.code) {
      case 'insufficient_credits':
        throw new Error('Please top up your credits at app.zupertry.com/billing');
      case 'rate_limit_exceeded':
        // Retry after 60s
        await new Promise((r) => setTimeout(r, 60_000));
        return submitTryOn(modelUrl, garmentUrl);
      case 'invalid_api_key':
      case 'api_key_revoked':
        throw new Error('API key is invalid. Check your ZUPERTRY_API_KEY environment variable.');
      default:
        throw new Error(`Zupertry error [${error.code}]: ${error.message}`);
    }
  }

  return res.json();
}
```
