HeyIdentity Agent API

HeyIdentity Agent API: guide for AI agents

HeyIdentity is a hosted fake OpenID Connect / OAuth 2.0 identity provider for development and tests. The Agent API lets an AI agent create a working test tenant for the user's application, configure it to match the app, and hand it to the user to claim. Plain text for agents: /agent/llms.txt.

Workflow

  1. Read the catalog (GET /agent/v1/catalog).
  2. Create a tenant (POST /agent/v1/tenants) with a random, secret Idempotency-Key header and the current accepted_terms_version.
  3. Show the claim URL to the user right away, and save the agent token and claim URL to .heyidentity/tenant.json (git-ignored).
  4. Configure the tenant to match the application, one item per request: clients and redirect URIs, users, claims, settings.
  5. Validate the configuration (POST /agent/v1/configurations/{configuration_id}/validate).

Endpoints

Base URL https://api.heyidentity.dev. JSON only. Authenticate with Authorization: Bearer <agent token> (not needed for creation and the catalog).

Moved on 2026-10-24: the Agent API used to be served on the app host (app.<domain>). Its base URL is now https://api.heyidentity.dev/agent/v1, and the old paths answer 404 there. Tenants and agent tokens are unchanged.

GET /agent/v1/catalogprofiles, URL shapes, token shape options, standard claims, limits and the current terms (no token)
POST /agent/v1/tenantscreate a tenant: { accepted_terms_version, display_name?, profile? } with an Idempotency-Key header (no token); returns the agent token and claim URL
GET /agent/v1/tenantthe tenant, its configurations and their endpoints
POST /agent/v1/configurationscreate another configuration: { name, profile? }; a retry with the same name answers configuration_exists
GET /agent/v1/configurations/{configuration_id}the configuration as items
POST /agent/v1/configurations/{configuration_id}/validateproblems and warnings, without changing anything
POST .../clients, PUT|DELETE .../clients/{client_id}clients, redirect URIs and scopes
POST .../users, PUT|DELETE .../users/{key}fictional test users and their claims
POST .../claims/custom, PUT|DELETE .../claims/custom/{name}custom claims
PUT .../claims/standardenabled standard claims: { enabled: [...] }
PUT .../settingsprofile, token shape, token lifetime, client secret validation
https://api.heyidentity.dev/agent/mcpMCP server (streamable HTTP); its tools take agent_token as an argument.
https://api.heyidentity.dev/agent/v1/openapi.jsonOpenAPI 3.1 document of these endpoints; import it to call the API as REST tools.

Request bodies

JSON objects with exactly these fields (snake_case); an unknown field is refused with 400 invalid_request naming it. On edits, fields left out keep their value. The catalog's request_shapes has the same as JSON schemas.

POST /agent/v1/tenants (create_tenant)

accepted_terms_versionstring, requiredThe current Terms of Use version (catalog terms.current_version), accepted on behalf of the operator running the agent.
display_namestringThe tenant's display name, at most 200 characters. Default: Agent test tenant.
profilestringGeneric (default) or Microsoft (an Entra ID drop-in).

POST /agent/v1/configurations (create_configuration)

namestring, requiredThe configuration's name, at most 200 characters and unique in the tenant ignoring case, e.g. staging. A retry with the same name answers configuration_exists with the existing configuration.
profilestringGeneric (default) or Microsoft (an Entra ID drop-in).

POST /agent/v1/configurations/{configuration_id}/clients (add_client)

client_idstring, requiredThe client id. Required when adding; fixed afterwards (it is the id in the URL when editing).
client_secretstringThe client secret; checked only when the configuration's validate_client_secrets setting is on.
allow_all_return_urlsbooleanAccept any redirect URI. Convenient for local tests; default false.
redirect_urisarray of stringExact redirect URIs the app uses, e.g. http://localhost:5173/callback (http(s) with a host, or an app scheme such as com.example.app:/callback or myapp://callback).
allowed_scopesarray of stringScopes the client may request, e.g. openid, profile, email, offline_access, api://orders/read. Default when adding: openid, profile.
exposed_scopesarray of stringScopes this client exposes as an API (resource), e.g. api://orders/read. Tokens for these scopes get this client as audience.
access_token_audiencestringAccess token audience when the request names no API scope; the client id when not set.

PUT /agent/v1/configurations/{configuration_id}/clients/{client_id} (edit_client)

client_idstringThe client id. Required when adding; fixed afterwards (it is the id in the URL when editing).
client_secretstringThe client secret; checked only when the configuration's validate_client_secrets setting is on.
allow_all_return_urlsbooleanAccept any redirect URI. Convenient for local tests; default false.
redirect_urisarray of stringExact redirect URIs the app uses, e.g. http://localhost:5173/callback (http(s) with a host, or an app scheme such as com.example.app:/callback or myapp://callback).
allowed_scopesarray of stringScopes the client may request, e.g. openid, profile, email, offline_access, api://orders/read. Default when adding: openid, profile.
exposed_scopesarray of stringScopes this client exposes as an API (resource), e.g. api://orders/read. Tokens for these scopes get this client as audience.
access_token_audiencestringAccess token audience when the request names no API scope; the client id when not set.

POST /agent/v1/configurations/{configuration_id}/users (add_user)

keystring, requiredThe user key shown on the sign-in page and used as sub by default. Required when adding; fixed afterwards.
display_namestringThe name shown on the sign-in page and emitted as the name claim.
substringThe sub claim; the key when not set.
iconstringA built-in icon id for the sign-in page.
access_token_substringA different sub for access tokens only.
claimsobjectThe user's claims: name to a string, number, boolean or array of strings, e.g. { "email": "alice@example.com", "roles": ["admin"] }. Use fictional people only.
claim_targetsobjectWhich token each claim goes into: name to both (default), id_token or access_token.

PUT /agent/v1/configurations/{configuration_id}/users/{key} (edit_user)

keystringThe user key shown on the sign-in page and used as sub by default. Required when adding; fixed afterwards.
display_namestringThe name shown on the sign-in page and emitted as the name claim.
substringThe sub claim; the key when not set.
iconstringA built-in icon id for the sign-in page.
access_token_substringA different sub for access tokens only.
claimsobjectThe user's claims: name to a string, number, boolean or array of strings, e.g. { "email": "alice@example.com", "roles": ["admin"] }. Use fictional people only.
claim_targetsobjectWhich token each claim goes into: name to both (default), id_token or access_token.

POST /agent/v1/configurations/{configuration_id}/claims/custom (add_custom_claim)

namestring, requiredThe claim name. Required when adding; fixed afterwards.
valuestringThe claim value.
targetstringboth (default), id_token or access_token.

PUT /agent/v1/configurations/{configuration_id}/claims/custom/{name} (edit_custom_claim)

namestringThe claim name. Required when adding; fixed afterwards.
valuestringThe claim value.
targetstringboth (default), id_token or access_token.

PUT /agent/v1/configurations/{configuration_id}/claims/standard (set_standard_claims)

enabledarray of string, requiredThe standard claims to enable, e.g. ["auth_time", "jti"]. Required.

PUT /agent/v1/configurations/{configuration_id}/settings (edit_settings)

profilestringGeneric or Microsoft.
validate_client_secretsbooleanRequire client secrets at the token endpoint.
token_lifetime_minutesstringToken lifetime in minutes (positive).
token_shapeobjectClaim shape options (Generic profile only); fields left out keep their value.

Terms

Current Terms of Use version: 2026-10-23 (Terms of Use, Privacy Notice).

Creating a tenant accepts these terms on behalf of the operator running the agent: send accepted_terms_version with the current version. The operator is responsible for using fictional test people only, and agent tokens are the operator's secret.

Rules

  • Right after creating a tenant, show its claim URL to the user with the ready-to-use message in next_steps: an unclaimed tenant and its configuration are deleted when it expires. Repeat it whenever a response's first next_steps entry is show_user.
  • Save the agent token and the claim URL to .heyidentity/tenant.json and add .heyidentity/ to .gitignore. The agent token is the operator's secret: never commit it or put it in a URL.
  • Never ask the user for their email address and never send one to the Agent API: the user claims the tenant themselves by opening the claim URL.
  • Use only fictional test people in users and claims, with email-shaped values on reserved test domains (for example alice@example.com, bob@example.org). Never use real people's names, emails or identifiers, even when modelling a real organisation.
  • Send an Idempotency-Key header when creating a tenant: a random secret of 32 to 128 characters from A-Z a-z 0-9 - _ (for example crypto.randomUUID(), uuidgen or openssl rand -hex 16), generated once per intended tenant and kept secret like the agent token. Retry only with the same key and body, and only if you never received the response. Never retry creation in a loop.
  • Read the catalog (GET /agent/v1/catalog) before configuring a tenant to match an application: it describes the profiles, URL shapes, token shape options and standard claims.
  • Prefer a separate client per environment (for example web-local, web-staging) within one configuration. Create another configuration (POST /agent/v1/configurations, with a name unique in the tenant) only for a different profile or a separate set of users. Only the user deletes configurations, on their management page. Only the user deletes tenants, on their management page after claiming; agents never delete either. The user can rename a configuration on their management page; keep its id, not its name, to find it again.

Errors and what to do next

StatusErrorMeaningNext
429rate_limitedA per-source, per-token or global limit was hit, or too many unclaimed tenants exist.Wait for the Retry-After time, then continue. Never retry creation in a loop; reuse your tenant.
410tenant_expiredThe tenant expired unclaimed and was deleted with its configuration.Create a new tenant, show its claim URL to the user right away, and update the application's configuration.
410tenant_deletedThe user deleted the tenant on its management page; deleted_at says when. It can't be restored.Stop and ask the user whether they want a new tenant before creating one; never create one automatically.
403not_supportedAgents cannot delete configurations or tenants.Ask the user to delete the configuration, or the whole tenant, on their management page.
400usage_limit_exceededA limit of the tenant's tier was reached; limit names it and its maximum. The tier follows the plan (pro, enterprise) and otherwise the assurance level.Remove an unused item, or ask the user to claim the tenant, which raises the limits. A claimed tenant on the free plan gets higher limits with the Pro plan (next_steps links to the plans). At the configuration limit of a claimed tenant, the user can delete a configuration they no longer need on their management page.
400invalid_request / invalid_itemA field is missing or invalid; field names it.Fix that field and send the request again.
400terms_not_acceptedaccepted_terms_version is missing or not current.Read the terms at terms_url and send current_terms_version.
400email_not_acceptedThe request contained an email address.Remove it; the user claims the tenant on the claim page.
401invalid_tokenThe agent token is missing, unknown or revoked.Use the saved token. If it was lost or revoked, the tenant owner issues a new one on the management page after claiming.
404not_foundNo configuration with this id belongs to your tenant, or there is no Agent API endpoint at that path.GET /agent/v1/tenant lists your configurations; the catalog's endpoints list the API.
409already_claimedA creation retry's tenant was already claimed, so the first response was received.Use the agent token you saved.
409idempotency_key_spentThe tenant created with this Idempotency-Key already received its response and its agent token has been used; nothing was issued.Use the agent token you saved. If it is lost, create a new tenant with a new key.
409concurrent_changeThe tenant changed while a creation retry was processed; nothing changed.Retry once more with the same Idempotency-Key and body.
409configuration_existsThe tenant already has a configuration with this name (ignoring case); existing_configuration describes it and nothing was created.Reuse that configuration if its profile is the one you need; otherwise choose another name.
409configuration_unloadableThis configuration can't be edited as items.Ask the user to delete it on their management page, or to contact support.
405method_not_allowedThe path exists but not with this HTTP method.Add items with POST, edit them with PUT (fields left out keep their value) and remove them with DELETE.
415unsupported_media_typeThe request body is not JSON.Send the body as JSON with Content-Type: application/json.
422idempotency_key_reusedThe Idempotency-Key was used with a different body.Use a new key for a different request.

Lifecycle of an unverified tenant

A tenant you create is unverified and expires 7 days after creation. It works right away (discovery, sign-in, tokens) under the unverified limits. The user keeps it by opening the claim URL, entering their own email and confirming the link they receive; the tenant then has no expiry, gets the claimed limits, and your agent token keeps working. An unclaimed tenant is deleted with its configuration when it expires, and cannot be recovered. For 90 days its agent tokens get 410 tenant_expired; after that they get 401. After claiming, the user can also delete the tenant at any time on their management page; its agent tokens that were not revoked then get 410 tenant_deleted for 90 days, and 401 after that.

Limits

TierConfigurationsClientsUsersBytesTokens/min
unverified13103276820
email_verified (claimed, free plan)552065536120
pro plan (claimed)2020100262144300
enterprise plan (granted by HeyIdentity on request)50502502621441200

Agent API: 10 tenant creations per source per hour; 60 calls per agent token per minute. A source is one IPv4 address or one IPv6 /64, so agents that share an egress address (a NAT, shared CI runners) share these limits.

All limits and how to tell rejections apart: https://heyidentity.dev/docs/limits.html

What agents can't do

  • Delete configurations: the user deletes them on the management page.
  • Delete a tenant: the user deletes it on the management page after claiming; an unclaimed tenant is deleted when it expires.
  • Revoke or list agent tokens: that is the tenant owner's, on the management page after claiming.
  • Collect or send the user's email: the user claims the tenant on the claim page.