Skip to content

Getting started

Authentication

One scheme: a bearer token in the Authorization header. There are no cookies, no sessions and no OAuth flow on this API.

The header

Send the key as a bearer token on every request. There is no unauthenticated endpoint, including the ones that return nothing sensitive: an API where some calls are open and some are not is an API where somebody eventually gets that wrong.

Request
curl "https://api.cresva.ai/v1/brands" \  -H "Authorization: Bearer cresva_sk_live_XXXXXXXXXXXXXXXX"

Key kinds

The prefix is the whole distinction and it is visible at a glance in a log, a config file or a screenshot, which is the point of putting it in the string.

cresva_sk_live_
Reads your real ingested data. Counts against your plan’s per-minute budget and is refused rather than guessed at when the limiter cannot be read.
cresva_sk_test_
Returns deterministic simulated rows from every endpoint. Touches no customer record and costs nothing, so it fails open if the limiter is unavailable rather than breaking your test suite to protect nothing. Every response it produces sets meta.simulated to true.

The MCP tools use the same keys, and the same rule

The tools in cresva-mcp-server read your account through the same keys as this API, and a test key means the same thing there as it does here: cresva_sk_test_ answers with deterministic simulated rows and reads no brand data, while cresva_sk_live_ reads your real ingested data.

Every MCP response says which it was in a top-level mode field, either "test" or "live", and repeats it in an X-Cresva-Mode header so it is readable without parsing the body. The field is absent on a refused request, because there is no key to report a mode for.

This was not always true

Until 2026-09-09 the MCP tools did not read the key kind at all, so a test key there returned whatever real data its account held. If you built anything against that behaviour, a test key now returns fixtures and a live key is what reads your account.

Point CI at a test key

A test key gives your integration tests the real response shapes with no dependency on an ad account being connected, and no risk of a test run exhausting a production quota.

Storage and rotation

A key is shown once, at creation, and stored hashed. Nothing in the product can retrieve the original string afterwards, including support. That is deliberate and it has a consequence worth planning for: losing a key means creating a new one, not looking the old one up.

  • Keep the key in an environment variable or a secret manager, never in source control and never in client-side code.
  • Create a separate key per integration. Revoking one then costs you one integration rather than all of them.
  • Revoked and expired keys are refused at the first check, before any data is read.
Environment
# .env, never committedCRESVA_API_KEY=cresva_sk_live_XXXXXXXXXXXXXXXX # and in your shellexport CRESVA_API_KEY="$(op read op://vault/cresva/api-key)"

Scoping a key to one brand

A key can be narrowed to a single brand. A narrowed key does not need the brand_id parameter, and asking it for a different brand is a 403 forbidden rather than an empty result. An agency running one integration per client is the case this exists for.

When authentication fails

Two different failures, deliberately distinguished, because they call for different fixes.

401 unauthorized
No Authorization header, a malformed one, or a key that has been revoked or has expired.
403 forbidden
The key is valid but is not scoped to the brand in the request. Keys can be narrowed to one brand.
401
{  "error": {    "code": "unauthorized",    "message": "Missing or invalid Authorization header"  }}

Rate-limit headers on a 401

A rejected request still carries the X-RateLimit-* headers. It did once not, which meant a client written to read those headers crashed on the one response it was most likely to receive while being set up.