Public API
A JSON REST API for third-party tools: channel status, followers, categories, and stream info updates.
Authentication#
Every request carries one OAuth bearer token: Authorization: Bearer <token>.
There are two ways to get one, and the API treats them identically; what a token may do is decided
only by its scopes: api:read unlocks the read endpoints (channel, followers, categories, me),
api:write unlocks PUT /me/stream-info. Each endpoint below states its required scope.
- Personal tokens: created in the dashboard Settings tab, in the API tokens card. You pick the scopes when creating one, give it a label so you can tell tokens apart, and it acts as your own account. The 64-character hex secret is shown exactly once at creation; only its first 8 characters appear in the list afterwards, so store it like a password. Personal tokens never expire; deleting one revokes it immediately. You can hold up to 32.
- App tokens: access tokens minted through Sign in with itzon. They act on behalf of the user who approved the grant, carry the scopes that user consented to, live for 1 hour, and are renewed with a refresh token.
Requests without a valid token answer 401. Calling an endpoint the token's scopes do not
cover answers 403 with an insufficient_scope error naming the scope you need.
Base URL: https://itzon.tv/api/public/v1
Rate limits#
- 120 requests per minute per token (personal or app), plus a global cap shared by all callers.
- Every authenticated response carries
X-RateLimit-Remaining, the requests left in the current minute. - Over the limit, requests answer
429withX-RateLimit-Remaining: 0until the window resets.
Errors#
Failures return JSON in the shape {"ok": false, "error": "..."}.
| Status | Meaning |
|---|---|
400 | invalid parameter, cursor, or body |
401 | missing, malformed, or revoked token |
403 | the token is missing the endpoint's scope (insufficient_scope), or the token owner lacks the required capability |
404 | unknown channel, or one you are not allowed to see |
429 | rate limited |
GET /channel/{username}#
Scope: api:read. Public info for a channel. viewers is only present while the channel is live; category and categoryId are null when unset; language is one or two comma-separated ISO 639-1 codes (e.g. fr,en) or und. Unknown, banned, and non-streaming accounts answer 404. Password-protected channels answer 404 to everyone except a token owned by the channel itself.
curl https://itzon.tv/api/public/v1/channel/streamer \
-H "Authorization: Bearer $TOKEN"
{
"username": "streamer",
"live": true,
"title": "Speedrun practice",
"category": "Retro",
"categoryId": 3,
"language": "en",
"followers": 1287,
"viewers": 341
}
GET /channel/{username}/followers#
Scope: api:read. Follower list, newest first, with cursor pagination. Same privacy rule as the channel endpoint.
| Param | Meaning |
|---|---|
limit | page size, 1 to 100, default 50 |
cursor | the nextCursor value from the previous page |
curl "https://itzon.tv/api/public/v1/channel/streamer/followers?limit=2" \
-H "Authorization: Bearer $TOKEN"
{
"total": 1287,
"followers": [
{ "username": "viewer1", "followedAt": "2026-08-05T18:24:11Z" },
{ "username": "viewer2", "followedAt": "2026-08-04T09:02:58Z" }
],
"nextCursor": "MTc1NDA2NzA1MTAwMDAwMC40Mg"
}
The cursor is an opaque string marking the position after the last entry of the page.
Pass it back as cursor= to fetch the next page and keep going until
nextCursor is null. Because pagination is keyed on the follow
itself rather than an offset, pages stay stable while people follow or unfollow between
requests. A garbled cursor answers 400.
GET /categories#
Scope: api:read.
curl https://itzon.tv/api/public/v1/categories \
-H "Authorization: Bearer $TOKEN"
{ "categories": [ { "id": 3, "name": "Retro" }, { "id": 7, "name": "Music" } ] }
GET /me#
Scope: api:read. Identifies the token owner.
curl https://itzon.tv/api/public/v1/me \
-H "Authorization: Bearer $TOKEN"
{ "id": 42, "username": "streamer" }
PUT /me/stream-info#
Scope: api:write. Updates the token owner's stream title, category, and language with exactly the validation the dashboard uses. Requires a streaming account; others get 403.
| Field | Rules |
|---|---|
title | max 200 characters; omitted or empty clears the title |
categoryId | must be an id from /categories; omitted or null clears the category |
language | up to two ISO 639-1 codes, as a comma-separated string ("fr,en") or an array (["fr","en"]); null resets to unspecified; omitted leaves it unchanged |
curl -X PUT https://itzon.tv/api/public/v1/me/stream-info \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title": "Speedrun practice", "categoryId": 3, "language": "en"}'
{ "title": "Speedrun practice", "category": "Retro", "categoryId": 3, "language": "en" }
Note that title and categoryId are replaced on every call, so send the full desired state. Validation failures answer 400 with the reason.
GET /me/followers#
Scope: api:read. The follower list of the token owner's own channel, same shape and pagination as the channel followers endpoint, without the privacy restriction.
Live status badge#
No token required. The badge endpoints are public so they can sit in a README:
GET /badge/{username}.svg renders a small image and
GET /badge/{username}.json returns the same state as JSON. Both are rate limited per IP,
cached for 30 seconds and answer 304 when your If-None-Match matches.
| Param | Meaning |
|---|---|
style | flat (default) or dot |
label | custom left-hand text, max 24 characters, sanitized and XML-escaped; defaults to the channel name |
The badge shows LIVE or OFFLINE and nothing else: no viewer count.
Password-protected channels always render OFFLINE, and so does an unknown
or banned channel, so a badge never reveals whether a private stream is running or whether an
account exists.
Markdown for a README:
[](https://itzon.tv/streamer)
Plain HTML:
<a href="https://itzon.tv/streamer">
<img src="https://itzon.tv/api/public/v1/badge/streamer.svg?style=dot&label=my%20stream" alt="stream status" />
</a>
The JSON variant speaks the shields.io endpoint schema, so shields.io can render the badge in its own style while the live state still comes from here:

curl https://itzon.tv/api/public/v1/badge/streamer.json
{
"schemaVersion": 1,
"label": "streamer",
"message": "LIVE",
"color": "2ecc71",
"live": true,
"username": "streamer"
}
