Zrzur Developers

OAuth2

Let people sign in to your website with their Zrzur account. Installing a bot uses the same screen.

You will need

An application, and a website or program that can receive a redirect.

What it can and cannot do

An access token reads who someone is and which servers they are in. It works only on /oauth2/@me.... It never acts as the person on the rest of the API.

ScopeGives
identifyGET /oauth2/@me
servers.readGET /oauth2/@me/servers
applications.commandsMarks that your commands may be added
bot.installInstalls the bot in a server the person picks

Steps

  1. Open your application, tab OAuth2. Add your redirect URIs, one per line, up to 10: https:// addresses, or http://localhost while you develop. They must match exactly.
  2. Make a client secret if your program runs on a server. Without one the app is a public client. It is shown once.
  3. For every sign-in, make a random state and a random code_verifier. Send the person to:
/oauth2/authorize
  ?client_id=YOUR_BOT_ID
  &redirect_uri=https%3A%2F%2Fexample.com%2Fcallback
  &response_type=code
  &scope=identify%20servers.read
  &state=RANDOM_STATE
  &code_challenge=BASE64URL_OF_SHA256_OF_VERIFIER
  &code_challenge_method=S256

That path is on the Zrzur app. The URL generator on the OAuth2 tab builds this address. PKCE (code_challenge) is always required, also with a client secret.

  1. The person approves. The browser comes back to your redirect URI with code and state. Check that state is yours.
  2. Exchange the code within 10 minutes. It works once.
curl -X POST "https://api.zrzur.com/oauth2/token" \
  -d grant_type=authorization_code \
  -d client_id=YOUR_BOT_ID \
  -d client_secret=YOUR_CLIENT_SECRET \
  -d code=THE_CODE \
  -d redirect_uri=https://example.com/callback \
  -d code_verifier=YOUR_VERIFIER
{
  "access_token": "...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "...",
  "scope": "identify servers.read"
}
  1. Call the API with it:
curl "https://api.zrzur.com/oauth2/@me" -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Make the PKCE values

code_verifier is a random text of 43 to 128 characters. code_challenge is the SHA-256 of that text, encoded as base64url without padding. Keep the verifier until step 5, and send only the challenge in the address.

import { createHash, randomBytes } from "node:crypto";

const verifier = randomBytes(32).toString("base64url");
const challenge = createHash("sha256").update(verifier).digest("base64url");

Refresh and revoke

Access tokens last 1 hour, refresh tokens 30 days. Each refresh token works once and comes back new. Using an old one ends the whole chain, as that looks like theft.

curl -X POST "https://api.zrzur.com/oauth2/token" \
  -d grant_type=refresh_token \
  -d client_id=YOUR_BOT_ID \
  -d refresh_token=YOUR_REFRESH_TOKEN

curl -X POST "https://api.zrzur.com/oauth2/revoke" \
  -d client_id=YOUR_BOT_ID \
  -d token=YOUR_REFRESH_TOKEN

Both routes take a form, not JSON. They are not in the OpenAPI document. A person can also withdraw your app in their settings.

Common errors

These routes answer { "error": "...", "error_description": "..." }.

errorCauseFix
invalid_requestA field is missing, such as code_verifierSend every field
invalid_grantThe code is unknown, used or expired, or PKCE is wrongStart the sign-in again
invalid_clientThe client id or secret is wrong (status 401)Check them
invalid_scopeMore scope than the first grantAsk for less
unsupported_grant_typeAnother grant typeUse the two above

Reference

GET /applications/{application_id}/oauth

Fetch OAuth2 Settings.

Rate limit: any_dlta, 20 / 10 s

PATCH /applications/{application_id}/oauth

Edit OAuth2 Settings. Replace the redirect URIs: https, or http for localhost only; exact match is required when authorizing.

Rate limit: any_dlta, 20 / 10 s

POST /applications/{application_id}/oauth/secret

Reset Client Secret. Create a new client secret (the old one stops working). Only the owner, with a validated MFA ticket (X-MFA-Ticket). The secret is shown once.

Rate limit: any_dlta, 20 / 10 s

GET /oauth2/@me

Current User (OAuth2). Needs identify. Only id, name and avatar; never e-mail or settings.

Rate limit: any_dlta, 20 / 10 s

GET /oauth2/@me/servers

Current User's Servers (OAuth2). Needs servers.read: id, name and icon of each server the user is in.

Rate limit: any_dlta, 20 / 10 s

GET /oauth2/authorize

Authorization Preview. What the consent page shows: the application, the scopes it asks for, and (for bot.install) the servers the user can add it to. Every check of the request happens here first; errors are shown, never redirected.

Not available to bots.

Rate limit: any_dlta, 20 / 10 s

POST /oauth2/authorize

Authorize. The user's decision on the consent page. Approving issues a single-use code (and installs the bot for bot.install); denying returns error=access_denied. Either way the answer is the URL to go to next.

Not available to bots.

Rate limit: any_dlta, 20 / 10 s

GET /oauth2/authorized

Authorized Apps. Applications this user authorized with OAuth2.

Not available to bots.

Rate limit: any_dlta, 20 / 10 s

DELETE /oauth2/authorized/{application_id}

Revoke Authorized App. Revoke every token the user gave this application. It does not remove the bot from servers; that is done per server by its managers.

Not available to bots.

Rate limit: any_dlta, 20 / 10 s

Next