Choose the right machine-to-machine credential on AuthPI — org API keys for the Core API, client_credentials for your own APIs, or agent identities.
Last updated 2026-06-12
AuthPI has three credentials for code that calls APIs without a user present, and they are not interchangeable. The single most common mix-up: org API keys authenticate calls to the AuthPI Core API; client_credentials tokens authenticate calls to your own APIs. Pick by destination first, then by identity model.
| Org API keys | M2M clients (client_credentials) | Agent identities | |
|---|---|---|---|
| Credential | key_... id + secret pair | c_... client id + client secret | agt_... id + secret verifier (or x402 wallet) |
| Authenticates against | The AuthPI Core API (manage users, issuers, webhooks, …) | Your own APIs | Your own APIs, plus x402 payment flows |
| Token exchange | None — HTTP Basic credentials on every request | Yes — short-lived JWT access token from your issuer’s /token endpoint | Yes — same client_credentials grant, client_id=agt_... |
| Token lifetime | n/a — the key is valid until revoked, blocked, or expired | Default 30 minutes (configurable per client); no refresh token, no ID token | Fixed 5 minutes, so scope changes propagate quickly |
| Scoping model | Authorization scopes (resource[.subresource]:action) gating Core API routes, deny-by-default | OAuth scopes drawn from the client’s allowed scope set | Per-agent scope set (arbitrary strings you define) |
| Rotation | /rotate endpoint; old secret stays valid 15 minutes | Rotate secret; old secret stays valid 15 minutes | Add a new secret verifier, then remove the old one |
| Secret storage | Hashed (SHA-256); plaintext shown once | Hashed (Argon2); plaintext shown once | Hashed (SHA-256); plaintext shown once |
| Choose when | A backend, CI job, or script manages AuthPI resources | Internal services need standard OAuth tokens for your APIs | Non-human actors (AI agents, bots) need first-class identity and org membership |
A 30-second decision path:
api.authpi.com? Org API key. Always. Tokens from your issuer are not valid there.client_credentials — your API verifies a standard JWT instead of holding shared secrets for every caller.All three secrets share one property: they are shown once at creation (or rotation) and only a hash is stored, so plan storage and rotation before you mint anything.
If the destination is api.authpi.com, the answer is an API key — there is no token exchange; the id + secret pair goes in the Authorization header as HTTP Basic on every request:
curl "https://api.authpi.com/v1/accounts/acc_7k2m9x4p1q8w5e3r6t0y2u4i7/issuers/i_9XK2mPqL4nF7Td/users?limit=10" \
-u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET"
The key’s scopes (for example issuers.users:read) determine which Core API routes it may call. Creation, scoping, rotation, and revocation are covered in Issue and manage API keys.
When service A needs to call your service B with a standard OAuth bearer token, register an M2M client on your issuer (application_type: "m2m", confidential, with client_credentials in its grant types) and exchange its credentials for a token:
curl -X POST "https://idp.authpi.com/i_9XK2mPqL4nF7Td/token" \
-u "c_8fK3mQ7xP2nL:$CLIENT_SECRET" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials&scope=read:reports"
{
"access_token": "eyJhbGciOiJFZERTQSIs...",
"token_type": "Bearer",
"expires_in": 1800,
"scope": "read:reports"
}
The client may authenticate with HTTP Basic as above (client_secret_basic) or by sending client_id and client_secret as form parameters (client_secret_post).
Service B verifies the JWT against your issuer’s public keys (discoverable at https://idp.authpi.com/{issuer_id}/.well-known/openid-configuration) and authorizes on its scopes. The response contains an access token only — no refresh token and no ID token; when it expires, request a new one. If you omit scope, the token carries the client’s full allowed scope set; the optional resource parameter sets the token’s aud claim (otherwise the audience is the client id). Grant mechanics and limits are in the OIDC reference.
Agents (agt_...) are first-class non-human identities: they can join organizations as members, carry their own per-identity scope sets, and authenticate with a secret verifier or an x402 wallet. For token-based auth they use the same client_credentials grant — just with an agent id:
curl -X POST "https://idp.authpi.com/i_9XK2mPqL4nF7Td/token" \
-u "agt_3k9mP2xQ7nL4fT8w:$AGENT_SECRET" \
-d "grant_type=client_credentials"
Agent tokens are fixed at 5 minutes so revoking an agent or changing its scopes takes effect almost immediately. Reach for agents over M2M clients when the caller is an actor in your product — an AI assistant acting inside a customer’s organization, a bot with its own audit trail — rather than anonymous service plumbing. See the agent identities guide for creating agents and managing their verifiers.
These options compose rather than compete. A typical production backend holds an org API key to manage AuthPI resources (provision users, register webhooks) and an M2M client to authenticate its calls to internal APIs — two credentials, two destinations, no overlap.
Sending an API key as a Bearer token. API keys are HTTP Basic credentials — key id as username, secret as password (-u "$AUTHPI_KEY_ID:$AUTHPI_KEY_SECRET"). Put the secret after Bearer and the Core API tries to verify it as a JWT and returns 401.
Calling the Core API with a client_credentials token. Tokens minted by your issuer’s token endpoint are scoped to your own APIs; the Core API will not accept them. To manage AuthPI resources from a backend, use an org API key.
Sharing one key across services. A single shared key means one rotation breaks every consumer at once, one leak exposes everything, and the audit trail can’t tell callers apart. Mint one minimally-scoped key per service.
Requesting openid with client_credentials. The grant never issues ID tokens — the openid scope is silently ignored. If you need user identity, you need a user flow, not M2M.
Hard-cutover rotations. Both API key and client secret rotations keep the old secret valid for 15 minutes. Use that window to roll the new secret out gradually instead of revoking and recreating the credential, which breaks every consumer instantly.
Expecting keys to survive their organization. Deleting an organization revokes all of its API keys as part of the deletion. Decommission or migrate the services using those keys before deleting the org — afterwards they fail introspection with revoked.
client_credentials details in the OIDC reference