OAuth 2.0
OAuth 2.0 is an alternative to wpk_live_… API keys
for authenticating to the Management API. Instead of a long-lived key, an OAuth
client exchanges its credentials for a short-lived access token (oat_…,
valid about one hour) that carries a specific set of scopes.
Which one should you use?
- API key (
wpk_live_…) — simplest for a single first-party integration you control. - OAuth client credentials — server-to-server access with short-lived, independently revocable tokens; register several clients with different scopes.
- OAuth authorization code — for interactive apps (such as MCP clients) that act on a user's behalf after a browser consent.
Discovery
The authorization server publishes its metadata (RFC 8414) at:
GET https://www.wizchat.com/.well-known/oauth-authorization-server
It advertises the token, registration, and introspection endpoints, the supported
scopes, and that PKCE uses S256. A conformant OAuth client can bootstrap from
this document.
Server-to-server: client credentials
The client-credentials grant (RFC 6749 §4.4) is the path for backend integrations.
You register an OAuth client once, then exchange its client_id + client_secret
for access tokens.
1. Register a client
As the account owner, register an OAuth client. Registration is owner-authenticated — it accepts a Firebase ID token (see Owner tokens):
POST /api/v1/oauth/clients
{
"name": "My integration",
"scopes": ["chatbots:read", "documents:write"],
"allowedChatbotIds": null
}
scopes— the scopes this client may request.allowedChatbotIds— restrict the client to specific chatbots, ornullfor every chatbot you own.
The response returns the credentials once — the clientSecret is never shown again:
{
"clientId": "occ_xxxxxxxxxxxxxxxx",
"clientSecret": "ocs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"name": "My integration",
"scopes": ["chatbots:read", "documents:write"],
"allowedChatbotIds": null,
"status": "active",
"createdAt": "2026-07-08T00:00:00.000Z",
"lastUsedAt": null
}
The clientSecret (ocs_…) is shown exactly once, on creation. Store it somewhere
safe — if you lose it, revoke the client and register a new one.
2. Exchange for an access token
The token endpoint is public — no bearer is required, because the client
credentials authenticate the request. It accepts JSON or
application/x-www-form-urlencoded:
curl -X POST https://www.wizchat.com/api/v1/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "occ_xxxxxxxxxxxxxxxx",
"client_secret": "ocs_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}'
{
"access_token": "oat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "chatbots:read documents:write"
}
- Optionally pass
"scope": "chatbots:read"(space-separated) to request a subset of the client's registered scopes. - Invalid credentials return
401; a malformed request or unsupported grant type returns400; exceeding the rate limit returns429with aRetry-Afterheader.
3. Call the API
Use the access token as a Bearer credential — exactly where you would put a
wpk_live_… key:
curl https://www.wizchat.com/api/v1/chatbots \
-H "Authorization: Bearer oat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
The token is valid for expires_in seconds (currently 3600 — one hour). When it
expires, request a new one: the client-credentials grant does not issue a refresh
token, so just call the token endpoint again.
A token that authenticates but lacks the scope an operation requires returns
403 Forbidden, the same as an under-scoped API key. Each operation's required
scope is listed on its API-reference page.
Managing clients
- List your clients:
GET /api/v1/oauth/clients - Fetch one:
GET /api/v1/oauth/clients/{clientId} - Revoke one:
DELETE /api/v1/oauth/clients/{clientId}— its tokens stop working immediately.
These endpoints are owner-authenticated and appear in the API reference under the OAuth tag.
Interactive apps: authorization code + PKCE
Interactive clients that act on a user's behalf — such as MCP clients like Claude
connecting through claude mcp add — use the authorization-code grant with PKCE
instead of a static secret. At a high level:
- Register a public client dynamically (RFC 7591):
POST /api/v1/oauth/registerwith yourredirect_uris. You receive a publicclient_id(dyn_…) and no secret. - Send the user to consent at the authorization endpoint
(
https://www.wizchat.com/oauth/authorize) with a PKCEcode_challenge(S256) and a scope tier —view(read-only) ormanage(read + write, includingdeploy). The user signs in and approves. - Exchange the returned code at
POST /api/v1/oauth/token(grant_type=authorization_code) with yourcode_verifier. You receive an access token (oat_…) plus a rotating refresh token (ort_…, valid about 30 days). - Refresh with
grant_type=refresh_token. Refresh tokens rotate on every use; reusing a rotated token revokes the entire family.
Authorization-code tokens are bound to the WizChat management MCP endpoint (RFC 8707 resource binding) and this flow is used primarily for MCP-client onboarding. For your own server-to-server integrations, prefer the client-credentials flow above.
You can list and revoke the apps you have authorized this way with the
owner-authenticated OAuth grants endpoints (GET / DELETE /api/v1/oauth/grants).
Credential reference
| Prefix | What it is | Lifetime |
|---|---|---|
occ_ | OAuth client id (client credentials) | until revoked |
ocs_ | OAuth client secret — shown once | until revoked |
oat_ | Access token (Bearer) | about 1 hour |
ort_ | Refresh token (authorization code only) | about 30 days, rotating |
dyn_ | Public client id (dynamic registration) | until revoked |
Treat ocs_… secrets and oat_… / ort_… tokens like passwords — never commit them
or expose them in client-side code. Secrets and tokens are stored only as hashes, so a
lost secret cannot be recovered; rotate by revoking and re-creating.