API docs
Search docs
Guides, operations and types

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

HTTP
GET https://api.jiltv.app/v1/sets/global
GET https://api.jiltv.app/v1/channels/twitch/{twitchUserId}/bootstrap

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

Response
{
  "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"
}
FieldMeaning
nameWhat viewers type. original_name is present when the channel renamed the emote.
vFile version. Files are immutable per version, so URLs are cacheable forever.
w, hSize at 1x in CSS pixels.
fFlags: 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

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

JavaScript
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());
EventData
set.batchset_id, added (compact emotes), removed (IDs), renamed (emote_id, name)
set.emote_addedset_id, emote
set.emote_removedset_id, emote_id
set.emote_renamedset_id, emote_id, name
emote.updatedid, active, and when active the new v, w, h, f
set.updatedSet renamed or resized. Refetch if you show set metadata.
channel.set_switchedset_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.

Search
Find emotes or jump to a page