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

# Production best practices

> Ship a reliable CreatorAudit integration — key hygiene, pagination, back-off, caching, idempotency, and polling.

This page consolidates the habits that keep an integration healthy in production. Each
section links to the convention page that covers it in full — start here, then go
deep where you need to.

<Note>
  Build against `https://api.creatoraudit.com/v2` and authenticate every request with an
  organization-scoped bearer key. See [Authentication](/api-reference/authentication)
  and [Versioning](/api-reference/versioning).
</Note>

## Manage keys safely

An API key grants full access to your organization's data, so treat it like a
password.

* **Keep it server-side.** Store the key in a secret manager and call the API from
  your backend. Never commit it, embed it in client-side code, or paste it into logs.
* **Use one key per integration.** Separate keys let you disable one without
  disrupting the others, and [`GET /v2/whoami`](/api-reference/authentication) tells
  you which key and organization a token resolves to.
* **Rotate without downtime.** Create a new key, deploy it, confirm traffic has moved
  over, then disable the old one. Don't delete the old key until you're sure nothing
  still uses it — disabled keys return `403`.

```bash theme={null}
# Confirm which org and key a token is bound to before you ship it.
curl https://api.creatoraudit.com/v2/whoami \
  -H "Authorization: Bearer YOUR_API_KEY"
```

## Paginate with cursors, not offsets

List endpoints use opaque cursor pagination. Loop while `pagination.has_next` is
true, passing the previous `next_cursor` each time, and keep your filters and sort
identical across pages — a cursor is bound to the filter and sort it was issued for.
Use the largest sensible `limit` (up to `200`) to fetch a page set in fewer calls.

<CodeGroup>
  ```python python theme={null}
  import os
  import httpx

  BASE = "https://api.creatoraudit.com/v2"
  headers = {"Authorization": f"Bearer {os.environ['API_KEY']}"}


  def fetch_all(path: str, **params) -> list[dict]:
      items: list[dict] = []
      cursor: str | None = None
      with httpx.Client(base_url=BASE, headers=headers) as client:
          while True:
              query = {"limit": 200, **params}
              if cursor:
                  query["cursor"] = cursor
              body = client.get(path, params=query).raise_for_status().json()
              items.extend(body["data"])
              page = body["pagination"]
              if not page["has_next"]:
                  return items
              cursor = page["next_cursor"]
  ```

  ```bash bash theme={null}
  #!/usr/bin/env bash
  set -euo pipefail
  BASE="https://api.creatoraudit.com/v2"
  CURSOR=""

  while :; do
    URL="$BASE/accounts?limit=200"
    [ -n "$CURSOR" ] && URL="$URL&cursor=$CURSOR"

    RESP=$(curl -s "$URL" -H "Authorization: Bearer $API_KEY")
    echo "$RESP" | jq -c '.data[]'

    HAS_NEXT=$(echo "$RESP" | jq -r '.pagination.has_next')
    [ "$HAS_NEXT" = "true" ] || break
    CURSOR=$(echo "$RESP" | jq -r '.pagination.next_cursor')
  done
  ```
</CodeGroup>

<Tip>
  Skip `include_total=true` unless you actually display the total — it runs an extra
  `COUNT` query per request. See [Pagination](/api-reference/pagination).
</Tip>

## Handle rate limits and back off on 429

Every authenticated response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`,
and `X-RateLimit-Reset`. Read them at runtime rather than hard-coding a number, and
slow down as `X-RateLimit-Remaining` approaches zero. On a `429`, honor `Retry-After`
when present, then fall back to exponential back-off. The same pattern fits transient
`503` (`SERVICE_UNAVAILABLE`) responses.

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


  def get_with_backoff(client: httpx.Client, url: str, *, max_attempts: int = 5):
      delay = 1.0
      for _ in range(max_attempts):
          resp = client.get(url)
          if resp.status_code not in (429, 503):
              return resp
          # Prefer the server's hint; otherwise exponential back-off.
          wait = float(resp.headers.get("Retry-After", delay))
          time.sleep(wait)
          delay *= 2
      resp.raise_for_status()
      return resp
  ```

  ```bash bash theme={null}
  #!/usr/bin/env bash
  # Retry on 429/503 with exponential back-off, honoring Retry-After.
  URL="https://api.creatoraudit.com/v2/accounts"
  DELAY=1
  for _ in 1 2 3 4 5; do
    CODE=$(curl -s -o /tmp/body -D /tmp/hdr -w '%{http_code}' "$URL" \
      -H "Authorization: Bearer $API_KEY")
    case "$CODE" in
      429|503)
        WAIT=$(grep -i '^retry-after:' /tmp/hdr | awk '{print $2}' | tr -d '\r')
        sleep "${WAIT:-$DELAY}"
        DELAY=$((DELAY * 2)) ;;
      *) cat /tmp/body; break ;;
    esac
  done
  ```
</CodeGroup>

See [Rate limits](/api-reference/rate-limits) for the full header reference.

## Cache with conditional requests

`GET` responses carry a strong `ETag`. Send the value back as `If-None-Match`; an
unchanged representation returns `304 Not Modified` with an empty body, so you skip
the re-download and save bandwidth.

```bash theme={null}
# First request returns 200 + ETag; store it.
curl -i "https://api.creatoraudit.com/v2/accounts/ACCOUNT_ID" \
  -H "Authorization: Bearer YOUR_API_KEY"

# Re-fetch with the stored ETag — returns 304 if nothing changed.
curl -i "https://api.creatoraudit.com/v2/accounts/ACCOUNT_ID" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H 'If-None-Match: "the-etag-you-stored"'
```

## Make writes idempotent

`POST /accounts`, `POST /videos`, and `POST /creators` accept an `Idempotency-Key`
header. Send a unique key per logical operation so a retry after a network blip
doesn't create a duplicate — the server returns the original result instead.

```bash theme={null}
curl -X POST "https://api.creatoraudit.com/v2/accounts" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 0b3f2c14-9a6d-4f1e-8b2a-1c2d3e4f5a6b" \
  -d '{"platform": "instagram", "username": "natgeo"}'
```

<Warning>
  Reuse the *same* key only for retries of the *same* request. A fresh logical write
  needs a fresh key.
</Warning>

## Poll on a sensible cadence

CreatorAudit refreshes data on a schedule, not in real time, and there are no
webhooks — consumers poll. After a write, a new resource's `last_scrape_time` is
`null` until its first refresh lands. Re-fetch on an interval (seconds to minutes,
not a tight loop) and anchor staleness decisions on `last_scrape_time` rather than
assuming "now".

<Warning>
  Don't busy-wait. A new account or video needs time to complete its first refresh — see
  [Data freshness](/data-freshness) for what to expect and how the cadence is set.
</Warning>

## Reduce calls with batch metrics

Instead of fetching windowed metrics one resource at a time, request many IDs in a
single call with the batch endpoints. Each takes a `period` (`7d`, `30d`, `90d`, or
`custom` with `start_date`/`end_date`) and a list of IDs.

| Endpoint                          | ID field                                       |
| --------------------------------- | ---------------------------------------------- |
| `POST /v2/accounts/metrics`       | `account_ids` (1–200)                          |
| `POST /v2/videos/metrics`         | `video_ids` (1–200)                            |
| `POST /v2/account-videos/metrics` | `instagram_post_ids` and/or `tiktok_video_ids` |

```bash theme={null}
curl -X POST "https://api.creatoraudit.com/v2/accounts/metrics" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"period": "30d", "account_ids": ["…", "…", "…"]}'
```

Fewer round-trips means fewer rate-limit windows consumed. See
[Pagination](/api-reference/pagination) for collection reads and
[Platforms & data coverage](/platforms) for what each metric means.

## Log the request ID on every failure

Every response includes an `X-Request-ID` header, mirrored as `request_id` in the
RFC 9457 error body. Log it with each failure so support can correlate a call to its
server-side event. Branch on the HTTP status for coarse flow and on the `code` field
for specifics.

```python theme={null}
import httpx

resp = httpx.get(url, headers=headers)
if resp.is_error:
    problem = resp.json()
    request_id = resp.headers.get("X-Request-ID")
    raise RuntimeError(
        f"{resp.status_code} {problem['code']}: {problem['detail']} "
        f"(request_id={request_id})"
    )
```

See [Errors](/api-reference/errors) for the full status and `code` reference.

## Next steps

<Columns cols={2}>
  <Card title="API setup" icon="plug" href="/api-setup">
    Get a key and make your first authenticated call.
  </Card>

  <Card title="Python example" icon="python" href="/examples/python">
    A worked end-to-end client you can adapt.
  </Card>

  <Card title="For AI agents" icon="robot" href="/agents/overview">
    Patterns for driving the API from an agent.
  </Card>

  <Card title="Errors" icon="triangle-exclamation" href="/api-reference/errors">
    Status codes, `code` values, and robust handling.
  </Card>
</Columns>
