Token User
A client integrates Bookable on behalf of end users without maintaining a Bookable specific login. The flow is:
- Register the resources the users will see.
- Decide which claim the users will hold, and bind it to those resources.
- Forward each user's identity into Bookable to upsert a samna user.
- Pass the same identity on every subsequent request through
SAuth-User-Token. The platform resolves it back to the upserted user and enforces that user's access.
1. Register Resources
Start with Register Resources. At the end of that step the client has at least one organization, one location and one bookable to expose. The client itself is authenticated through the OAuth 2.1 client credentials grant (HTTP Basic on Authorization).
2. Prepare a Claim
A user is given access by holding a claim that is bound to the resources you want them to see. You either reuse an existing claim or create one.
POST /claim
{
"name": "Bookable Member",
"type": "user",
"access": 6,
"owner_id": "2cc61227-7c11-465f-beb4-76e58f311f1e"
}access: 6 grants write | read. Pick the bits that match what users should be able to do.
Bind the claim to each resource the users should reach. Bindings cascade through ownership when inherits: true is set on both the claim and the binding, so binding once at the organization is usually enough.
POST /claim/{claim_id}/object
{
"object_type": "organization",
"object_id": "2cc61227-7c11-465f-beb4-76e58f311f1e",
"reason": "Members can read and write resources in this organization."
}Verify the binding by reading the claim back through GET /claim/{id} and checking that the new binding appears.
3. Forward the User's Token
When a user lands on the client, you already have an authenticated identity in your own system, typically a JWT carrying an external user id. Forward it to Bookable through POST /user/token. The platform upserts a samna user keyed to the external id and links it to the chosen claim.
POST /user/token
{
"token": "<the JWT issued by your own identity layer>",
"id_claim": "sub",
"claim_id": "<the claim id from step 2>",
"user": {
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada.lovelace@example.com"
}
}tokenis the access token your system already holds for the user.id_claimis the JWT claim name that carries the user's external id.subis the typical choice.claim_idis the claim from step 2. The first time a user shows up, the platform attaches this claim to them so they immediately have access to the bound resources.useris the profile snapshot used on the upserted samna user row. On the first call it populates the row; on subsequent calls it refreshes any fields that have changed.
The response carries the resolved samna user, including the external_id it was keyed on:
{
"data": {
"id": "9a225666-8871-4b75-82be-de295d0c1b63",
"user_id": "00fc9820-cc51-4394-9793-d10fff7b5707",
"first_name": "Ada",
"last_name": "Lovelace",
"email": "ada.lovelace@example.com",
"external_id": "external-debug-12345",
"status": "active",
"type": "user",
"created_at": "2026-05-11T18:04:30.968883Z",
"updated_at": "2026-05-11T18:04:30.968883Z"
},
"request_id": "8ecee030-d551-4527-84e2-689a6c705437",
"count": 1
}Store the user_id if you want to reference the user in claim management calls. You do not need it for normal user requests; the next step uses the token directly.
4. Use SAuth-User-Token on Subsequent Requests
For every call the client makes on behalf of the user, send the same token in the SAuth-User-Token header alongside the standard client Authorization header.
Authorization: Basic <base64(client_id:client_secret)>
SAuth-User-Token: <the JWT issued by your own identity layer>
GET /bookableThe middleware validates the token, resolves it back to the upserted samna user, and runs the rest of the call as that user. Every access check, every list, every write is scoped to what the user's claims permit.
When the user's identity changes (rotated token, refreshed JWT, new email), call POST /user/token again with the fresh token and the refreshed user profile. The upsert keys on the external id, so the same samna user row stays in place.
Notes
- The token is never stored. It is used to extract the external identity (via
id_claim), upsert the samna user keyed on that identity, and then discarded. Subsequent requests pass the same token again throughSAuth-User-Token; the platform resolves it fresh each time. - A user can hold many claims. Use additional
POST /claim/{id}/usergrants or further calls toPOST /user/tokenwith differentclaim_ids to broaden a user's access. - A token that does not resolve to a known external id returns
404 User token not recognized. CallPOST /user/tokento upsert before retrying. - Token validation respects the access of the calling application. The application must hold access to the owning organization of the user being resolved.