Sign in with itzon

An OAuth 2.0 authorization code flow that lets your app verify a user's itzon identity and, with the api scopes, call the Public API on the user's behalf.

Registering your app#

Scopes#

Scopes are space-separated and shown to the user on the consent page. identity is always implied: requesting api:read grants identity api:read. Anything outside this table is rejected with invalid_scope.

ScopeGrants
identityconfirm who the user is: GET /api/oauth/userinfo (public username, user id, avatar). Every token has it.
chatconnect to chat as the user: the access token works as the IRC PASS and the connection gets the user's own name, badges and roles
api:readthe Public API read endpoints: channel, channel followers, categories, me, me/followers
api:writePUT /api/public/v1/me/stream-info only; implies nothing else

Access tokens share their token store and semantics with the personal API tokens created in the dashboard Settings tab: the Public API treats both kinds identically and only scopes decide access. The difference is lifetime: an app token expires after 1 hour and is renewed with its refresh token, a personal token never expires until its owner deletes it.

https://itzon.tv/oauth/authorize
            ?client_id=YOUR_CLIENT_ID
            &redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback
            &response_type=code
            &scope=identity
            &state=RANDOM_STRING
ParamRules
client_idrequired, your 32-hex id
redirect_urirequired, must exactly match a registered URI
response_typerequired, exactly code
scopespace-separated scopes from the table above; omitted defaults to identity
stateoptional, max 512 characters, echoed back untouched; use a random value to tie the callback to the session that started it
code_challengePKCE challenge, base64url of SHA-256 over your code_verifier (43 characters, no padding); required for public clients, optional for confidential ones
code_challenge_methodexactly S256 whenever code_challenge is sent; plain is not supported

A signed-out user is sent through login first and lands back on the consent page. The page shows your app name and a plain-language line per requested scope with Approve and Deny buttons.

2. Receive the redirect#

Approval navigates to your redirect URI with a code:

https://app.example.com/callback?code=AUTHORIZATION_CODE&state=RANDOM_STRING

Denial redirects with error=access_denied and the same state. The code is single use and expires 60 seconds after it is minted, so exchange it immediately.

3. Exchange the code for a token#

curl -X POST https://itzon.tv/api/oauth/token \
          -d grant_type=authorization_code \
          -d client_id=YOUR_CLIENT_ID \
          -d client_secret=YOUR_CLIENT_SECRET \
          -d code=AUTHORIZATION_CODE \
          -d redirect_uri=https://app.example.com/callback
{
          "access_token": "64_HEX_TOKEN",
          "token_type": "bearer",
          "expires_in": 3600,
          "refresh_token": "64_HEX_TOKEN",
          "refresh_expires_in": 2592000,
          "scope": "identity"
        }

Form-encoded and JSON bodies are both accepted. redirect_uri must be the exact value used in step 1. If the code was minted with a code_challenge, the exchange must also carry the matching code_verifier. Access tokens live for 1 hour; use the refresh token to get a new pair when one expires.

Public clients (PKCE)#

A public client holds no client_secret and proves itself with PKCE (RFC 7636) instead, so an open-source desktop app or SPA can ship its client_id in the clear. Per authorization: generate a random code_verifier (43-128 characters of A-Z a-z 0-9 - . _ ~), send code_challenge=BASE64URL(SHA256(code_verifier)) with code_challenge_method=S256 in step 1, then exchange the code with the verifier and NO secret:

curl -X POST https://itzon.tv/api/oauth/token \
          -d grant_type=authorization_code \
          -d client_id=YOUR_CLIENT_ID \
          -d code=AUTHORIZATION_CODE \
          -d code_verifier=YOUR_CODE_VERIFIER \
          -d redirect_uri=http://127.0.0.1:49152/callback

Rules: the challenge is REQUIRED for a public client and rejected as invalid_request when missing or not S256; sending a client_secret from a public client answers invalid_client; a wrong or missing code_verifier answers invalid_grant and the code is burned. The refresh grant needs only client_id and the refresh token; rotation and reuse detection protect it the same way as for confidential clients. Desktop apps should register a loopback redirect URI and use whatever free port they bound at runtime, see Registering above.

Connecting to chat#

An access token whose scope includes chat doubles as an IRC credential: open the chat connection and send PASS ACCESS_TOKEN as the first command. The connection is assigned the approving user's own username, badges and roles, exactly as if they had signed in on the site. When the access token expires (1 hour) the server revokes the chat session within its 90 second revalidation window (TCP kill / WS close 4403); refresh the token and reconnect with the new one.

Refreshing a token#

curl -X POST https://itzon.tv/api/oauth/token \
          -d grant_type=refresh_token \
          -d client_id=YOUR_CLIENT_ID \
          -d client_secret=YOUR_CLIENT_SECRET \
          -d refresh_token=REFRESH_TOKEN

The response has the same shape as the code exchange. Refresh tokens live for 30 days, are single use, and rotate: a successful refresh revokes BOTH the old access token and the old refresh token atomically and issues a fresh pair, so store the new refresh_token from every response. Reusing a consumed refresh token, presenting it with a different client, or refreshing after the 30 days answer invalid_grant; the user then has to run the authorization flow again.

4. Fetch the user's identity#

curl https://itzon.tv/api/oauth/userinfo \
          -H "Authorization: Bearer ACCESS_TOKEN"
{
          "id": 42,
          "username": "streamer",
          "avatar": "https://itzon.tv/api/live/profile/streamer/avatar?v=1722880000000"
        }

avatar is null when the user has not uploaded one.

Error codes#

Failures answer with OAuth-style JSON: {"error": "...", "error_description": "..."}.

CodeWhereMeaning
invalid_requestauthorize, tokenmissing or malformed parameter, or an unregistered redirect URI
invalid_clientauthorize, tokenunknown client_id or wrong client_secret
unsupported_response_typeauthorizeresponse_type is not code
invalid_scopeauthorizea scope outside identity, chat, api:read, api:write
unsupported_grant_typetokengrant_type is not authorization_code or refresh_token
invalid_granttokencode or refresh token invalid, expired, already used, or bound to a different client (codes also check the redirect URI and, when PKCE started the flow, the code_verifier)
invalid_tokenuserinfoaccess token invalid or expired
login_requiredauthorize, consentno signed-in session; the consent page handles this by redirecting to login
access_deniedredirectthe user pressed Deny
temporarily_unavailableauthorize, tokenrate limited (30 requests per minute per IP), retry later