App platform

OAuth for apps

Apps get access to a workspace through OAuth 2.0 authorization codes with PKCE (S256). The workspace's owner or admin approves the scopes; your server receives tokens for that one installation.

The flow

  1. Your server creates a random code_verifier (43 to 128 characters) and its code_challenge = base64url(SHA-256(verifier)), without padding.
  2. Send the user's browser to the authorize URL on the Kweko hub.
  3. The user signs in, picks a workspace where they are owner or admin and approves your scopes.
  4. Kweko redirects to your redirect_uri with code and your state.
  5. Your server exchanges the code (with the verifier and your client secret) for tokens.
  6. Call the API with the access token; refresh it before it expires.

1. Authorize

text
https://app.kweko.uz/oauth/authorize
  ?response_type=code
  &client_id=app_01j8za0b1c2d3e4f5g6h7j8k9m
  &redirect_uri=https%3A%2F%2Fexample.com%2Fkweko%2Fcallback
  &scope=leads%3Aread%20leads%3Awrite%20ui%3Aextend
  &state=af0ifjsldkj
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
ParameterDescription
client_idYour app's id (app_…)
redirect_uriMust exactly match one of the app's redirect URIs. May be left out when the app has exactly one.
response_typecode
scopeSpace-separated. Must be a subset of the scopes your app declares; empty means all of them.
stateRecommended: an unguessable value you check on the callback (up to 500 printable characters)
code_challenge, code_challenge_methodPKCE. Only S256 is accepted.

The consent request is valid for 15 minutes. If the user denies, your redirect URI gets error=access_denied; a malformed request gets error=invalid_request, unsupported_response_type or invalid_scope. An unknown client or redirect URI is never redirected: the user sees an error page in Kweko instead.

Redirect URIs must use https://, except http://localhost and http://127.0.0.1 for development. No wildcards or fragments.

2. Exchange the code

The code (kwk_ac_…) is valid for 10 minutes and works once. Using it twice revokes every token issued from it.

Request
curl -X POST https://api.kweko.uz/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=authorization_code \
  -d code=kwk_ac_… \
  -d redirect_uri=https://example.com/kweko/callback \
  -d code_verifier=$CODE_VERIFIER

The token endpoint is https://api.kweko.uz/oauth/token (outside /v1). It accepts form-encoded or JSON bodies. Authenticate with HTTP Basic, or send client_id and client_secret in the body. Apps marked as public clients (no secret, for example a desktop tool) send only client_id and rely on PKCE.

Response
{
  "access_token": "kwk_at_…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "kwk_rt_…",
  "refresh_token_expires_in": 5184000,
  "scope": "leads:read leads:write ui:extend",
  "workspace_id": "ws_01j8z5w2k4h6j8m0n2p4r6t8v0",
  "installation_id": "inst_01j8za1c2d3e4f5g6h7j8k9m0n"
}

Store the tokens per installation_id. Call the API with Authorization: Bearer kwk_at_…, exactly like an API key.

3. Refresh

Request
curl -X POST https://api.kweko.uz/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=refresh_token \
  -d refresh_token=kwk_rt_…

Access tokens live 1 hour, refresh tokens 60 days. Refresh tokens rotate: every refresh returns a new refresh token and the old one stops working. Store the new one before you use the new access token. Presenting an already used refresh token is treated as theft: Kweko uninstalls the app from that workspace and revokes all its tokens. If two of your workers may refresh at once, serialize refreshes per installation.

An optional scope on refresh can only narrow the granted scopes.

Revoke

POST https://api.kweko.uz/oauth/revoke with token=… (and client authentication) revokes an access or refresh token, following RFC 7009; revoking a refresh token revokes everything issued with it. It always answers 200 {}.

Errors

The token and revoke endpoints answer in the OAuth format, not the usual one:

json
{ "error": "invalid_grant", "error_description": "…" }

error is invalid_request, invalid_client (401, with WWW-Authenticate), invalid_grant, unsupported_grant_type (only authorization_code and refresh_token exist), invalid_scope or server_error. On the API, an expired or revoked access token answers invalid_token.

Scopes

Apps request the same scopes as API keys, plus these app-only scopes:

ui:extend, channels:provide, telephony:provide

ui:extend is required to add blocks or frames to Kweko. channels:provide and telephony:provide are accepted but not used by any feature yet. On the consent screen Kweko groups scopes into what your app can see, change and do.

Calls your frames make through the SDK run with the lower of your app's scopes and the member's own permissions, so an app never lets a member do more than their role allows.

Lifecycle webhooks

If your app has a webhook URL, Kweko posts these events to it:

EventWhen
app.installedA workspace installed the app
app.scopes_changedA workspace approved the app again with other scopes
app.settings_updatedAn admin changed the app's settings in the workspace
app.uninstalledThe app was uninstalled, or removed after a refresh token was reused
json
{
  "id": "evt_…",
  "type": "app.installed",
  "created_at": "2026-09-27T09:41:12Z",
  "data": { "app_id": "app_…", "workspace_id": "ws_…", "installation_id": "inst_…", "scopes": ["leads:read", "ui:extend"] }
}

They carry Kweko-Event, Kweko-Timestamp and Kweko-Signature headers, signed like webhooks but with your client secret as the key. They go through the same durable queue as webhooks: queued in the same transaction as the change, answered within 10 seconds, and retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours for up to 24 hours, with the same Kweko-Delivery id on every attempt. Failures never disable anything. Still treat the API as the source of truth (a token that stops working means the app was uninstalled).

Client secrets

Rotate the client secret in the developer console. The old secret keeps working at the token endpoint for 24 hours; signatures on requests Kweko sends to you use the new secret right away.