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.

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#

Errors#

Failures return JSON in the shape {"ok": false, "error": "..."}.

StatusMeaning
400invalid parameter, cursor, or body
401missing, malformed, or revoked token
403the token is missing the endpoint's scope (insufficient_scope), or the token owner lacks the required capability
404unknown channel, or one you are not allowed to see
429rate 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.

ParamMeaning
limitpage size, 1 to 100, default 50
cursorthe 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.

FieldRules
titlemax 200 characters; omitted or empty clears the title
categoryIdmust be an id from /categories; omitted or null clears the category
languageup 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.

ParamMeaning
styleflat (default) or dot
labelcustom 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:

[![stream status](https://itzon.tv/api/public/v1/badge/streamer.svg)](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:

![stream status](https://img.shields.io/endpoint?url=https%3A%2F%2Fitzon.tv%2Fapi%2Fpublic%2Fv1%2Fbadge%2Fstreamer.json)
curl https://itzon.tv/api/public/v1/badge/streamer.json
{
          "schemaVersion": 1,
          "label": "streamer",
          "message": "LIVE",
          "color": "2ecc71",
          "live": true,
          "username": "streamer"
        }