[API docs](/content/docs/v1/index.html) v1

Search documentation `
/`

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

Shell  JavaScript  Python

```
curl https://api.rankprompt.com/v1/me \
-H "Authorization: Bearer rp_live_YOUR_KEY"
```

```
const res = await fetch('https://api.rankprompt.com/v1/me', {\nheaders: { Authorization: `Bearer ${process.env.RANKPROMPT_API_KEY}` },\n});\nconst me = await res.json();
```

```
import os, httpx

res = httpx.get(\n  "https://api.rankprompt.com/v1/me",\n  headers={"Authorization": f"Bearer {os.environ['RANKPROMPT_API_KEY']}"},\n  timeout=30,\n)\nres.raise_for_status()\nme = 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`.

| Scope | Grants |
| --- | --- |
| ` 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:export-sections` | List and inspect export-sections. |
| ` write:export-sections` | Create, update, and delete export-sections (implies read). |
| ` read:citations` | List and inspect citations. |
| ` read:audit` | List and inspect audit. |
| ` 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: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:seo` | List and inspect seo. |
| ` write:seo` | Create, update, and delete seo (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` and `plan`. 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](/content/docs/v1/errors/index.html): 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`).

## Lifecycle: create, rotate, revoke

Manage keys from the [Developers page](https://app.rankprompt.com/developers) 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:** create a new key, deploy it, then revoke the old one. There is no “regenerate” button on purpose. Explicit rotation makes overlapping keys the default and prevents downtime.
- **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.
