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.
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

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. Access tokens live for 1 hour; use the refresh token to get a new pair when one expires.

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, 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)
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