# Guest Badges

A **Guest Badge** lets a workspace member host an email that is **not** a
member of the Roam. The guest can visit that host; they do not become a
provisioned user (that is [SCIM](/docs/scim/roam-hq-scim-2-0-api)) and they are not an
[On-Air event guest](/docs/onair-api/on-air-api).

| Action | Endpoint |
| --- | --- |
| Grant a badge | [`POST /guest.badge.create`](/docs/api/guest-badge-create) |
| Revoke a badge | [`POST /guest.badge.revoke`](/docs/api/guest-badge-revoke) |

[`group.add`](/docs/api/group-add) can attach the same email to a channel. It
does **not** mint a Guest Badge or send the invite. Typical CSM onboarding:

1. `guest.badge.create` with the client's email and a host
2. `group.add` if they should also land in a channel
3. `guest.badge.revoke` to offboard

## Authorization

| Credential | Who you can host as | Scope |
| --- | --- | --- |
| Organization API key / org OAuth | Any member, via `hostUserId` (UUID or member email) | `guest:write` |
| Personal Access Token | The token owner only | `pat:guests:write` (expands to `guest:write`) |
| Personal OAuth | The token owner only | `guest:write` |

Naming another host on a personal token returns `403` `access_mode_not_supported`.
A host who is not allowed to grant badges (account grant level /
`canGrantAccessBadge`) returns `403` `guest_badge_forbidden`. Unknown hosts
return `404` `user_not_found`.

`guest:read` is reserved for a future list/info surface and is not grantable
in the picker today.

## Create

```bash
curl -X POST https://api.ro.am/v1/guest.badge.create \
  -H "Authorization: Bearer $ROAM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "alex@client.example",
    "hostUserId": "3f1c0b2a-8d4e-4c91-9a7b-2e6f1d8c0a11"
  }'
```

`hostUserId` may be a member UUID or email (same as `group.create`
`members[].userId`). Personal tokens may omit it. `visitPermission` defaults
to `true`. Repeating the same host and email returns the existing row without
changing `visitPermission` or re-sending mail.

The response `hostUserId` is always a UUID. `userId` is the guest's chat
address when one has been provisioned.

## Revoke

```bash
curl -X POST https://api.ro.am/v1/guest.badge.revoke \
  -H "Authorization: Bearer $ROAM_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "alex@client.example"
  }'
```

`{ "revoked": true }` means a matching grant was deleted. `{ "revoked": false }`
means there was nothing to revoke. If several hosts granted the same email,
pass `hostUserId` to pick one.

Archiving a host already deletes the badges they granted. You can still name
that host on revoke (it will not 404); the result is typically `revoked: false`.