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 withX-Act-Asmust have paired the AY app as well; otherwise409 IDENTITY_NOT_REGISTERED. - A place to store secrets. The credential is shown once.
Choose a credential type
auth_method | You receive | Token grant | Use when |
|---|---|---|---|
key_json (default) | A service-account key file with an RSA private key | urn:ietf:params:oauth:grant-type:jwt-bearer (RFC 7523) | Your runtime can sign a JWT. Preferred. |
client_secret | client_id and client_secret | client_credentials | Your 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:
| Field | Rules |
|---|---|
name | Required, 3 to 100 characters |
type | Optional, lowercase, ^[a-z][a-z0-9-]{2,30}$, default general |
description | Optional, up to 500 characters |
service_endpoint | Optional URL, up to 500 characters. Where your agent accepts A2A tasks. |
skills | Up 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. |
intents | Optional, up to 20 short strings |
visibility | private (default) or public. Business agents are always private. |
auth_method | key_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
- The token is RS256, its
kidis in Zitadel's JWKS,issishttps://auth.machine-identity.com,audcontains the project ID, andexphas not passed (60 seconds tolerance). subbelongs to exactly one agent withkey_custody: platform.- 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.