Credentials and integrations
Scope API credentials
Give API keys and tokens a narrow audience, permission set, lifetime, owner, storage location, and revocation path.
An API key is a bearer credential. Whoever holds it can use it. The server can’t tell your nightly job from an attacker sending the same string. Unless the protocol adds stronger proof, possession is the whole story.
That’s why the shape of a credential matters so much. Narrow scope, short life, clear owner.
The common types differ mostly in lifetime and in where the authority lives. A static API key is easy to issue and lives until you revoke it. An OAuth access token is short-lived and scoped by the authorization server that issued it. A JWT carries its claims inside the token, like audience, scopes, and expiry, so the server can verify it without a lookup. Whichever you use, the scoping discipline is the same.
One credential per job
Create separate credentials per environment and per integration. A shared production key used by both billing and analytics means revoking it is an outage for both.
Yes, separate credentials mean more inventory work. In exchange you can see ownership and blast radius at a glance:
key_billing_prod_a91x owner: billing-team scope: invoices:read charges:create
key_analytics_prod_covk owner: data-team scope: invoices:read
key_billing_stg_m2p owner: billing-team scope: invoices:read charges:create
Scope each key to what the integration does. Analytics reads invoices, so it gets invoices:read and nothing else.
Keep keys out of URLs and browser code. A key in a query string ends up in access logs, proxies, and browser history. Store it only where it’s needed.
Log the identifier, never the secret
Your logs should show which credential was used without ever recording its value:
2026-08-03T09:12:44Z key=key_analytics_prod_covk route="GET /invoices" status=200
The identifier is enough. Add a last-used timestamp and the expected source, and responders can tell an abandoned key from one still serving production.
Make revocation routine
Rotation and revocation should be boring. Not an emergency procedure you run for the first time during an incident, with everyone watching.
Revoke the analytics key, then test both keys:
# after revoking the analytics key
curl -i https://api.example.com/invoices \
-H "Authorization: Bearer $ANALYTICS_KEY"
# HTTP/1.1 401 Unauthorized
curl -i https://api.example.com/invoices \
-H "Authorization: Bearer $BILLING_KEY"
# HTTP/1.1 200 OK <- billing unaffected, blast radius contained
The second request is the real test. Revoking a credential is easy. Proving the revocation touched nothing else is what scoping buys you. With one shared key, that second request would have failed too, and billing would be down.
Try this on your own system: find one shared key and replace it with two scoped credentials. Record the owner and permissions of each. Then revoke the analytics one and prove billing still works while analytics gets a 401.
Lesson completed