Aller au contenu
Busymate AI

Ce guide est actuellement en anglais.

Documentation

Référence du contrat d’identité

Sur cette page

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.

Version: 2.0.0-beta.4 (current) · earlier: 2.0.0-beta.3 · 2.0.0-beta.2 · 2.0.0-beta.1

Machine-readable: busymate.ai/.well-known/bmai-integration.json. Anything marked [PLANNED] is not live yet.

Releases

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.

PropertyValue
AlgorithmsES256, RS256. Anything else is refused.
Lifetime (exp - iat)30 to 900 s per provider. Use 120 s.
Clock skew0 to 120 s per provider, default 30 s
SizeAt most 16,384 characters
Single useA reused jti or nonce gets 409
Session it opens5 to 1440 minutes per provider, default 120
ClaimRequiredRule
issYesYour issuer. Must equal the registered issuer.
audYesbusymate-ai. Must equal a registered audience.
subYesYour 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.
<workspace claim>YesYour tenant value (default claim name tenant_id). Must equal the registered tenant claim value.
nonceYesThe nonce from the request, unchanged. Busymate AI generates it; you never make your own.
jtiYesRandom, unique per token. Single use: a replay gets 409.
iatYesNow.
expYesNow + 120 s. Keep it at your provider's maximum token age or less.
name, email, ...NoOptional 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 sendMeaning
return_toWhere to send the user back. Redirect to exactly this URL.
bmai_nonceThe nonce to sign into the token, unchanged.
bmai_clientWhich client asks (web, or channel for channel sign-in).
bmai_promptnone: a silent check. Never show UI.
Your answerRedirect to
Signed inreturn_to#bmai_token=<token>&bmai_nonce=<nonce>
Silent check, no sessionreturn_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 <chat origins> 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

PropertyValue
AlgorithmES256, typ: bmai-actor+jwt
Keyshttps://busymate.ai/.well-known/bmai-actor-jwks.json. Rotated every 90 days with 14 days of overlap: never pin a key.
Issuerhttps://busymate.ai
LifetimeAt most 120 s. Clock skew 10 s.
HeaderX-Bmai-Actor: <token>. In Authorization: Bearer once GET /api/bmai/status returns {"actorVerifier":true,"actor":3}.
Who gets oneSigned-in customers only, never guests
RolloutPer workspace (actor_token_v3), signed beside version 2 for 90 days
ClaimRule
isshttps://busymate.ai.
audThe EXACT origin of the connector URL.
subThe verified launch subject.
idpThe identity provider id. Map users by (idp, sub), never by sub alone.
tenant_idYour Busymate AI tenant id.
connector_idYour connector id.
support_session_idThe chat session.
actor_kindAlways identified.
confirmedTrue only for a call the customer confirmed.
toolThe tool this token was minted for.
iatIssued at.
nbfNot before.
expiat + at most 120.
jtiUnique id; a verifier rejects a repeat.

Version 2 (compatibility)

PropertyValue
AlgorithmHS256, 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)
Issuerhttps://busymate.ai
LifetimeAt most 300 s. Clock skew 10 s.
HeaderAuthorization: Bearer <token>
Who gets oneidentified and anonymous visitors: check actor_kind
ClaimRule
isshttps://busymate.ai.
audThe EXACT origin (scheme and host) of the connector URL.
subThe verified user id. For actor_kind: "anonymous" it is anonymous:<session>: never map it to an account.
tenant_idYour Busymate AI tenant id.
connector_idYour connector id.
support_session_idThe chat session.
policy_revisionThe published revision that allowed the call.
actor_kindidentified or anonymous. Check it: anonymous means public data only.
confirmedTrue only for a call the customer confirmed. Require it for money and writes.
iatIssued at.
nbfNot before (iat - 5).
expiat + 300.
jtiUnique 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.

CodeWhereWhat it meansFix
clock_skewSign-in proofYour server clock is out of step with ours.Sync the server clock (NTP) and mint a fresh token.
token_expiredSign-in proofThe 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_invalidSign-in proofThe 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_kidSign-in proofThe 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_unavailableSign-in proofWe could not fetch your JWKS.Make <issuer>/.well-known/jwks.json reachable over HTTPS without authentication.
claim_invalidSign-in proofA 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_mismatchSign-in proofThe token names another workspace.Set the tenant claim to the value registered for this workspace.
nonce_mismatchSign-in proofThe 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_tokenSign-in proofThe token is not a parseable JWT.Return a compact JWS (three dot-separated parts) with an iss claim.
provider_not_registeredSign-in proofNo enabled sign-in provider has this issuer.Register the provider under Console › Identity with the exact issuer.
provider_not_publishedSign-in proofThe provider is saved but not in the published settings.Publish (Console › Publish, or publish_tenant_runtime).
issuer_owned_elsewhereSign-in proofThis issuer and tenant claim belong to another workspace.Use an issuer and tenant claim unique to your workspace, or contact support.
verifier_misconfiguredSign-in proofThe provider has no usable signing algorithm.Allow ES256 and/or RS256 on the provider.
launch_replayedSign-in proofThis token was already used; tokens are single use.Generate a unique jti per token and never cache or resend a token.
launch_failedSign-in proofThe verified identity could not be turned into a session.Retry; if it persists, check the identity link events in the Console.
origin_not_allowedSign-in proofThis page's origin is not an allowed website of the workspace.Add the page origin under Console › Integration › Allowed origins.
verifier_errorSign-in proofAnother verification failure.Check the identity link events in the Console for the precise internal reason.
not_identifiedAccount cardSign in before this account tool can run.Fix launch identity first (steps 1 and 2): the visitor is not identified.
third_partyAccount 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.
revokedAccount cardThe customer revoked access and is asked to authorize again.Nothing to fix: consent is theirs to renew.
ask_usersAccount cardThe workspace asks every customer before using their account.Turn "Ask customers before using their account" off if you do not want the prompt.
grant_reauthAccount cardA grant the customer gave has expired and must be renewed.Nothing to fix: only the customer can renew it.
partner_rejectedAccount cardYour 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.
silentConsole readinessRecognised customers reach their account with no second prompt.Nothing to fix.
asks_policy_onConsole readinessThe workspace chose to ask customers before using their account.Turn the "Ask customers" switch off for a prompt-free experience.
asks_third_partyConsole readinessThe 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_conveyedConsole readinessAccount 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_succeededConsole readinessCustomers arrive identified but no call was ever accepted.Install the actor-token verifier on your MCP and check the secret and aud.
stale_evidenceConsole readinessA call was accepted once, but not under the current published revision.Run a signed-in test call after publishing.
unknownConsole readinessNothing has exercised this path yet.Run the Step 5 checks.
login_page_unreachableLogin page checkWe could not fetch your login page.Make login_url a public https page that answers within 5 seconds.
login_page_shows_uiLogin page checkWith 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_redirectLogin page checkWith 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_xfoLogin page checkYour 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_missingLogin page checkYour 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_noneLogin page checkYour 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_unconfiguredConnector callSigned connector access is temporarily unavailable.Configure the connector's customer identity mode and its actor verifier.
oauth_authorizeAccount 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_pendingConsole readinessThe 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_failedConsole readinessYour 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_unavailableConsole readinessThe 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_conveyedConnector callThis 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_hereConnector callAccount 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_missingConnector callA 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. 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 <issuer>/.well-known/jwks.json (kid, alg ES256, use sig).
   - POST <mint path>, body {nonce, returnTo?}, needs OUR existing session. Signed out: 401. Else 201 {token, nonce, expiresIn:120}, header Cache-Control: no-store.
   - JWT claims: iss=<issuer>, aud=<audience>, sub=<immutable per-person user id>, <workspace claim>=<workspace id>, nonce=<request nonce unchanged>, jti=<uuid>, 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 <chat origin>/embed/v1.js (data-assistant=<slug>). 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(<mint url>)}); 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=<token>&bmai_nonce=<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 <chat origins> 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: <exact connector origin>, tenantId, connectorId: BMAI_CONNECTOR_ID, idp: BMAI_IDP, tool: <tools/call name>}).
   - 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 <issuer> --mint <mint url> --login-url <login url> --chat-origin <chat origin> --mcp <connector origin> --tenant-id <tenant>.

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.