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#
- Clients are registered by site staff; there is no self-service form. Reach out on the Discord linked in the navbar with your app name (max 64 characters) and up to 8 redirect URIs.
- Redirect URIs must be absolute
http(s)URLs without a fragment, max 512 characters, and are matched exactly, character for character, including any query string. - You receive a 32-hex
client_idand a 64-hexclient_secret. The secret is shown once; store it server-side and never ship it to a browser or app binary.
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.
| Scope | Grants |
|---|---|
identity | confirm who the user is: GET /api/oauth/userinfo (public username, user id, avatar). Every token has it. |
api:read | the Public API read endpoints: channel, channel followers, categories, me, me/followers |
api:write | PUT /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.
1. Send the user to the consent page#
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
| Param | Rules |
|---|---|
client_id | required, your 32-hex id |
redirect_uri | required, must exactly match a registered URI |
response_type | required, exactly code |
scope | space-separated scopes from the table above; omitted defaults to identity |
state | optional, 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": "..."}.
| Code | Where | Meaning |
|---|---|---|
invalid_request | authorize, token | missing or malformed parameter, or an unregistered redirect URI |
invalid_client | authorize, token | unknown client_id or wrong client_secret |
unsupported_response_type | authorize | response_type is not code |
invalid_scope | authorize | a scope outside identity, api:read, api:write |
unsupported_grant_type | token | grant_type is not authorization_code or refresh_token |
invalid_grant | token | code or refresh token invalid, expired, already used, or bound to a different client (codes also check the redirect URI) |
invalid_token | userinfo | access token invalid or expired |
login_required | authorize, consent | no signed-in session; the consent page handles this by redirecting to login |
access_denied | redirect | the user pressed Deny |
temporarily_unavailable | authorize, token | rate limited (30 requests per minute per IP), retry later |
