Developer docs · v1.0.0

API token example: create, scope, rotate and revoke a key

A real key format, a real header, and the lifecycle a credential goes through — shown on an API you can call for free.

What an API token is (and what it is not)

An API token is a secret string that proves a request comes from a known account. The provider mints it, you store it, your code sends it on every request. "API key", "API token", "access key" and "secret key" are mostly the same object under different names; the distinctions that matter are below.

TermWhat it isLifetimeWhere it is sent
API keyA long random credential bound to an account; the provider stores only its hashUntil rotated or revokedA request header (X-API-Key)
Access tokenA short-lived credential issued by an OAuth authorization serverMinutes to an hourAuthorization: Bearer …
Refresh tokenA long-lived OAuth credential used only to obtain new access tokensDays to monthsThe token endpoint only, from a backend
Client secretThe password of an OAuth client, exchanged for tokens; never sent to the API itselfUntil rotatedThe token endpoint only
Session tokenA credential for a signed-in person, tied to a browser sessionMinutes to a dayA cookie or bearer header, from that browser
Publishable keyA non-secret identifier safe to ship in a browser, limited by origin and abilityUntil revokedClient-side code

This page is about the first row. If your situation involves a user granting access, read API key vs OAuth first.

An API token example: the header on the wire

OffenderSearch keys look like os_live_… (production) and os_test_… (sandbox). The prefix tells you, and the server, which host the key belongs to; the rest is random. Here is a complete authenticated request as it crosses the network. The key shown is an illustration, not a working credential.

One authenticated request
POST /v1/search HTTP/1.1
Host: api.offendersearch.app
X-API-Key: os_live_7f3a9c21b0d4e8f6a2c5b1d9e0f7a3c4b6d8e2f1a5c7b9d3
Content-Type: application/json

{"query": {"firstName": "Jordan", "lastName": "Rivera", "dob": "1988-04-12"}}

Three things to notice. The key is in a header, so it never appears in a URL, a log line keyed on the path, or a referrer. The body is ordinary JSON — authentication and payload are separate concerns. And nothing else is required: no token exchange, no nonce, no signature. (Request signing is a different mechanism, used here for webhooks; see API signature.)

Generate an API key: where it comes from

  1. Create an account. Sign up. Every new account has 25 free calls and needs no card to make them.
  2. Open Dashboard → API keys. /dashboard/keys lists every key on the account with its name, masked prefix, creation time, last use and status.
  3. Create a key. Name it for the service or environment that will hold it — screening-service (prod), not my key. The full secret is shown once, at creation. Copy it into your secret store now; it is stored on our side only as a one-way hash and cannot be shown again.
  4. Create a test key for the sandbox. The same page has Create test key. It begins os_test_, works only on sandbox.offendersearch.app, answers with the sandbox's fixed scenarios and is never billed. Up to 3 test keys may be active at once, with 10 mints a day.
What the dashboard (and GET /v1/keys) shows for a key — the secret is not in it
{
  "id": "key_01j9x…",
  "name": "screening-service (prod)",
  "maskedKey": "os_live_7f3a…",
  "createdAt": "2026-10-28T14:02:11Z",
  "lastUsedAt": "2026-10-28T14:20:47Z",
  "status": "active",
  "kind": "secret"
}

There is no "generate API key" endpoint callable *with* an API key, on purpose: a credential that can mint credentials turns one leak into unlimited leaks. Keys are created by a signed-in person in the dashboard, or by the dashboard's own session-authenticated route.

How to use the API key in code

Read the key from the environment, pick the host from its prefix, send it as X-API-Key. The examples search the sandbox for a synthetic person; a name the sandbox does not list returns a complete answer with zero records, which is exactly what you want to see first. The names that return matches are listed on the sandbox page.

# Keep the key in the environment, never in the command history of a shared machine.
export OFFENDERSEARCH_API_KEY=os_test_…   # a sandbox key from Dashboard → API keys

curl -X POST https://sandbox.offendersearch.app/v1/search \
  -H "X-API-Key: $OFFENDERSEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": {"firstName": "Jordan", "lastName": "Rivera", "dob": "1988-04-12"}}'

The same header works for every product on the account — sex offender search, criminal records, monitoring, the Map API. The published Python and Node SDKs read OFFENDERSEARCH_API_KEY for you and pick the host from the prefix.

Scoping a token: what a key is allowed to do

Two models exist. Scoped tokens carry permissions inside the credential (reports:read); the API checks the scope on every call. Pure credentials carry nothing; permissions live on the account the key resolves to. OffenderSearch keys are the second kind — a key is a pure authentication credential with no per-key features — and the account carries the entitlements: whether the Map API is turned on, whether the account is an approved FCRA partner, the plan's rate limits.

ModelProConFits
Scopes in the tokenLeast privilege per credential; one leaked token does lessScope sprawl; every caller must know which scope it needsLarge APIs with many distinct permissions and many clients
Permissions on the accountNothing to configure per key; a new key is immediately rightA key is as powerful as the accountData APIs where every call does one kind of thing

The scoping you *do* get from us is by kind: a secret key for your backend; a test key that only the sandbox honours; and a publishable Map API browser key limited to the sites it may be used from. Pick the least powerful kind that does the job — a map rendered in a browser never needs a secret key.

Rotate an API key without a gap

Rotation replaces a secret that may have leaked, or that has simply been in service long enough. There are two ways to do it and they have different gap behaviour.

MethodHowGap?
Rotate in placeDashboard → API keys → Rotate. The key keeps its id and name; a new secret is shown once; the old secret stops working immediately.Yes — between the rotation and your redeploy, requests with the old secret answer 401. Use it when the old secret must die *now*.
Create, deploy, revokeCreate a second key, deploy it everywhere, confirm lastUsedAt on the old key stops moving, then revoke the old one.No — two live keys coexist, so the switch is gapless. Use it for scheduled rotation.

Test keys cannot be rotated in place: a test key's id is signed into the token itself, so a "new secret under the same id" would leave the old token valid on the sandbox. Revoke the test key and create a new one instead — the dashboard says so when you try.

Revoke a key

Dashboard → API keys → Revoke. It is immediate and permanent: the next request with that key answers 401. A revoked key stays in the list with status: revoked so the audit trail of what existed, and when it was last used, survives the revocation. For a test key, the sandbox learns of the revocation within its refresh interval of about five minutes; a new test key works at once.

Keys belong to the account, not to the person who created them. A team member who leaves does not take a key with them, and a key keeps working until someone revokes it — so revoking is a step in off-boarding, not something that happens by itself.

Store the token safely

Where a key lives in development, CI and production
# .env — never committed; the file is in .gitignore
OFFENDERSEARCH_API_KEY=os_live_…

# CI / production: inject from the secret manager at run time
#   GitHub Actions   secrets.OFFENDERSEARCH_API_KEY
#   Cloud Run        --set-secrets OFFENDERSEARCH_API_KEY=offendersearch-key:latest
#   Kubernetes       a Secret mounted as an env var, not a ConfigMap
  • Environment variables or a secret manager, read at start-up. Never a string literal in code, never a config file in the repository, never a Slack message.
  • Server-side only. A key in a browser bundle, a mobile app or a public repository has been published; rotate it the moment you notice.
  • One key per service and per environment. Name them that way in the dashboard. A leak then costs one revocation.
  • Watch `lastUsedAt`. A key that is supposed to be retired but keeps being used is a deployment you forgot. A key that is used from somewhere it should not be is an incident.
  • Prefer the test key for everything that is not production. Integration tests, CI, demos and tutorials run against the sandbox with os_test_ keys — nothing to bill, nothing real to leak.

API token errors: 401 and 403

The response to a bad or missing key
HTTP/1.1 401 Unauthorized
Content-Type: application/json

{"detail": "Invalid or missing API key"}

# Causes, in the order to check them:
# 1. header name is not exactly X-API-Key
# 2. an os_test_ key was sent to api.offendersearch.app (or os_live_ to the sandbox)
# 3. the key was revoked or rotated — the dashboard shows status and the new prefix
StatusMeaningWhat to do
401No key, an unknown key, a revoked key, or a test key on the live host (and the reverse)Check the header name, the prefix against the host, and the key's status in the dashboard
403The key is valid; the account lacks the entitlement for this route (detail.code names it)Turn the feature on in the dashboard, or request access
429The per-key or per-account rate limit (sandbox: 60 a minute and 2,000 a day per test key)Wait Retry-After seconds; nothing was billed

Branch on the status, not on the text of detail. The complete contract — including 422 validation errors and when to retry — is on the API errors page.

API token FAQ

How do I generate an API key?

Sign up, open Dashboard → API keys, click Create key, and copy the secret while it is on screen. For a sandbox key click Create test key instead; it begins os_test_ and is never billed.

What does an api token example look like?

A header on a request: X-API-Key: os_live_… followed by about 48 random characters. The prefix identifies the environment, the rest is the secret, and the server compares a hash of the whole string with the hash it stored.

Is an api key the same as a client secret?

Same handling, different use. An API key is sent to the API on every request. A client secret is sent only to an OAuth token endpoint, which hands back a short-lived access token that is then sent to the API. Both must stay server-side.

What are the api token types?

API keys (long-lived account credentials), OAuth access tokens (short-lived, bearer), refresh tokens (long-lived, exchange-only), session tokens (a signed-in person), and publishable keys (non-secret, browser-safe, origin-limited). The table at the top of this page compares them.

Can an API key be used in a browser?

A secret key, no — it is then public. For browser use, providers issue a publishable key that can do only what is safe in public; OffenderSearch does this for the Map API, with the key limited to the origins you list.

What happens when an api key is required and missing?

The API answers 401 Unauthorized with a JSON detail. Nothing is searched and nothing is billed. The two endpoints that do not require a key are the coverage catalogues, GET /v1/sources and GET /v1/criminal/sources.

What this page does not cover

  • OAuth token issuance, refresh and the authorization code flow — see API key vs OAuth.
  • Signing requests with the key as an HMAC secret. We do not sign inbound requests; we sign outbound webhooks — see API signature.
  • Per-key scopes or per-key billing. Keys here carry neither; usage is recorded per account and shown in the dashboard.
  • Organisation SSO for signing in to the dashboard itself, which is separate from API authentication.