AY Developer docs

Register with a Zitadel credential

Level 1 in detail, the two credential types, the token exchange, and what to store.

Level 1 is the default. AY creates a machine user for your agent at its identity provider (Zitadel) and hands you a credential once. Your agent trades the credential for short-lived access tokens.

Before you begin

  • An OIDC token of the owner account. A business owner registers on behalf of the business with the X-Act-As: <business id> header, see the API reference.
  • For a person: the AY app must be paired with the account. Until then, registration and attestation answer 409 IDENTITY_NOT_REGISTERED, because the agent's passport names the person's DID as its owner.
  • For a business: the business must be verified. Until then, registration and attestation answer 409 BUSINESS_NOT_VERIFIED, because the agent's passport names the business twin as its owner. The person acting for the business with X-Act-As must have paired the AY app as well; otherwise 409 IDENTITY_NOT_REGISTERED.
  • A place to store secrets. The credential is shown once.

Choose a credential type

auth_methodYou receiveToken grantUse when
key_json (default)A service-account key file with an RSA private keyurn:ietf:params:oauth:grant-type:jwt-bearer (RFC 7523)Your runtime can sign a JWT. Preferred.
client_secretclient_id and client_secretclient_credentialsYour runtime cannot sign JWTs, or you use a platform that only speaks client credentials.

Register

curl -X POST https://api.aytwin.ai/api/v1/agents \
  -H "Authorization: Bearer $AY_OIDC_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "Support Agent", "service_endpoint": "https://agent.example.com/a2a", "auth_method": "client_secret"}'

Request fields:

FieldRules
nameRequired, 3 to 100 characters
typeOptional, lowercase, ^[a-z][a-z0-9-]{2,30}$, default general
descriptionOptional, up to 500 characters
service_endpointOptional URL, up to 500 characters. Where your agent accepts A2A tasks.
skillsUp to 20 entries of { id, name, description }. Optional for the API, but required if a twin should call the agent: the twin resolves an agent by skills[].name, an agent without skills is never dispatched.
intentsOptional, up to 20 short strings
visibilityprivate (default) or public. Business agents are always private.
auth_methodkey_json or client_secret

Limits: five registrations per hour per account, ten active agents per account.

Changes after registration (service_endpoint, skills, description, guardrails) go through PATCH /api/v1/agents/{id} and PATCH /api/v1/agents/{id}/config with the owner's OIDC token, for a business again with X-Act-As. The agent's own machine token cannot change the agent record; it gets 401.

Store the credential

The credentials object appears only in this response.

"credentials": {
  "type": "key_json",
  "key_json": { "type": "serviceaccount", "keyId": "…", "key": "-----BEGIN RSA PRIVATE KEY-----…", "userId": "…" },
  "token_endpoint": "https://auth.machine-identity.com/oauth/v2/token",
  "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
  "scope": "openid urn:zitadel:iam:org:project:id:392496763820310530:aud"
}

The response also contains a legacy api_key with api_key_deprecated: true. Do not build on it. It stops working on the api_key_sunset date, 3 April 2027.

Get a token

Build an RS256 assertion (kid = keyId, iss = sub = userId, aud = https://auth.machine-identity.com, exp within one hour) and post it. The Quickstart has Node.js and Python code.

curl -X POST https://auth.machine-identity.com/oauth/v2/token \
  -d grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \
  -d "scope=openid urn:zitadel:iam:org:project:id:392496763820310530:aud" \
  -d "assertion=$ASSERTION"

The scope must include the project audience urn:zitadel:iam:org:project:id:392496763820310530:aud. Without it the token has no aud for AY and every call fails with 401 token_invalid, reason aud.

The Zitadel SDKs for Go, Python, Java, and Node.js read the key file directly and handle the exchange for you.

Call the API

curl https://api.aytwin.ai/api/v1/agents/me -H "Authorization: Bearer $ACCESS_TOKEN"

What AY checks on every call

  1. The token is RS256, its kid is in Zitadel's JWKS, iss is https://auth.machine-identity.com, aud contains the project ID, and exp has not passed (60 seconds tolerance).
  2. sub belongs to exactly one agent with key_custody: platform.
  3. The agent is active, the kill switch is off, the daily limit is not exceeded, and for business agents the owner's verification credential is valid.

Step 3 is the real safeguard. A token stays cryptographically valid until it expires, but the checks in step 3 apply immediately.