API key vs OAuth client credentials: which to use
An API key identifies your application. OAuth proves that a user, or a machine, granted it something. Here is each flow as it actually runs, and the decision rule that picks one.
API key vs OAuth: the short answer
Use an API key when your own backend calls a service and no end user is in the picture. Use OAuth 2.0 client credentials when a machine needs short-lived, scoped, centrally revocable credentials from an authorization server. Use the OAuth authorization code flow when a person is delegating access to their own data in a third-party app. Everything else on this page is the detail behind those three sentences.
| API key | OAuth client credentials | OAuth authorization code | |
|---|---|---|---|
| Identifies | An application (an account) | An application, via an authorization server | A user, who granted an application access |
| Credential sent | The key itself, on every request | A short-lived access token obtained from the token endpoint | A short-lived access token; a refresh token to get more |
| Lifetime | Until revoked or rotated | Minutes to an hour, then re-issued | Access minutes; refresh days to months |
| Scopes | Usually none; permissions live on the account | Yes, at issue time | Yes, consented by the user |
| User present? | No | No | Yes, once, at consent |
| Setup cost | One header | A token endpoint and a client registration | Redirects, a callback route, PKCE, token storage |
| Fits | Server-to-server integrations of one account | Fleets of services, zero-trust networks, platforms issuing credentials to tenants | Sign in with…, third-party apps acting for a user |
API key authentication, with an example
An API key is a long random secret the provider issues to an account. The client sends it on every request, usually in a header. The provider stores a one-way hash, looks the hash up, and treats the request as that account. There is no token exchange, no expiry clock and no consent screen — which is why it is the right shape for a backend talking to a data API.
This is how the OffenderSearch API authenticates: one X-API-Key header, one key format, every product. The key is a pure credential — it carries no scopes; what an account may do (the Map API, FCRA partner access) is a property of the account and applies to every key on it. Run the request below against the sandbox with a test key.
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"}}'X-API-Key) or an Authorization header, never as ?api_key= in the URL. Query strings land in server logs, proxy logs, browser history and referrer headers; a request header lands in none of them.Where the key comes from, how it is shown once, rotated and revoked is on the API token example page.
OAuth 2.0 client credentials flow, step by step
The client credentials grant (RFC 6749 §4.4) is OAuth for machines. The client authenticates to the authorization server with its own client_id and client_secret, asks for a token with the scopes it needs, and receives a short-lived access token it then presents to the API as a bearer token. No user is involved at any point.
- Register the client. The authorization server issues a
client_idandclient_secretto your service. The secret is a credential exactly like an API key — same storage rules. - Request a token.
POST /oauth/tokenwithgrant_type=client_credentials, the client credentials (HTTP Basic or form fields) and thescopeyou want. - Receive the access token. The response carries
access_token,token_type: Bearer,expires_in(seconds) and the grantedscope. There is no refresh token in this grant: when the token expires you simply ask for another. - Call the API.
Authorization: Bearer <access_token>on every request untilexpires_inelapses. Cache the token for its lifetime; do not request a new one per call. - Handle 401. An expired or revoked token answers
401. Fetch a fresh token once, retry once, then surface the error.
POST /oauth/token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)
grant_type=client_credentials&scope=reports:read
HTTP/1.1 200 OK
Content-Type: application/json
{
"access_token": "eyJhbGciOi...",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "reports:read"
}
GET /v1/reports HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOi...What it buys over a static key: tokens expire on their own, so a leaked token is a small window; scopes are decided at issue time; one authorization server can revoke a client for every API behind it. What it costs: a token endpoint to run or rent, a client registry, and a token cache in every caller.
Access tokens, bearer tokens and JWTs
An access token is whatever the authorization server hands back; the API accepts it as a bearer token — whoever bears it is authorized, which is why it is short-lived. Two formats are common. An opaque token is a random string the API has to look up (introspection). A JWT (RFC 7519) is a signed JSON document the API can verify locally with the issuer's public key, no lookup needed.
# A JWT access token is three base64url parts: header.payload.signature
eyJhbGciOiJSUzI1NiIsImtpZCI6IjIwMjYtMTAifQ
.eyJpc3MiOiJodHRwczovL2F1dGguZXhhbXBsZS5jb20iLCJzdWIiOiJzdmMtcmVwb3J0cyIsImF1ZCI6ImFwaS5leGFtcGxlLmNvbSIsImV4cCI6MTc5MzAwMDAwMCwic2NvcGUiOiJyZXBvcnRzOnJlYWQifQ
.MEUCIQD…
# decoded payload
{ "iss": "https://auth.example.com", "sub": "svc-reports", "aud": "api.example.com",
"exp": 1793000000, "scope": "reports:read" }| Claim | Meaning | What a resource server checks |
|---|---|---|
iss | Who issued the token | Matches the authorization server you trust |
sub | Who the token is about (a user or a client) | Becomes the principal for the request |
aud | Which API it was minted for | Equals this API, or reject — a token for another service is not valid here |
exp | Expiry (Unix seconds) | In the future, with a small clock skew allowance |
scope | What was granted | Covers the operation being attempted |
| signature | Issuer's signature over header + payload | Verifies against the issuer's published key (kid picks which) |
Which to use: the decision rule
Ask one question first: is an end user delegating access to their own data? If yes, it is the authorization code flow, full stop. If no, it is a machine credential, and the choice between an API key and client credentials is about operations, not security theatre.
| Situation | Use | Why |
|---|---|---|
| Your backend calls a data API under your account | API key | One credential, one header, no token plumbing. Rotate on a schedule. |
| A third-party app acts for your users | Authorization code + PKCE | The user consents; the token is scoped to them and expires. |
| Dozens of services, several APIs, one identity provider | Client credentials | Central issue and revocation; short-lived tokens; scopes per service. |
| A worker or CI job with no user present | API key or client credentials | Never borrow a user's token for a job that outlives their session. |
| Code running in a browser or mobile app | Authorization code + PKCE, or a publishable key with origin limits | Anything shipped to a client is public. A secret API key in a client has been published. |
| An MCP server or AI agent calling a tool | The tool's own credential, held server-side | The agent holds the key; the model never sees it. See the MCP server. |
Many systems use both at once: a key identifies the application for rate limiting while an OAuth token authorizes access to one user's data. That is not indecision; it is two questions with two answers.
Securing APIs: the checklist that applies to both
- TLS everywhere. A credential sent over plain HTTP is a credential on the wire. Refuse
http://at the edge. - Credentials in headers, never in URLs. Query strings are logged by everything between the client and the server.
- Store hashes, not secrets. The provider keeps a one-way hash of each key and compares hashes; a database leak then leaks nothing usable. (This is how OffenderSearch stores keys.)
- Show a secret once. At creation, then never again. If it is lost, rotate.
- Rotate without a gap. Create the new credential, deploy it, then revoke the old one. Two live keys may coexist for exactly this reason.
- One credential per service or environment. A leak is then one revocation, not a redeploy of everything.
- Least privilege. Scopes for tokens; separate accounts or publishable keys with origin limits for anything that runs in a browser.
- Rate limit per credential and answer `429` with `Retry-After`. It protects the API and tells an honest client what to do. Ours is documented under errors, rate limits and retries.
- Log the principal, not the credential. Record which key id made a request; never write the key itself to a log line.
- Validate `aud` and `exp` on every token, and verify signatures against the issuer's published keys, not a key you copied once.
How OffenderSearch authenticates (so you know what to expect)
Plainly: the APIs use API keys, and there is no OAuth flow and no bearer token for them. Create a key at Dashboard → API keys, send it as X-API-Key, done. A key beginning os_test_ works only on sandbox.offendersearch.app; a key beginning os_live_ works only on api.offendersearch.app; sending one to the other host answers 401. The two keyless endpoints are the coverage catalogues, GET /v1/sources and GET /v1/criminal/sources, so you can audit coverage before you sign up.
Two places look like OAuth and are not. The FCRA consumer portal issues a 30-minute session (POST /v1/consumer/portal/session) that a consumer's browser sends as a bearer token — it is a session for a person reading their own report, documented on the consumer portal page, not a way to call the APIs. And the Map API can issue a publishable browser key limited to the sites it may be used from, for maps rendered client-side; it can read map tiles and nothing else.
Webhooks are the other direction of trust: every event we send is signed with an HMAC over a timestamp and the body, so your receiver can prove it came from us. That scheme is on the API signature page, and you can exercise it with the webhook tester.
API authentication errors and what they mean
| Status | API key world | OAuth world | Fix |
|---|---|---|---|
401 | No key, unknown key, revoked key, or the wrong prefix for the host | Missing, expired, malformed or revoked access token | Check the header name; check the prefix against the host; refresh the token once and retry once |
403 | Valid key, but the account lacks the entitlement (detail.code names it) | Valid token, insufficient scope, or wrong aud | Enable the feature on the account; request the scope at issue time |
429 | Per-key or per-account limit hit; Retry-After says how long | Same, per client | Wait the header's seconds, then retry; nothing was billed |
Branch on the status code, not on the sentence in detail — the wording varies by route and may change. The full error contract is on the API errors page.
API authentication FAQ
api key vs oauth — which is more secure?
Neither is more secure by nature. An API key stored as a hash, sent in a header over TLS, rotated on a schedule and scoped to one service is as safe as a well-run OAuth deployment, and simpler to get right. OAuth is more secure *when a user is delegating access*, because a key cannot express "this user allowed this app to do this much for this long".
What is the oauth client credentials flow?
The grant a machine uses to get an access token for itself: it authenticates with its client id and secret at the token endpoint and receives a short-lived bearer token with the requested scopes. No user, no redirect, no refresh token.
What is an access token?
The credential an API accepts on a request after OAuth has run: a short-lived string (opaque, or a signed JWT) presented as Authorization: Bearer …. It says what was granted, to whom, by which issuer, until when.
Is an api key a bearer token?
Functionally yes — whoever holds it is authorized — which is why a key needs the same handling as a token: TLS, headers, server-side only. Some APIs accept keys as Authorization: Bearer <key>; OffenderSearch uses a dedicated X-API-Key header so a key is never confused with an OAuth token.
What is the authorization code flow for?
Letting a person grant a third-party application access to their data on another service without sharing their password: the user consents at the authorization server, the app receives a code, and exchanges it on the backend for tokens scoped to that consent.
Does OffenderSearch support OAuth or Auth0?
No. The APIs authenticate with an API key in the X-API-Key header. There is nothing to configure in Auth0 or any identity provider to call them; your backend holds the key and makes the call.
What is restful api authentication?
Any of the schemes on this page applied to a REST API: an API key header, a bearer access token from OAuth, or (rarely today) HTTP Basic. REST itself defines none of them; it only asks that each request carry what the server needs to authenticate it.
What this page does not cover
- Configuring a specific identity provider (Auth0, Okta, Cognito, Entra). The flows above are the standard ones those products implement; their console steps are in their own documentation.
- SAML, OpenID Connect sign-in, and user-facing login for your application. OIDC is OAuth plus an identity token; the flows are the same, the purpose is sign-in rather than API access.
- mTLS and request signing as authentication. Signing is covered separately on the API signature page, because we use it for webhooks.
- An OAuth server for the OffenderSearch APIs. There is none; see the section above.