Authentication

API key format, scopes, brand restriction, team keys, and key rotation.

Every request to the Public API authenticates with an opaque bearer token in the Authorization header. Browser CORS calls are blocked at the edge, so the API is server-to-server only.

The header

curl https://api.rankprompt.com/v1/me \
-H "Authorization: Bearer rp_live_YOUR_KEY"
const res = await fetch('https://api.rankprompt.com/v1/me', {
headers: { Authorization: `Bearer ${process.env.RANKPROMPT_API_KEY}` },
});
const me = await res.json();
import os, httpx

res = httpx.get(
  "https://api.rankprompt.com/v1/me",
  headers={"Authorization": f"Bearer {os.environ['RANKPROMPT_API_KEY']}"},
  timeout=30,
)
res.raise_for_status()
me = res.json()

Keys are prefixed with rp_live_ so they are easy to detect in logs and pull requests. We submit the prefix to GitHub secret scanning so leaks are caught automatically.

Scopes

Scopes are granular per resource. The implication hierarchy is: * ⊃ read:all / write:all ⊃ specific scopes; write:foo ⊃ read:foo.

ScopeGrants
read:all Read access to every resource type.
write:all Read + write access to every resource type.
read:brands List and inspect brands.
write:brands Create, update, and delete brands (implies read).
read:reports List and inspect reports.
write:reports Create, update, and delete reports (implies read).
write:prompts Create, update, and delete prompts (implies read).
read:jobs List and inspect jobs.
write:jobs Create, update, and delete jobs (implies read).
read:region-configs List and inspect region-configs.
write:region-configs Create, update, and delete region-configs (implies read).
read:scheduled List and inspect scheduled.
write:scheduled Create, update, and delete scheduled (implies read).
read:citations List and inspect citations.
write:citations Create, update, and delete citations (implies read).
write:audit Create, update, and delete audit (implies read).
read:webhooks List and inspect webhooks.
write:webhooks Create, update, and delete webhooks (implies read).
read:meta List and inspect meta.
read:competitors List and inspect competitors.
write:competitors Create, update, and delete competitors (implies read).
read:content List and inspect content.
write:content Create, update, and delete content (implies read).
read:cms List and inspect cms.
write:cms Create, update, and delete cms (implies read).
read:brand-audits List and inspect brand-audits.
write:brand-audits Create, update, and delete brand-audits (implies read).
read:tasks List and inspect tasks.
write:tasks Create, update, and delete tasks (implies read).
read:analytics List and inspect analytics.
read:ads List and inspect ads.
read:seo List and inspect seo.
write:seo Create, update, and delete seo (implies read).
read:outreach List and inspect outreach.
write:outreach Create, update, and delete outreach (implies read).
read:community List and inspect community.
write:community Create, update, and delete community (implies read).
read:gbp List and inspect gbp.
write:gbp Create, update, and delete gbp (implies read).
read:client-reports List and inspect client-reports.
write:client-reports Create, update, and delete client-reports (implies read).
read:agency List and inspect agency.
write:prospects Create, update, and delete prospects (implies read).
write:billing Create, update, and delete billing (implies read).

read:meta and GET /v1/me

GET /v1/me is the introspection endpoint that confirms your credentials work. It always returns user_id, auth_type, plan, account_id, account_role and account_plan (see Team keys). The fields that describe what your key can do (key_prefix, scopes, allowed_brand_id) are only included when the key carries the read:meta scope.

When a request lands with insufficient scope you get back the insufficient_scope error from the error catalog: a 403 whose details carry both the missing scope (required_scope) and the scopes your key actually has (key_scopes):

{
  "error": {
    "code": "insufficient_scope",
    "message": "This endpoint requires the 'write:brands' scope",
    "details": {
      "required_scope": "write:brands",
      "key_scopes": ["read:brands"]
    }
  },
  "request_id": "01HZK3..."
}

Brand restriction

Optionally bind a key to a single brand at creation time (allowed_brand_id). The API will then refuse any request whose target brand does not match, even if the calling key carries write:all. This lets you issue per-brand keys to clients without having to spin up a new Rank Prompt workspace.

A request that crosses the boundary fails with brand_not_authorized (403).

Team keys

A key acts in the account that was active when it was created: your own, or a team account you hold a seat in, with your role there. GET /v1/me says which one:

FieldWhat it tells you
account_idThe account the key acts in. Equal to user_id for a key made in your own account.
account_roleYour role in that account: owner in your own, editor or viewer in a team account.
account_planThat account’s plan, which plan-gated account features and the monthly request pool follow. plan stays your own plan.

What changes for a key made in a team account:

  • Losing the seat stops the key. If you are no longer an active member of that account, every call answers 403 account_membership_required, with the account in details.account_id. The key never falls back to your own account.
  • A failed membership read is retryable. If the membership cannot be checked, the call answers 503 service_unavailable with a Retry-After header. Wait that many seconds and send the same request again.
  • Billing stays with the owner. Buying credits (POST /v1/me/credits/purchases) and starting a subscription (POST /v1/me/subscriptions) answer 403 forbidden: that account’s billing belongs to its owner. Use a key made in your own account, with write:billing, for both.
  • The request pool is the account’s. Every key made in that account, the owner’s and the team’s, draws on one monthly pool sized by the account’s plan. When it runs out the 429 carries no upgrade_url: only the owner raises it. A key you make in your own account keeps your own pool.
  • The owner sees and revokes team keys. The account owner lists every key made in their account, with the request units each drew this month, and can revoke one, with a reason, without removing you from the team. GET /v1/me/usage?account_id= counts only the calls made in one account (see Inspecting your usage). Leaving the team or being removed revokes your keys for that account.
  • Work on a brand is charged to the brand’s owner. A report, an audit or an article on a brand draws on its owner’s credits, whichever account the key acts in.

Lifecycle: create, rotate, revoke

Manage keys from the Developers page inside your workspace. Common operations:

  • Create: pick scopes, optional brand restriction, and optional expiry. The full key is shown once at creation time and never again.
  • Rotate: the key’s Rotate action issues a new key with the same name, scopes, brand restriction, expiry and account, shows it once, and revokes the old one at that moment. The monthly request count carries over. For zero downtime, create a second key instead, deploy it, then revoke the old one.
  • Revoke: disables the key immediately. In-flight requests still complete; the next call returns invalid_api_key.
  • Expire: keys with an expires_at start returning expired_api_key once the timestamp passes. Use this for short-lived integrations.

Storage hygiene

  • Treat keys like database passwords. Never commit them, never log them, never put them in client-side code.
  • Prefer your platform’s secret manager (AWS Secrets Manager, GCP Secret Manager, Vercel/Netlify env vars, Doppler, 1Password CLI).
  • Rotate immediately on suspected exposure; we cannot revoke retroactively what has already been read from your codebase.