Get started
Scopes
What each API-key scope unlocks, and how to handle a missing one.
A scope is a permission stamped onto the key when you create it — "this key may search", "this key may read usage". Each key holds its own set, granted independently, so a key that searches fine can still 403 on Record Lookup.
| Scope | Grants access to |
|---|---|
dsp.search.read | POST /v1/search, POST /v1/similar, all four POST /v1/relationships* routes, all three POST /v1/org-chart* routes, GET /v1/coverage, GET /v1/search/filters/{name}, GET /v1/search/filters/{category}/{name}, GET /v1/sorts/{category} |
dsp.entities.read | POST /v1/entities/* (all nine category routes) |
dsp.usage.read | GET /v1/credits, GET /v1/keys/usage, GET /v1/usage/history, GET /v1/usage/history/{run_id}, GET /v1/usage/metrics, POST /v1/billing/top-up/intents, GET /v1/billing/top-up/intents/{topup_id} |
dsp.agents.read | GET /v1/agents/runs, GET /v1/agents/runs/{run_id}, GET /v1/agents/runs/{run_id}/results, GET /v1/agents/runs/{run_id}/events |
dsp.agents.write | POST /v1/agents/runs and POST /v1/agents/runs/{run_id}/cancel, which need this scope. It also grants every dsp.agents.read route, so a key that starts runs can always read them back |
Some routes accept more than one scope. Any one of the listed scopes is enough:
| Route | Accepts |
|---|---|
POST /v1/feedback, GET /v1/feedback, PUT /v1/feedback/votes, GET /v1/feedback/votes | dsp.search.read or dsp.usage.read |
All /v1/lists* routes, and POST /v1/search/export | dsp.search.read or dsp.entities.read |
POST /v1/members/resolve | dsp.search.read or dsp.agents.write |
Feedback accepts either scope because anyone who can produce a result or read back a run should be able to report one, and reporting is free. GET /v1/feedback/tags needs no credential at all.
These five scopes are the entire ceiling; no credential of any kind can hold more.
Why relationships and org charts use the search scope
Relationships and org charts share the search scope rather than holding one of their own: they read the same data you are already entitled to, from a neighbouring corpus. They are metered separately — as dsp.relationships, dsp.relationships.ask and dsp.org_chart — because a billing action is what a call is charged as, never a credential you grant.
A missing scope returns a 403. The detail lists every scope the route accepts, any one of which would have been enough:
{
"status": 403,
"code": "auth.insufficient_scope",
"title": "Insufficient scope",
"detail": "This credential holds none of: dsp.entities.read."
}