Tenant-scoped API requests need a bearer token and the tenant identified by the endpoint. Most modules take X-Tenant-Id; some Account management routes take the tenant in the URL instead. Public endpoints have their own rules.
Personal Access Tokens
- Open Settings → Developers. This tab requires
account:api:access. - Under Create API Token, enter a name, choose a 7-, 30- or 90-day expiry, and select the permissions needed by the integration.
- Click Create Token and copy the token immediately: it is shown only once.
- Send it as
Authorization: Bearer <token>and send the current workspace’s Tenant ID from Settings → User.
You can select permissions you hold in that workspace. Keep the token private. To replace it, create a new token, update your integration, then Revoke the old token from Active Tokens. Revoked or expired credentials must be replaced.
OAuth application registration and consent
ThirdSectorBee supports authorisation-code exchange. There is no documented client_credentials grant: registering an application does not by itself authorise it to access user data.
For a confidential application:
- Register it using
POST /v1/account/tenants/{tenantId}/oauth-clients, with an authenticated user who hasaccount:api:access. Supplyname, exactredirectUrisandrequiredPermissionsas described in the Account API. - Store the returned
clientIdandclientSecretsecurely. The secret is returned only at creation. -
Send the user to the application’s consent page:
https://app.thirdsectorbee.com/oauth/authorize?client_id=<clientId>&redirect_uri=<encoded-registered-uri>&state=<random-state> - The user signs in and approves access. At your registered callback, verify that
statematches the value you sent and retrieve the single-usecode. -
Exchange the code from your server. This flow uses a JSON body:
curl https://api.thirdsectorbee.com/v1/account/oauth/token \ -H 'Content-Type: application/json' \ --data @oauth-code-exchange.jsonCreate
oauth-code-exchange.jsonsecurely with these fields, replacing the example values:{ "client_id": "your-client-id", "client_secret": "your-client-secret", "code": "code-from-callback", "redirect_uri": "https://your-service.example/callback" }
The confidential-client flow returns an access token with a 90-day lifetime. It does not issue the connector flow’s rotating refresh token. Arrange renewed authorisation before expiry; use the returned expiry information rather than assuming tokens last indefinitely.
Connector applications: PKCE and refresh tokens
Explicitly registered connector clients (connectorPlatform: claude or chatgpt) use a separate public-client contract. Registration includes the configured API resource. Their consent request must include response_type=code, that exact resource, requested scope, client_id, the registered redirect_uri, state, and a PKCE code_challenge with code_challenge_method=S256. Connector consent also requires the workspace’s LLM integration feature to be enabled; otherwise the consent page rejects the request.
Exchange their code with application/x-www-form-urlencoded using grant_type=authorization_code, client_id, resource, code, code_verifier and the exact redirect_uri. A refresh uses grant_type=refresh_token, client_id, resource and refresh_token. Do not send the confidential-client JSON example for these clients.
Connector access tokens last at most one hour; refresh tokens rotate and share a 30-day absolute grant lifetime. Replace the stored refresh token after each successful refresh. Reusing an old refresh token revokes the grant. See the Account API for schemas and supported scopes.
Make an authenticated request
For example, listing programmes requires the Programmes module and programmes:read:programmes:
curl https://api.thirdsectorbee.com/v1/programmes/programmes \
-H "Authorization: Bearer $TSB_TOKEN" \
-H "X-Tenant-Id: $TSB_TENANT_ID"
A workspace is a tenant; an organisation may contain several. Use the intended workspace’s ID. Access is checked against current membership and permissions, and connector credentials are additionally restricted by the consented grant.
Diagnose authentication failures
| Response | Next step |
|---|---|
401 |
Check that the bearer header is present and the credential is valid |
403 with {"message":"Forbidden"} from the Account authorizer |
The presented token may be expired or invalid; obtain a valid credential |
403 with nested error.code: FORBIDDEN |
Check workspace membership, required permissions and the token’s permitted scope |
400 at token exchange |
Check the request format, registered callback, code/PKCE verifier and resource; codes cannot be reused |
Do not assume every 403 means a missing permission. Error formats vary by endpoint; consult the module reference.
Public endpoints
Engage’s public form endpoints and Account’s token exchange do not require a user bearer header. Their form/embed credentials or OAuth exchange parameters still apply. Follow each operation’s reference rather than removing authentication from a tenant-scoped API.