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

# Cherry Shot API: Error Codes and Response Handling

> Learn how Cherry Shot structures error responses, which HTTP status codes to expect, and how to write resilient error-handling code in Python and Node.js.

Every error response from the Cherry Shot API follows the same JSON envelope — one top-level `error` object containing a machine-readable `code` and a human-readable `message`. Always branch your error-handling logic on `error.code`: it is a stable, versioned string that will not change between releases. The `message` field is intended for logs and debugging only, and its exact wording may change at any time without notice.

```json theme={null}
{
  "error": {
    "code": "insufficient_credits",
    "message": "This shoot needs 4 credits."
  }
}
```

## HTTP status codes

| Status | Meaning                                                     |
| ------ | ----------------------------------------------------------- |
| `200`  | Success.                                                    |
| `201`  | Resource created (a shoot or video).                        |
| `400`  | Validation error — check `message` for the offending field. |
| `401`  | Missing, invalid, or revoked API key.                       |
| `402`  | Insufficient credits to complete the request.               |
| `404`  | Resource not found, or not owned by your account.           |
| `429`  | Rate limit exceeded — 60 requests per minute per key.       |
| `500`  | Unexpected server error — safe to retry with backoff.       |

## Error codes

| `code`                 | When you'll see it                                                         |
| ---------------------- | -------------------------------------------------------------------------- |
| `unauthorized`         | No key was sent, or the key is invalid or revoked.                         |
| `rate_limited`         | Your key has exceeded the per-minute request limit.                        |
| `validation_error`     | A required field is missing or a value is out of range.                    |
| `insufficient_credits` | Your account does not have enough credits for the request.                 |
| `not_found`            | The resource ID doesn't exist or isn't owned by your account.              |
| `internal_error`       | An unexpected server-side error occurred — retry with exponential backoff. |

<Note>
  For security, `500` responses always return a generic message; the full detail is captured in server-side logs. If you encounter a persistent `500`, share the request time and endpoint path with Cherry Shot support so the team can trace it.
</Note>

## Handling errors in code

The examples below show a minimal but complete error-handling pattern. Check the HTTP status first, then switch on `error.code` for precise recovery logic.

<CodeGroup>
  ```python Python theme={null}
  import time
  import requests

  API_KEY = "cs_..."
  BASE_URL = "https://kyliwpyuseadbwawnsyd.supabase.co/functions/v1/api"

  def create_shoot(payload: dict, retries: int = 3):
      for attempt in range(retries):
          response = requests.post(
              f"{BASE_URL}/v1/shoots",
              json=payload,
              headers={"Authorization": f"Bearer {API_KEY}"},
          )

          if response.ok:
              return response.json()

          error = response.json().get("error", {})
          code = error.get("code", "unknown")

          if code == "insufficient_credits":
              raise RuntimeError("Top up your Cherry Shot credits and try again.")
          elif code == "validation_error":
              raise ValueError(f"Bad request: {error.get('message')}")
          elif code == "rate_limited":
              time.sleep(2 ** attempt)   # exponential back-off
              continue
          elif code == "unauthorized":
              raise PermissionError("Check your API key.")
          elif code == "internal_error" and attempt < retries - 1:
              time.sleep(2 ** attempt)
              continue
          else:
              raise RuntimeError(f"Unhandled error [{code}]: {error.get('message')}")

      raise RuntimeError("Request failed after all retries.")
  ```

  ```javascript Node.js theme={null}
  const BASE_URL = "https://kyliwpyuseadbwawnsyd.supabase.co/functions/v1/api";

  async function createShoot(payload, retries = 3) {
    for (let attempt = 0; attempt < retries; attempt++) {
      const res = await fetch(`${BASE_URL}/v1/shoots`, {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${process.env.CHERRY_SHOT_API_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify(payload),
      });

      if (res.ok) return res.json();

      const { error = {} } = await res.json();
      const { code, message } = error;

      switch (code) {
        case "insufficient_credits":
          throw new Error("Top up your Cherry Shot credits and try again.");
        case "validation_error":
          throw new Error(`Bad request: ${message}`);
        case "rate_limited":
        case "internal_error":
          if (attempt < retries - 1) {
            await new Promise((r) => setTimeout(r, 2 ** attempt * 1000));
            continue;
          }
          throw new Error(`Request failed [${code}]: ${message}`);
        case "unauthorized":
          throw new Error("Check your API key.");
        default:
          throw new Error(`Unhandled error [${code}]: ${message}`);
      }
    }

    throw new Error("Request failed after all retries.");
  }
  ```
</CodeGroup>
