Guides

Machine-to-machine auth: API keys, clients, or agents

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.

Compare the three options

Org API keysM2M clients (client_credentials)Agent identities
Credentialkey_... id + secret pairc_... client id + client secretagt_... id + secret verifier (or x402 wallet)
Authenticates againstThe AuthPI Core API (manage users, issuers, webhooks, …)Your own APIsYour own APIs, plus x402 payment flows
Token exchangeNone — HTTP Basic credentials on every requestYes — short-lived JWT access token from your issuer’s /token endpointYes — same client_credentials grant, client_id=agt_...
Token lifetimen/a — the key is valid until revoked, blocked, or expiredDefault 30 minutes (configurable per client); no refresh token, no ID tokenFixed 5 minutes, so scope changes propagate quickly
Scoping modelAuthorization scopes (resource[.subresource]:action) gating Core API routes, deny-by-defaultOAuth scopes drawn from the client’s allowed scope setPer-agent scope set (arbitrary strings you define)
Rotation/rotate endpoint; old secret stays valid 15 minutesRotate secret; old secret stays valid 15 minutesAdd a new secret verifier, then remove the old one
Secret storageHashed (SHA-256); plaintext shown onceHashed (Argon2); plaintext shown onceHashed (SHA-256); plaintext shown once
Choose whenA backend, CI job, or script manages AuthPI resourcesInternal services need standard OAuth tokens for your APIsNon-human actors (AI agents, bots) need first-class identity and org membership

A 30-second decision path:

  1. Calling api.authpi.com? Org API key. Always. Tokens from your issuer are not valid there.
  2. One of your services calling another of your services? M2M client with client_credentials — your API verifies a standard JWT instead of holding shared secrets for every caller.
  3. The caller is an actor in your product — an AI agent working inside a customer’s organization, a bot that needs its own memberships and audit trail? Agent identity.

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.

Use an API key to call the Core API

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.

Use an M2M client to protect your own APIs

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.

Use an agent for non-human identities

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.

Avoid the common mistakes

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.

Next steps