Chat clients & realtime
Rendering emotes in chat for millions of viewers needs a different shape than a dashboard: one small, edge-cached document per channel and a push stream for changes. That's what these REST endpoints are for. No key needed.
1. Load emotes once
GET https://api.jiltv.app/v1/sets/global
GET https://api.jiltv.app/v1/channels/twitch/{twitchUserId}/bootstrapThe channel bootstrap holds the active set in a compact form; the global document holds the sets every channel can use. Channel emotes win over global ones with the same name.
{
"channel": { "id": "01J9…", "platform": "TWITCH", "platform_channel_id": "123456", "owner_id": "01J8…", "display_name": "Jilence" },
"set": {
"id": "01J9Z3K8…", "name": "Jilence's Emotes", "capacity": 1000,
"emotes": [
{ "id": "01M43XZ4…", "name": "catDance", "original_name": "catJAM", "v": 1, "w": 32, "h": 32, "f": 9 }
]
},
"cdn": "https://cdn.jiltv.app/emotes"
}| Field | Meaning |
|---|---|
name | What viewers type. original_name is present when the channel renamed the emote. |
v | File version. Files are immutable per version, so URLs are cacheable forever. |
w, h | Size at 1x in CSS pixels. |
f | Flags: 1 animated, 2 zero-width (overlays the previous emote), 4 NSFW, 8 AVIF stored. |
Responses carry an ETag; send If-None-Match to get a 304. Unknown channels answer 404.
During an outage the CDN keeps serving the last good version for a day (stale-if-error).
2. Build image URLs
// f bits: 1 animated · 2 zero-width · 4 NSFW · 8 AVIF stored
function sources(cdn, e, scale = 1) {
const base = `${cdn}/${e.id}/${e.v}/${scale}x`;
return {
avif: e.f & 8 ? `${base}.avif` : null,
webp: `${base}.webp`,
width: e.w * scale, // w/h are the 1x size: reserve the box, no layout shift
height: e.h * scale,
};
}Use a <picture> with the AVIF source (when stored) and WebP as the image. GIF and PNG are not stored, so never build
those URLs. Scales 1x–4x exist for every emote; pick by devicePixelRatio or use srcset.
Larger scales are never upscaled past the uploaded image, so always size the image from w/h.
3. Stay in sync
Subscribe to the channel's Server-Sent Events stream. Browsers reconnect automatically; the server sends heartbeats and a retry hint. On reconnect, refetch the bootstrap once in case you missed events.
const es = new EventSource('https://api.jiltv.app/v1/events/channels/twitch/123456');
es.addEventListener('set.batch', (e) => {
const { set_id, added, removed, renamed } = JSON.parse(e.data);
if (set_id !== activeSetId) return;
for (const id of removed) index.deleteById(id); // removals first,
for (const r of renamed) index.rename(r.emote_id, r.name); // then renames,
for (const emote of added) index.add(emote); // then additions
});
es.addEventListener('emote.updated', (e) => { /* new version or emote disabled */ });
es.addEventListener('channel.set_switched', () => reloadBootstrap());| Event | Data |
|---|---|
set.batch | set_id, added (compact emotes), removed (IDs), renamed (emote_id, name) |
set.emote_added | set_id, emote |
set.emote_removed | set_id, emote_id |
set.emote_renamed | set_id, emote_id, name |
emote.updated | id, active, and when active the new v, w, h, f |
set.updated | Set renamed or resized. Refetch if you show set metadata. |
channel.set_switched | set_id of the new active set (or null). Refetch the bootstrap. |
Ignore event types you don't know; new ones may be added without notice.
Twitch login names
If you only know a channel's login (e.g. from the URL), resolve it once: GET /v1/resolve/twitch/{login} returns {"platform":"TWITCH","channel_id":"123456"}, or 404 when the channel isn't on JilTV.