Skip to content

Tokens and authentication

Every Web API call carries a token in the Authorization header:

Authorization: Bearer kbot_…

The workspace is implied by the token. A missing, invalid, expired or revoked token answers 401 invalid_token with a WWW-Authenticate header.

Credential Format Where it comes from Lifetime
Bot token kbot_ + 43 characters API access page, or kit.access with tokenType: bot No expiry; rotate or revoke
User token kusr_ + 43 characters kit.access with tokenType: user 1 hour
Refresh token kref_ + 43 characters With every user token Single use
Authorization code kcode_ + 43 characters The authorize redirect 10 minutes, single use
Client secret kcs_ + 43 characters API access page (private Kits) Until replaced; the previous one works 24 hours
Webhook URL https://hooks.ketvia.com/v1/…/…/<secret> API access page Until revoked

The 43 characters are base64url (256 random bits). Every secret is shown once, when it is created, and stored only as a hash. If you lose one, revoke it and create a new one.

A bot token acts as the Kit bot of one installation. It sees only channels the Kit bot was added to, and edits or deletes only messages the Kit bot posted.

Create one on the Kit’s API access page (admins), or through the kit.access flow with token_type=bot, which an admin must approve.

Revoke it on the API access page. Revocation is immediate.

Rotate it from your code without downtime:

Terminal window
curl -s -X POST https://api.ketvia.com/api/v1/auth/rotate \
-H "Authorization: Bearer $KETVIA_TOKEN"
{
"token": "kbot_…",
"previousTokenExpiresAt": "2026-10-06T09:30:00.000Z"
}

The new token is shown once. The token you called with keeps working for 24 hours, then expires. A token can be rotated only once: rotate again with the new token, not the old one.

A user token acts as one member and is bounded by that member’s access: it sees what they see and posts as them. User tokens:

  • last 1 hour (expiresIn: 3600);
  • come with a single-use refresh token: each refresh returns a new access token and a new refresh token;
  • if a refresh token is used twice, Ketvia treats it as stolen and revokes the whole grant (all access and refresh tokens from that authorization);
  • end when the member’s web session that authorized them ends (for example when they sign out).

Use user tokens for actions a Kit bot cannot take in v1: adding and removing reactions, and uploading files.

kit.access lets your Kit’s own backend obtain tokens: a bot token for the installation (an admin approves) or a user token for one member (any member approves for themselves). It is an OAuth-style authorization code flow.

Before you start, on the Kit’s API access page:

  • note the client ID (the Kit id);
  • generate a client secret (kcs_…, shown once);
  • check the redirect URLs: they come from the manifest’s oauth.redirectUrls, must be https, and the redirect_uri you send must equal one of them.
  1. Send the member to Ketvia. For a user token, first create a PKCE verifier (43 to 128 characters from A–Z a–z 0–9 . _ ~ -) and its S256 challenge, base64url(sha256(verifier)) without padding. Keep the verifier and a random state in the member’s session on your side.

    https://ketvia.com/app/kits/authorize
    ?workspace=<workspaceId>
    &kit=<slug>
    &token_type=user
    &redirect_uri=https%3A%2F%2Fkit.acme.example%2Fketvia%2Fcallback
    &state=<opaque>
    &code_challenge=<S256 challenge>

    (Shown on several lines for reading; it is one URL.) token_type is bot or user. code_challenge is required for user tokens. A bot authorization must be allowed by a workspace admin.

  2. The member allows it. Ketvia shows who is asking for what and, after the member allows it, redirects to:

    https://kit.acme.example/ketvia/callback?code=kcode_…&state=<opaque>

    Check that state matches what you stored. The code is valid for 10 minutes and works once.

  3. Exchange the code from your backend (never from a browser):

    Terminal window
    curl -s https://api.ketvia.com/api/v1/oauth/kit.access \
    -H 'Content-Type: application/json' \
    -d '{
    "grantType": "authorization_code",
    "clientId": "<kit id>",
    "clientSecret": "kcs_…",
    "code": "kcode_…",
    "redirectUri": "https://kit.acme.example/ketvia/callback",
    "codeVerifier": "<the PKCE verifier>"
    }'

    redirectUri must equal the one the code was issued for. codeVerifier is required for user tokens.

A bot authorization returns:

{
"tokenType": "bot",
"accessToken": "kbot_…",
"botId": "9c4e2b7a-1d3f-4a6e-b8c5-0f2d7e6a9b14",
"installationId": "5d1a8e7c-93b2-4f3e-8c0d-2a6b4e9f1c37",
"workspace": { "id": "0b9f6c1e-2f4d-4c55-9a51-6f7f3c1d2e10", "name": "Acme" },
"scopes": ["conversations:read", "messages:write"]
}

A user authorization returns:

{
"tokenType": "user",
"accessToken": "kusr_…",
"refreshToken": "kref_…",
"expiresIn": 3600,
"userId": "2f8b6d1c-7e4a-4c9b-a3d5-6e1f0a8c2b97",
"installationId": "5d1a8e7c-93b2-4f3e-8c0d-2a6b4e9f1c37",
"workspace": { "id": "0b9f6c1e-2f4d-4c55-9a51-6f7f3c1d2e10", "name": "Acme" },
"scopes": ["messages:read", "reactions:write"]
}

Before the hour is up, exchange the refresh token:

{
"grantType": "refresh_token",
"clientId": "<kit id>",
"clientSecret": "kcs_…",
"refreshToken": "kref_…"
}

The answer has the same shape as a user authorization, with a new refresh token. Store it and discard the old one: the old refresh token is spent, and using it again revokes the whole grant.

With the SDK, exchangeKitAccess(request, { baseUrl }) sends either request and returns the parsed answer. It does not retry after network errors, because codes and refresh tokens are single-use.

The client secret authenticates your backend in POST /oauth/kit.access. Generate it on the Kit’s API access page. Generating a new secret shows it once; the previous secret keeps working for 24 hours so you can deploy the new one without downtime.

A token carries the scopes that were granted when it was issued, limited to what the installed manifest version still requests. GET /auth/test returns these effective scopes. If a new manifest version drops a scope, existing tokens lose it at once.

  • Keep tokens, refresh tokens, client secrets and webhook URLs in a secret store, never in code or logs.
  • Never send a token to a browser. The kit.access exchange happens server-side.
  • Rotate bot tokens with POST /auth/rotate and deploy the new token within 24 hours.
  • Revoke anything you suspect has leaked: revocation is immediate.