--- title: "Riferimento del contratto di identità" description: "Ogni token, claim, parametro della pagina di accesso, codice motivo e rilascio del contratto di accesso, con il blocco dell'agente e il changelog." last_updated: "2026-10-31T10:54:34+02:00" --- # Riferimento del contratto di identità Source: https://busymate.ai/it/docs/reference/identity-contract Last modified: 2026-10-31T10:54:34+02:00 The identity contract `bmai-identity` 2.0.0 (`@busymate/identity-contract` 2.0.0-beta.4) as tables: every token, claim, reason code and release. For the setup steps, read [Sign-in and identity](https://busymate.ai/it/docs/identity). **Version:** 2.0.0-beta.4 (current) · earlier: [2.0.0-beta.3](#200-beta3-2026-10-30) · [2.0.0-beta.2](#200-beta2-2026-10-29) · [2.0.0-beta.1](#200-beta1-2026-10-29) Machine-readable: [busymate.ai/.well-known/bmai-integration.json](https://busymate.ai/.well-known/bmai-integration.json). Anything marked **[PLANNED]** is not live yet. ## Releases | Part | Version | Status | |---|---|---| | [Web SDK `@busymate/ai-web`](https://www.npmjs.com/package/@busymate/ai-web) | `0.2.0` | Live | | [Website script `/embed/v1.js`](https://busymate.ai/embed/v1.js) | `v1` | Live | | [iOS SDK](https://github.com/serebano/busymate-ai-sdk-ios/releases/tag/1.0.2) | `1.0.2` | Live | | [Android SDK](https://github.com/serebano/busymate-ai-sdk-android/releases/tag/1.0.2) | `1.0.2` | Live | | [macOS SDK](https://github.com/serebano/busymate-ai-sdk-macos/releases/tag/1.0.2) | `1.0.2` | Live | | [Windows SDK](https://github.com/serebano/busymate-ai-sdk-windows/releases/tag/1.0.2) | `1.0.2` | Live | | [Linux SDK](https://github.com/serebano/busymate-ai-sdk-linux/releases/tag/1.0.2) | `1.0.2` | Live | | [Electron SDK](https://github.com/serebano/busymate-ai-sdk-electron/releases/tag/1.0.2) | `1.0.2` | Live | | [Tauri SDK](https://github.com/serebano/busymate-ai-sdk-tauri/releases/tag/1.0.2) | `1.0.2` | Live | | [Bridge files `busymate.bridge/2` (React Native, Flutter)](https://busymate.ai/sdk/v2/2.0.0/) | `2.0.0` | Live | | [v1 kit drop-ins](https://busymate.ai/sdk/v1/kit/) | `1` | Legacy | | [Verifier `@busymate/ai-verify` (Node, Deno; Python, PHP, Go files in the repository)](https://www.npmjs.com/package/@busymate/ai-verify) | `0.1.2` | Live | | [Check CLI `@busymate/identity-check`](https://www.npmjs.com/package/@busymate/identity-check) | `0.1.2` | Live | ## Sign-in proof (launch token) Your backend signs it for the signed-in user. The chat sends it to us once, and we verify it against your published keys. | Property | Value | |---|---| | Algorithms | `ES256`, `RS256`. Anything else is refused. | | Lifetime (`exp - iat`) | 30 to 900 s per provider. Use 120 s. | | Clock skew | 0 to 120 s per provider, default 30 s | | Size | At most 16,384 characters | | Single use | A reused `jti` or nonce gets `409` | | Session it opens | 5 to 1440 minutes per provider, default 120 | | Claim | Required | Rule | |---|---|---| | `iss` | Yes | Your issuer. Must equal the registered issuer. | | `aud` | Yes | `busymate-ai`. Must equal a registered audience. | | `sub` | Yes | Your immutable internal user id. Never an e-mail address or phone number, and one person per `sub`. A shared organization id gives every member the same account. | | `` | Yes | Your tenant value (default claim name `tenant_id`). Must equal the registered tenant claim value. | | `nonce` | Yes | The `nonce` from the request, unchanged. Busymate AI generates it; you never make your own. | | `jti` | Yes | Random, unique per token. Single use: a replay gets `409`. | | `iat` | Yes | Now. | | `exp` | Yes | Now + 120 s. Keep it at your provider's maximum token age or less. | | `name, email, ...` | No | Optional display claims. Only what the chat may show; never secrets. | ## Login page handoff (`login_url`) For the full chat page and channel sign-in, where there is no code of yours on the chat page. | Parameter we send | Meaning | |---|---| | `return_to` | Where to send the user back. Redirect to exactly this URL. | | `bmai_nonce` | The nonce to sign into the token, unchanged. | | `bmai_client` | Which client asks (`web`, or `channel` for channel sign-in). | | `bmai_prompt` | `none`: a silent check. Never show UI. | | Your answer | Redirect to | |---|---| | Signed in | `return_to#bmai_token=&bmai_nonce=` | | Silent check, no session | `return_to#error=login_required` (also accepted: `#bmai_error=login_required`) | A sign-in request stays valid for 10 minutes. The silent check gives up after 3 seconds. - Load inside a hidden frame on the chat host: send `Content-Security-Policy: frame-ancestors ` and do not send `X-Frame-Options: DENY`. - Send the session cookie with `Secure; SameSite=None`, or the frame cannot see the signed-in session. - On `bmai_prompt=none` never render UI: redirect with the token, or with `#error=login_required`. - Redirect to exactly `return_to` plus the fragment; the nonce is echoed unchanged. ## Action proof (actor token) We send it to your MCP server or API on each tool call by a signed-in customer. You verify it and act as that customer. ### Version 3 | Property | Value | |---|---| | Algorithm | `ES256`, `typ: bmai-actor+jwt` | | Keys | `https://busymate.ai/.well-known/bmai-actor-jwks.json`. Rotated every 90 days with 14 days of overlap: never pin a key. | | Issuer | `https://busymate.ai` | | Lifetime | At most 120 s. Clock skew 10 s. | | Header | `X-Bmai-Actor: `. In `Authorization: Bearer` once `GET /api/bmai/status` returns `{"actorVerifier":true,"actor":3}`. | | Who gets one | Signed-in customers only, never guests | | Rollout | Per workspace (`actor_token_v3`), signed beside version 2 for 90 days | | Claim | Rule | |---|---| | `iss` | `https://busymate.ai`. | | `aud` | The EXACT origin of the connector URL. | | `sub` | The verified launch subject. | | `idp` | The identity provider id. Map users by (idp, sub), never by sub alone. | | `tenant_id` | Your Busymate AI tenant id. | | `connector_id` | Your connector id. | | `support_session_id` | The chat session. | | `actor_kind` | Always `identified`. | | `confirmed` | True only for a call the customer confirmed. | | `tool` | The tool this token was minted for. | | `iat` | Issued at. | | `nbf` | Not before. | | `exp` | iat + at most 120. | | `jti` | Unique id; a verifier rejects a repeat. | ### Version 2 (compatibility) | Property | Value | |---|---| | Algorithm | `HS256`, `kid: bmai-support-v2`, with the secret you set in **Console › Connections › Actor verifier** (at least 32 bytes; a rotated-out secret works for 24 h) | | Issuer | `https://busymate.ai` | | Lifetime | At most 300 s. Clock skew 10 s. | | Header | `Authorization: Bearer ` | | Who gets one | `identified` and `anonymous` visitors: check `actor_kind` | | Claim | Rule | |---|---| | `iss` | `https://busymate.ai`. | | `aud` | The EXACT origin (scheme and host) of the connector URL. | | `sub` | The verified user id. For `actor_kind: "anonymous"` it is `anonymous:`: never map it to an account. | | `tenant_id` | Your Busymate AI tenant id. | | `connector_id` | Your connector id. | | `support_session_id` | The chat session. | | `policy_revision` | The published revision that allowed the call. | | `actor_kind` | `identified` or `anonymous`. Check it: anonymous means public data only. | | `confirmed` | True only for a call the customer confirmed. Require it for money and writes. | | `iat` | Issued at. | | `nbf` | Not before (iat - 5). | | `exp` | iat + 300. | | `jti` | Unique id; optionally reject a repeat until it expires. | ### Rules for every version - Check `actor_kind`: an anonymous token is for public data only. - Take the user only from `sub` (v3: from `(idp, sub)`), never from a tool argument such as `accountId`. - Require `confirmed === true` for anything that moves money or writes data. - Optionally reject a repeated `jti` until it expires. - Optionally serve `GET /api/bmai/status` returning `{"actorVerifier": true}` so the Console can show "connected". ## Reason codes Additive: a new code may appear, and a code never changes meaning. The same codes are in the [manifest](https://busymate.ai/.well-known/bmai-integration.json). | Code | Where | What it means | Fix | |---|---|---|---| | `clock_skew` | Sign-in proof | Your server clock is out of step with ours. | Sync the server clock (NTP) and mint a fresh token. | | `token_expired` | Sign-in proof | The token expired before it reached us. | Mint a new token on every call; never cache one; keep `exp - iat` at 120 s or less. | | `signature_invalid` | Sign-in proof | The token signature does not match your published keys. | Sign with the key whose public half is in your JWKS; for ES256 use raw r\|\|s (64 bytes), not DER. | | `unknown_kid` | Sign-in proof | The token's `kid` is not in your JWKS. | Publish the signing key in your JWKS before using it; keep the old key until old tokens expire. | | `jwks_unavailable` | Sign-in proof | We could not fetch your JWKS. | Make `/.well-known/jwks.json` reachable over HTTPS without authentication. | | `claim_invalid` | Sign-in proof | A required claim is missing or differs from the registration. | Match `iss`, `aud`, `sub`, `jti`, `iat`, `exp` and the maximum token age to the registered provider. | | `tenant_claim_mismatch` | Sign-in proof | The token names another workspace. | Set the tenant claim to the value registered for this workspace. | | `nonce_mismatch` | Sign-in proof | The token's nonce is not the one the chat asked with. | Use the nonce the chat passes you, unchanged, and mint a new token every call. | | `malformed_token` | Sign-in proof | The token is not a parseable JWT. | Return a compact JWS (three dot-separated parts) with an `iss` claim. | | `provider_not_registered` | Sign-in proof | No enabled sign-in provider has this issuer. | Register the provider under Console › Identity with the exact issuer. | | `provider_not_published` | Sign-in proof | The provider is saved but not in the published settings. | Publish (Console › Publish, or `publish_tenant_runtime`). | | `issuer_owned_elsewhere` | Sign-in proof | This issuer and tenant claim belong to another workspace. | Use an issuer and tenant claim unique to your workspace, or contact support. | | `verifier_misconfigured` | Sign-in proof | The provider has no usable signing algorithm. | Allow ES256 and/or RS256 on the provider. | | `launch_replayed` | Sign-in proof | This token was already used; tokens are single use. | Generate a unique `jti` per token and never cache or resend a token. | | `launch_failed` | Sign-in proof | The verified identity could not be turned into a session. | Retry; if it persists, check the identity link events in the Console. | | `origin_not_allowed` | Sign-in proof | This page's origin is not an allowed website of the workspace. | Add the page origin under Console › Integration › Allowed origins. | | `verifier_error` | Sign-in proof | Another verification failure. | Check the identity link events in the Console for the precise internal reason. | | `not_identified` | Account card | Sign in before this account tool can run. | Fix launch identity first (steps 1 and 2): the visitor is not identified. | | `third_party` | Account card | "Authorize Busymate AI account tools" is shown while the customer is signed in. | The connector is Per-customer OAuth and not treated as yours: set its customer identity to the Busymate AI token and accept the actor token on your MCP. | | `revoked` | Account card | The customer revoked access and is asked to authorize again. | Nothing to fix: consent is theirs to renew. | | `ask_users` | Account card | The workspace asks every customer before using their account. | Turn "Ask customers before using their account" off if you do not want the prompt. | | `grant_reauth` | Account card | A grant the customer gave has expired and must be renewed. | Nothing to fix: only the customer can renew it. | | `partner_rejected` | Account card | Your server answered 401 to the actor token: the customer sees "temporarily unavailable" (no card) and you get a Console alert; it is retried hourly. | Check the secret, the exact `aud` origin and the `kid`, and that the verifier is installed. | | `silent` | Console readiness | Recognised customers reach their account with no second prompt. | Nothing to fix. | | `asks_policy_on` | Console readiness | The workspace chose to ask customers before using their account. | Turn the "Ask customers" switch off for a prompt-free experience. | | `asks_third_party` | Console readiness | The connector is treated as somebody else's product, so the consent card is shown. | Make the connector yours: accept the actor token on your MCP and set its customer identity to the Busymate AI token. | | `asks_identity_not_conveyed` | Console readiness | Account tools are hidden: the connector identity mode is `none`. | Set the connector's customer identity to the Busymate AI token (or OAuth for a third party). | | `never_succeeded` | Console readiness | Customers arrive identified but no call was ever accepted. | Install the actor-token verifier on your MCP and check the secret and `aud`. | | `stale_evidence` | Console readiness | A call was accepted once, but not under the current published revision. | Run a signed-in test call after publishing. | | `unknown` | Console readiness | Nothing has exercised this path yet. | Run the Step 5 checks. | | `login_page_unreachable` | Login page check | We could not fetch your login page. | Make `login_url` a public https page that answers within 5 seconds. | | `login_page_shows_ui` | Login page check | With `bmai_prompt=none` and no session your login page returned a page. | It must never show UI: redirect to `return_to#error=login_required`. | | `login_page_wrong_redirect` | Login page check | With `bmai_prompt=none` and no session your login page redirected somewhere other than `return_to#error=login_required`. | Redirect to exactly the `return_to` you were given, with `#error=login_required`. | | `login_page_frame_blocked_xfo` | Login page check | Your login page sends `X-Frame-Options`, so the silent check cannot load it in the hidden frame. | Remove `X-Frame-Options` and use `Content-Security-Policy: frame-ancestors` with the chat origins. | | `login_page_frame_ancestors_missing` | Login page check | Your login page's `frame-ancestors` does not list every chat origin. | Add the chat origins shown by the probe to `frame-ancestors`. | | `login_page_cookie_not_samesite_none` | Login page check | Your login page sets a cookie without `SameSite=None; Secure`, so the browser will not send the session in the hidden frame. | Set the session cookie with `SameSite=None; Secure`. | | `delegation_unconfigured` | Connector call | Signed connector access is temporarily unavailable. | Configure the connector's customer identity mode and its actor verifier. | | `oauth_authorize` | Account card | "Authorize {partner}" is shown once per customer. | Nothing to fix for a real third party. If the connector is your own product, set Customer identity to Busymate AI and accept the actor token on your MCP. | | `probe_pending` | Console readiness | The connector is set to Busymate AI, but the live actor probe has not passed yet, so it still runs as OAuth (or public only). | Install the actor-token verifier on your MCP, then run the probe (Console or `probe_connector_customer_identity`). | | `probe_failed` | Console readiness | Your MCP did not accept Busymate AI's actor token in the live probe, so the connector runs as OAuth (or public only). | Check the verifier, the exact `aud` origin and the key, then run the probe again. | | `oauth_unavailable` | Console readiness | The connector is set to OAuth but has no per-customer OAuth sign-in, so only public tools run. | Set the connector's authentication to OAuth, or choose Busymate AI. | | `identity_not_conveyed` | Connector call | This tool needs to know who the customer is, but the connector receives no identity. | Set the connector's Customer identity to Busymate AI. | | `not_available_here` | Connector call | Account actions are not available in this chat: you can do this at Busymate AI. | Set the connector's Customer identity to Busymate AI or OAuth if customers should act on their account here. | | `credential_missing` | Connector call | A sign-in-required connector has no stored credential on this channel. | Channels such as WhatsApp or Telegram cannot carry your customer's identity yet. | ## For AI coding agents The same block as on [Sign-in and identity](https://busymate.ai/it/docs/identity#for-ai-coding-agents). Copy it into your agent with your values filled in. ```text GOAL: The Busymate AI chat must recognise the signed-in user and call our MCP/API as that user, with no extra login and no Authorize card. CONTRACT: bmai-identity 2.0.0 (@busymate/identity-contract 2.0.0-beta.4). Manifest: https://busymate.ai/.well-known/bmai-integration.json 1. BACKEND (language of this repo) - ES256 key pair. Serve the public key as a JWKS at /.well-known/jwks.json (kid, alg ES256, use sig). - POST , body {nonce, returnTo?}, needs OUR existing session. Signed out: 401. Else 201 {token, nonce, expiresIn:120}, header Cache-Control: no-store. - JWT claims: iss=, aud=, sub=, =, nonce=, jti=, iat=now, exp=now+120. - Never an e-mail or phone in sub. Never a shared organization id. Never cache or store a token. Never make your own nonce. 2. FRONTEND (pick what this repo is) - Plain site: define window.BusymateAI.getIdentity({nonce}) BEFORE loading /embed/v1.js (data-assistant=). Call window.BusymateAI.identityChanged() after login, rotation and account switch, and window.BusymateAI.signedOut("signed_out") after logout. - In-chat login card: BusymateAI.registerPageTools with a sign_in tool (readOnlyHint, returns a $bmForm card: email + password, submit.tool sign_in_submit) and a sign_in_submit tool that POSTs to OUR OWN login endpoint and returns {signedIn:true} (or {signedIn:false,error}). The password never goes to the assistant; a password field only on a card that submits into our page. For an emailed or texted code use two cards (contact, then autocomplete one-time-code, never type password). The widget then calls getIdentity again and the same conversation continues signed in. - Bundler: npm install @busymate/ai-web. createBusymateChat({assistant, origin, getIdentity: mintFromEndpoint()}); chat.mount(); identityChanged() / switchAccount() / logout() on our auth events. - iOS, Android, desktop: BusymateBridge.install(... account, mint ...) BEFORE the first load; accountChanged() on every auth change. Desktop uses channel=desktop_sdk, never channel=desktop. React Native and Flutter use the frozen bridge files. - Hosted chat page or custom domain: our login page reads return_to, bmai_nonce and bmai_prompt, mints with that nonce and redirects to return_to#bmai_token=&bmai_nonce=. On bmai_prompt=none it never renders UI: no session means redirect to return_to#error=login_required. Validate return_to against the registered chat origins (open-redirect guard: otherwise 400). Send Content-Security-Policy frame-ancestors and a SameSite=None; Secure session cookie. 3. MCP SERVER / API - npm install @busymate/ai-verify (Python, PHP, Go files: github.com/serebano/busymate-ai-sdk-verify). - Before our own auth middleware call verifyBusymateActor(req, {audience: , tenantId, connectorId: BMAI_CONNECTOR_ID, idp: BMAI_IDP, tool: }). - Token v3: ES256, typ bmai-actor+jwt, header X-Bmai-Actor (or Authorization: Bearer when GET /api/bmai/status returns {"actorVerifier":true,"actor":3}); keys at https://busymate.ai/.well-known/bmai-actor-jwks.json; iss https://busymate.ai; exp-iat <= 120; clock skew <= 10 s. - Map the user by (idp, sub) ONLY. Never from a tool argument. Require confirmed === true (requireConfirmed) for money and write tools. Reject a repeated jti. - On a BusymateActorError answer 401 with WWW-Authenticate: Bearer error="invalid_token"; log e.reason; never fall through to another user. - Serve GET /api/bmai/status -> {"actorVerifier": true, "actor": 3}. - Compat v2 (HS256, kid bmai-support-v2, header Authorization: Bearer, exp-iat <= 300, secret set in the Console): check actor_kind === "identified"; anonymous means public data only. 4. DO NOT: make your own nonce; cache or store tokens; use channel=desktop; use OAuth mode for our own MCP; put an account id in tool arguments; ask the human for private keys. 5. TELL THE HUMAN to: register the provider and publish (Console > Identity), set the connector's customer identity to the Busymate AI token (token v3 is per workspace: ask for it), keep "Ask customers before using their account" OFF unless they want the confirm, then run: npx @busymate/identity-check --issuer --mint --login-url --chat-origin --mcp --tenant-id . ``` ## Changelog ### 2.0.0-beta.4 · 2026-10-30 - Setup A now has the in-chat sign-in card code (`sign_in` and `sign_in_submit`), and Setup B a login page handler with the `return_to` origin check and the silent `bmai_prompt=none` answer. Both are in the coding-agent block. ### 2.0.0-beta.3 · 2026-10-30 - `@busymate/ai-web` 0.2.0: the canonical lifecycle names (`identityChanged()`, `signedOut()`) and page tools, so the in-chat `sign_in` card works without the script tag. - The React Native, Flutter and desktop guides point to the frozen v2 bridge files and `channel=desktop_sdk`. - Per-connector customer identity (`busymate`, `oauth`, `none`), the live actor probe, the Ask-customers confirm, the registrable-domain default and live provider config are live. Telegram channel sign-in is live; WhatsApp and Messenger are still planned. - The worked example is neutral (Acme). The release registry can be synced from npm with `scripts/identity-contract/sync-versions.mjs`; a nightly check fails when it is behind. ### 2.0.0-beta.2 · 2026-10-29 - One public identity page, generated from this contract. - Actor token v3 (ES256, public JWKS) is live behind a per-workspace flag, dual-signed beside v2. Guests get no token on a flagged workspace. - `@busymate/ai-verify` and `@busymate/identity-check` are on npm. `@busymate/ai-web` is on npm. ### 2.0.0-beta.1 · 2026-10-29 - The identity contract package, the machine manifest `/.well-known/bmai-integration.json` and signed test vectors. - Launch verifier constants (algorithms, token age, required claims) now come from the contract. No behaviour change.