Skip to main content
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.
Build against https://api.creatoraudit.com/v2 and authenticate every request with an organization-scoped bearer key. See Authentication and Versioning.

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 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.
# 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.
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"]
Skip include_total=true unless you actually display the total — it runs an extra COUNT query per request. See Pagination.

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.
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
See 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.
# 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.
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"}'
Reuse the same key only for retries of the same request. A fresh logical write needs a fresh key.

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”.
Don’t busy-wait. A new account or video needs time to complete its first refresh — see Data freshness for what to expect and how the cadence is set.

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.
EndpointID field
POST /v2/accounts/metricsaccount_ids (1–200)
POST /v2/videos/metricsvideo_ids (1–200)
POST /v2/account-videos/metricsinstagram_post_ids and/or tiktok_video_ids
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 for collection reads and Platforms & data coverage 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.
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 for the full status and code reference.

Next steps

API setup

Get a key and make your first authenticated call.

Python example

A worked end-to-end client you can adapt.

For AI agents

Patterns for driving the API from an agent.

Errors

Status codes, code values, and robust handling.