ドキュメント
本人確認コントラクトのリファレンス
このページの内容
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
| Part | Version | Status |
|---|---|---|
Web SDK @busymate/ai-web | 0.2.0 | Live |
Website script /embed/v1.js | v1 | Live |
| iOS SDK | 1.0.2 | Live |
| Android SDK | 1.0.2 | Live |
| macOS SDK | 1.0.2 | Live |
| Windows SDK | 1.0.2 | Live |
| Linux SDK | 1.0.2 | Live |
| Electron SDK | 1.0.2 | Live |
| Tauri SDK | 1.0.2 | Live |
Bridge files busymate.bridge/2 (React Native, Flutter) | 2.0.0 | Live |
| v1 kit drop-ins | 1 | Legacy |
Verifier @busymate/ai-verify (Node, Deno; Python, PHP, Go files in the repository) | 0.1.2 | Live |
Check CLI @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. |
<workspace claim> | 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=<token>&bmai_nonce=<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 <chat origins>and do not sendX-Frame-Options: DENY. - Send the session cookie with
Secure; SameSite=None, or the frame cannot see the signed-in session. - On
bmai_prompt=nonenever render UI: redirect with the token, or with#error=login_required. - Redirect to exactly
return_toplus 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: <token>. 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 <token> |
| 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:<session>: 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 asaccountId. - Require
confirmed === truefor anything that moves money or writes data. - Optionally reject a repeated
jtiuntil it expires. - Optionally serve
GET /api/bmai/statusreturning{"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.
| 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 <issuer>/.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. Copy it into your agent with your values filled in.
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_inandsign_in_submit), and Setup B a login page handler with thereturn_toorigin check and the silentbmai_prompt=noneanswer. Both are in the coding-agent block.
2.0.0-beta.3 · 2026-10-30
@busymate/ai-web0.2.0: the canonical lifecycle names (identityChanged(),signedOut()) and page tools, so the in-chatsign_incard 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-verifyand@busymate/identity-checkare on npm.@busymate/ai-webis on npm.
2.0.0-beta.1 · 2026-10-29
- The identity contract package, the machine manifest
/.well-known/bmai-integration.jsonand signed test vectors. - Launch verifier constants (algorithms, token age, required claims) now come from the contract. No behaviour change.