Guests and bots in identity
Applies to: identity after 0.35 (creating worker bots — after 0.40, messaging after 0.155), the coordinator and messaging in platform_token: identity mode · Checked: 01.10.2026
Everything here works once the platform is switched to identity's token
(platform_token: identity, ADR-0047 §8a). In hmac mode — the default — guests and bots
live on the coordinator as described in Guests, contractors and family members,
and nothing on this page changes.
In identity mode a guest and a bot are identity accounts like employees, and each has a person who answers for it: a guest its sponsor, a bot or an assistant its owner. They sign in through the same issuer, and their token is revoked by the same revocation journal.
Guests
The invitation
The apps still invite through the coordinator — POST /v1/guests, with the guests:invite
permission checked by the coordinator. In identity mode it does not create the guest itself: it
passes the invitation on to identity, naming the caller as the sponsor. identity's answer
(the refusal's code and text) reaches the app unchanged.
identity keeps the invitation's rules now — identity console → Guests:
| Setting | Default | Meaning |
|---|---|---|
| Longest invitation | 365 days | a longer term is cut to it |
| Invitation when none is given | 30 days | there is no invitation without an expiry |
| Active guests per sponsor | 0 — no limit | at the limit a new invitation gets 409 |
The coordinator's guest-max-days setting does not apply in identity mode: these settings
took its place.
The invitation carries the guest's features (see Access to SimpleTwo): chat and calls only by default. Such a guest is inside the access model like an employee; guests invited earlier on the coordinator stay outside it and reach every service as before.
Check: identity console → Guests shows the guest's sponsor, expiry and the sign-in state "card pending".
The card and the first sign-in
The guest gets a single-use card code, not a password: 72 hours, never longer than the
invitation. The app redeems it through the coordinator's POST /v1/auth/enroll/confirm, which
passes it on to identity; the guest chooses the password under identity's password policy. A
new card is issued only while the guest has never signed in.
The recovery code
As before, the sponsor gets the guest back in: in the app, My guests → Help sign in issues
a six-digit code that the sponsor reads out. identity issues it and keeps only its keyed hash.
The rules are the same: 10 minutes, once, five wrong tries per record — issuing again adds no
tries, and once they are spent a new code is issued only after the old one expires. The guest
redeems it with their phone through POST /v1/auth/recover/confirm; the new password ends every
earlier session.
The guest's "I cannot sign in" (POST /v1/auth/recover) stays on the coordinator: it marks the
request on the sponsor's list and writes to them from the support bot, but mints no code of its
own any more.
An administrator issues a code for any guest in the coordinator's console (as before) or in the identity console → Guests → Recovery code.
Extend, revoke, review
- Extend —
+30 daysfrom the current expiry (or from today when it has passed), never past the longest invitation. - Revoke — reversible: the account is disabled and all its sessions in identity end.
- Access review — the "Review guests older than" policy in the identity console → Access reviews: the sponsor answers keep or revoke, and silence revokes.
A guest who lapsed, was revoked or failed a review goes onto the revocation journal as an
account event: every service refuses its token at once, without waiting for it to expire.
The guest's token
A guest signs in on identity's page like everyone and gets a platform token marked
knd: guest. The guest's fences are the ones of today: the directory per the role's policy,
only the conversations they were invited to, no mailbox. A guest session the coordinator
minted is not accepted in identity mode — the guest signs in again.
Bots and assistants
The owner
Every bot and assistant has an owner — the employee who answers for it. identity console → Bots lists every bot, its owner and its credentials.
- The owner left (disabled, deleted, expired) — their bots and assistants are disabled with
them, each with an
accountevent on the revocation journal. The organisation's system bots (support, announcements) are not disabled: the list marks their owner as inactive. - Hand over — give the bot a new owner (an active employee) and enable it at once; Disable — leave it off.
- Owner review — the "Review bots with their owner every, days" policy in the identity console → Access reviews; silence disables the bot.
A new worker bot
In identity mode worker bots are created in the identity console → Bots → New worker
bot. The coordinator's console no longer creates them or rotates their token: in place of
New bot it links to identity's console. In hmac mode it is the other way round — bots are
created on the coordinator as before, and identity has no such button.
The form's fields:
| Field | Meaning |
|---|---|
| Username | 2–64 characters: a lowercase letter, then letters, digits, ., _, -; a taken name (a deleted account's included) is refused |
| Display name | the name in the directory and in conversations |
| Owner | the username of an active employee, who answers for the bot |
| Scopes | at least one of read, send, react, manage-conv; the empty set (the old "full access") is not granted by the console |
| Conversations | conversation ids, comma-separated; empty — every conversation the bot is added to |
| Webhook and its secret | optional: where messaging posts events and what signs each delivery |
| Credential | a key pair made by identity (the private half shown once), the bot's own public key, or a client secret (shown once) |
What creating does:
- identity asks the coordinator to create the bot's record in messaging — scopes,
conversations, webhook,
credential: platform(nobot_…token). identity has no way to messaging's control API: it listens on loopback on the messaging host, and its one client is the coordinator through the host's agent. So identity calls the coordinator's door/v1/service/botsassvc:identity. - identity creates the bot's account (kind service, the owner from the form) and issues the credential. If the account cannot be made, the record in messaging is removed again.
- The coordinator takes the account from identity's feed within a minute; the bot appears in the directory and on its Bots card.
Check: in the identity console → Bots the bot shows its owner, its key or secret and the May do column — the scopes and conversations of messaging's record. A "?" in that column means messaging's records could not be read through the coordinator; the reason is written above the table.
Edit, disable, delete
The row's buttons keep messaging's record in step with identity:
- Edit — scopes, conversations (an empty field lifts the fence), webhook, name. The credential is not touched; the bot's open connections are dropped so the new grants apply at once.
- Disable — identity's account first (the bot cannot sign in from that moment), then messaging's record. If messaging is unreachable the console says so; pressing again repeats only the second half.
- Enable, and Hand over with enabling — messaging's record first; if it was not resumed, the bot stays disabled.
- Delete — messaging's record first, then the account, its credentials and every token. If messaging is unreachable the account is kept. The username stays taken after deletion. The system bots (support, welcome) are removed with what made them, on the coordinator.
When the owner leaves, their bots are disabled on identity and their records in messaging follow.
A worker bot: client_credentials
A worker bot (support, announcements, an integration) gets its platform token from identity:
client_id—bot:followed by the bot's username, e.g.bot:announcer;- a key (preferred,
private_key_jwt) — New key in the console makes a pair and shows the private half once; a bot can also bring its own public half (POST /control/bots/{username}/keyswithpublic). Rotate key replaces all earlier ones; - a secret — New secret, shown once, replaces the previous one.
A request with a key carries a signed assertion (the suite's algorithm, EdDSA by default; iss
and sub are the client_id, aud the /oauth/token address, exp at most five minutes
ahead, a single-use jti):
curl -fsS -X POST "https://id.<domain>/oauth/token" \
-d grant_type=client_credentials \
-d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
-d client_assertion="$ASSERTION"
With a secret:
curl -fsS -X POST "https://id.<domain>/oauth/token" -u "bot:announcer:$SECRET" \
-d grant_type=client_credentials
The answer is a platform token for an hour: sub is the bot, knd: bot, every token is its own
session, and there is no refresh token — the bot asks again.
An assistant: exchanging the owner's token
An assistant's brain runs on its owner's device, so the assistant has no credentials of its own. The owner's app exchanges the owner's token (RFC 8693):
curl -fsS -X POST "https://id.<domain>/oauth/token" \
-d grant_type=urn:ietf:params:oauth:grant-type:token-exchange \
-d client_id=simpletwo \
-d subject_token="$OWNER_TOKEN" \
-d subject_token_type=urn:ietf:params:oauth:token-type:access_token \
-d requested_subject=ivan.assistant
The assistant must belong to that person. The answer: sub is the assistant, knd: bot,
act: {sub: the owner}, the owner's session (signing out ends this token too), services and
lifetime never wider than the owner's token. A token with act cannot be exchanged again: an
assistant does not act for an assistant.
An assistant's rights are an intersection: a service checks the permission for both the assistant and the owner in the coordinator's RBAC. The owner signing out everywhere, or being disabled or deleted, revokes the assistant's tokens too. What the assistant does — draft or full, in which conversations, the model quota — is still messaging's.
"My agents" (POST /v1/me/agents) in identity mode creates the assistant's account on identity
(owned by whoever pressed the button) and its behaviour in messaging with no token of its own;
the answer carries token_exchange — the address and parameters of the exchange — instead of
token.
Moving existing bots
In identity mode messaging does not accept the old bot_… tokens — only the platform token.
So before the switch:
- Check that every bot has an account on identity: identity console → Bots (the coordinator writes bots there since identity exists). Give a bot without an owner one with Hand over.
- For each worker bot issue a key (or a secret) and put it into the bot's environment
together with its
client_idand the/oauth/tokenaddress. - Move the bot to getting its token from identity; until the switch services do not accept that token, so it is enough to check that identity issues it.
- Assistants need nothing: the owner's updated app gets their token by the exchange.
- Switch the platform to identity. The bots' records in messaging — scopes, permissions,
conversations — stay as they are; the old
bot_…token can be removed by creating the bot again withcredential: platform(the way new assistants are created).
Common refusals
| Symptom | Likely cause |
|---|---|
404 on /control/guests… at identity | identity's issuer is off: guests are still the coordinator's |
409 on an invitation: a sponsor has as many guests as allowed | the guest policy's limit is reached |
400 a guest is invited by an active person | the sponsor is not an active employee (a guest, a bot, disabled) |
429 issuing a recovery code | the five tries are spent; wait ten minutes from the previous code |
401 invalid_client for a bot | a wrong key, secret, or a client_id without bot:; the assertion was already used |
400 invalid_grant for a bot | the bot's account is disabled (for instance, its owner left) |
400 invalid_grant: no assistant of yours by that name | the assistant belongs to somebody else |
messaging answers invalid_token to bot_… | in identity mode the old bot tokens are not accepted |
409 hmac_mode on "New worker bot" | the coordinator's platform token is still hmac: the bot is created in the coordinator's console |
409 managed_on_identity on New bot in the coordinator's console | identity mode: the bot is created in identity's console |
409 name_taken | the name is held by an account on the coordinator or by an orphan record in messaging (the coordinator's Bots card lists it) |
503 no_relay | identity has no DIR_COORDINATOR_URL, so no way to messaging's records |
502 messaging_unreachable | the coordinator could not reach messaging through the host's agent |