Skip to content

API ​

Put Explain inside your own product. One API call turns a prompt or a document into a narrated video, so your users get video lessons and nobody has to record them.

Companies use it for:

  • LMS and training platforms: a video for every course page, for staff or students
  • Corporate training: turn internal docs and SOPs into videos automatically
  • SaaS products: onboarding and help-center videos for every feature
  • EdTech apps: video explanations next to your lessons
  • Media apps: turn articles into videos, like HN.watch

The API is open to every Scrimba account. To start, create an API key in your settings.

Your product with an embedded Explainer video showing The Q3 Report.

Basics ​

All endpoints are under one base URL and return JSON.

text
https://scrimba.com/api/v1

Every request sends your API key as a bearer token:

text
Authorization: Bearer scr_live_…

Request bodies are application/json, or multipart/form-data when you upload files. Dates are ISO 8601 strings in UTC. Errors are also JSON; see Errors.

The version is in the path. We may add new fields to responses within v1, but we won't remove or rename existing ones.

API keys ​

A key belongs to a Scrimba account. Explainers created with the key belong to that account and count toward its monthly limit.

  1. Sign in to scrimba.com and open your settings. Scroll to the API keys section.
  2. Click Create API key and give it a name.
  3. Copy the key. It starts with scr_live_ and is only shown once.

Keep the key on your server, never in a browser or an app you ship. To revoke a key, click Revoke next to it. Requests with a revoked key return 401. An account can have up to 20 keys, and they share the account's limits.

The examples below use two shell variables:

bash
export SCRIMBA=https://scrimba.com
export SCRIMBA_KEY=scr_live_your_secret_here

Quick start ​

Create an explainer, wait for it to finish, then share its link. The create call returns right away and the explainer is generated in the background. Its page shows progress while it is being built.

javascript
const base = 'https://scrimba.com/api/v1'
const headers = {Authorization: `Bearer ${process.env.SCRIMBA_KEY}`}

const created = await fetch(`${base}/explainers`, {
  method: 'POST',
  headers: {...headers, 'Content-Type': 'application/json', 'Idempotency-Key': crypto.randomUUID()},
  body: JSON.stringify({prompt: 'Explain how the Node.js event loop schedules work', privacy: 'unlisted'})
})
if (!created.ok) throw new Error((await created.json()).error.message)

let explainer = await created.json()
while (explainer.status === 'generating') {
  await new Promise(resolve => setTimeout(resolve, 5000))
  explainer = await (await fetch(`${base}/explainers/${explainer.id}`, {headers})).json()
}
console.log(explainer.status, explainer.url)

A short explainer is ready in under a minute. A long one can take a few minutes. Poll every 5 seconds or so.

GET /api/v1/me ​

The account behind the key, the key itself, the account's tier and monthly usage, and the names the create endpoint accepts.

bash
curl -s "$SCRIMBA/api/v1/me" -H "Authorization: Bearer $SCRIMBA_KEY"
json
{
  "account": {"id": "u1a2b3c", "alias": "acme", "name": "Acme Learning"},
  "key": {"id": "ak9x", "name": "server", "prefix": "scr_live_abc123", "created_at": "2026-10-09T10:12:00.000Z"},
  "tier": {"name": "pro", "requests_per_minute": 120, "concurrent_generations": 3, "explainers_per_month": 100},
  "explainers": {"used": 12, "remaining": 88, "resets_at": "2026-11-01T00:00:00.000Z"},
  "teachers": ["eli5", "standard", "per", "tom", "lol", "professor", "tldr"],
  "modes": ["landscape", "portrait", "square"],
  "privacy": ["private", "unlisted", "public"]
}

key.prefix is the start of the secret, enough to tell keys apart. tier.explainers_per_month is the account's monthly limit: the tier's number, or one Scrimba set for the account. explainers.resets_at is when the monthly count starts over.

POST /api/v1/explainers ​

Creates an explainer and starts generating it. Returns 201 with the explainer object and status generating.

Fields ​

FieldValuesDefault
promptWhat to explain, as a string of up to 12,000 characters. Required unless the request has an attachment.
modelandscape (16:9), portrait (9:16), square (1:1). The shape of the slides.landscape
privacyprivate (your account only), unlisted (anyone with the link), public (listed on the site).private
languageAn ISO 639-1 code such as en or pt-BR. The narration and the visible text are in that language, whatever language the prompt is in. A region, as in pt-BR, selects that dialect.The language of the prompt
teachereli5, standard, per, tom, lol, professor, tldr. The voice and style of the explanation.eli5
watermarktrue or false. false needs a Pro account.true
attachmentsMultipart file parts, up to 5. See Attachments.
cite_attachmentstrue or false. With true, the narration cites the parts of your text attachments it teaches from, and the transcript carries those citations.false

Fields you leave out or set to null use their default.

JSON ​

bash
curl -s "$SCRIMBA/api/v1/explainers" \
  -H "Authorization: Bearer $SCRIMBA_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-4821-explainer" \
  -d '{
    "prompt": "Explain how HTTP caching headers decide what a browser keeps",
    "mode": "landscape",
    "privacy": "unlisted",
    "language": "en",
    "teacher": "standard"
  }'

Multipart, with attachments ​

Send a multipart/form-data body. The text fields are the same. Add each file as a part named attachments. The prompt is optional when there is at least one attachment.

bash
curl -s "$SCRIMBA/api/v1/explainers" \
  -H "Authorization: Bearer $SCRIMBA_KEY" \
  -F "prompt=Walk through this diagram and the code behind it" \
  -F "mode=portrait" \
  -F "attachments=@architecture.png" \
  -F "attachments=@server.ts"

Idempotency ​

Send an Idempotency-Key header (1 to 200 printable characters) with a unique value per explainer. If a request fails before you get the response, retry it with the same header. The API returns the explainer it already created, with 200 instead of 201, and does not create a second one. Keys are stored per API key, and deleting an explainer frees its key.

Response ​

json
{
  "id": "guide0abc123",
  "url": "https://scrimba.com/explain/guide0abc123",
  "status": "generating",
  "error": null,
  "title": "Explain how HTTP caching headers decide what a browser keeps",
  "description": null,
  "mode": "landscape",
  "privacy": "unlisted",
  "language": null,
  "created_at": "2026-10-09T10:15:02.000Z",
  "finished_at": null,
  "video_url": null,
  "embed": "<iframe src=\"https://scrimba.com/explain/guide0abc123\" …></iframe>",
  "attachments": [],
  "duration_ms": 0
}

You can open or share url right away. The page shows the explainer while it is being built and plays it when it is ready.

Failed requests don't count toward your limit. If you are over the monthly limit, you get 429 quota_exceeded. If too many explainers are generating at once, you get 429 too_many_running. Both include a Retry-After header.

GET /api/v1/explainers/:id ​

Returns an explainer created through the API, with its current status.

bash
curl -s "$SCRIMBA/api/v1/explainers/guide0abc123" -H "Authorization: Bearer $SCRIMBA_KEY"

status is generating, then ready or failed. When it is ready, finished_at, language and duration_ms are set. When it failed, error says why, and the explainer does not count toward your monthly limit. A generation that stops, for example because of a server restart, is marked failed after ten minutes.

Explainers made on the site, or by another account, return 404.

GET /api/v1/explainers/:id/transcript ​

Returns the narration of a ready explainer: one entry per spoken passage, in playback order, with the parts of your attachments each passage cites.

While the explainer is generating, or if it failed, you get 409 not_ready. Explainers made on the site, or by another account, return 404.

bash
curl -s "$SCRIMBA/api/v1/explainers/guide0abc123/transcript" -H "Authorization: Bearer $SCRIMBA_KEY"
json
{
  "id": "guide0abc123",
  "attachments": [{"ref": 1, "name": "server.ts", "type": "text/plain"}],
  "entries": [
    {
      "index": 3,
      "slide": 2,
      "title": "Checking the token",
      "text": "The handler reads the token from the header, then compares it to the stored secret.",
      "references": [
        {
          "text": "reads the token from the header",
          "text_start": 12,
          "text_end": 43,
          "attachment": 1,
          "offset": 412,
          "length": 39,
          "lines": [10, 10],
          "cited_text": "const token = req.headers.authorization"
        }
      ]
    }
  ]
}
FieldTypeMeaning
attachmentsarrayThe same list the explainer object carries, so one call is enough.
entries[].indexnumberThe entry's place in the narration, from 0.
entries[].slidenumberThe slide shown while the entry plays, from 0.
entries[].titlestring or nullThe narration label the explainer gave the passage.
entries[].textstringThe spoken words, as the page's transcript shows them.
entries[].referencesarrayThe parts of your attachments the entry cites, in the order the narration lists them. Empty when the entry cites none.
references[].textstringThe words of text the citation supports.
references[].text_start, text_endnumber or nullWhere those words are in text: the index of the first character, and the index after the last. Null when the narration does not speak the words exactly as the citation gives them.
references[].attachmentnumberThe ref of the cited file in attachments.
references[].offset, lengthnumberThe cited part of the file: its first character, and how many characters.
references[].linesarrayThe first and last line of the cited part, counted from 1.
references[].cited_textstringThe cited part itself.

Citations come from cite_attachments: true on the create; Citations shows how to use them. Without it, every references list is empty. Only text and code files are cited, never pictures or PDFs.

A text file is read up to 30,000 characters, and offset counts within what was read. A citation whose quoted part is not found in the file is left out.

GET /api/v1/explainers ​

Lists the explainers created through the API, newest first. Explainers made on the site are not included.

Query parameterValuesDefault
limitRows per page, 1 to 100.25
cursorThe next_cursor of the page before.The first page
bash
curl -s "$SCRIMBA/api/v1/explainers?limit=25" -H "Authorization: Bearer $SCRIMBA_KEY"
# {"explainers": [{…}, {…}], "next_cursor": "guide0abc123"}

curl -s "$SCRIMBA/api/v1/explainers?limit=25&cursor=guide0abc123" -H "Authorization: Bearer $SCRIMBA_KEY"
# {"explainers": [{…}], "next_cursor": null}

Each item is an explainer object without duration_ms. next_cursor is null on the last page. An unknown cursor returns 400 invalid_cursor.

DELETE /api/v1/explainers/:id ​

Deletes an explainer created through the API, and its exported video if it has one. Its page and link stop working, and it returns 404 after that. A deleted explainer still counts toward your monthly limit.

bash
curl -s -X DELETE "$SCRIMBA/api/v1/explainers/guide0abc123" -H "Authorization: Bearer $SCRIMBA_KEY"
# {"deleted": "guide0abc123"}

The explainer object ​

The object that POST, GET and the list return for an explainer.

FieldTypeMeaning
idstringThe explainer's id, used in the paths above.
urlstringThe explainer's page. Who can open it depends on privacy.
statusstringgenerating, ready or failed.
errorobject or null{"code": "generation_failed", "message": "…"} when status is failed; otherwise null.
titlestringThe explainer's title: the prompt, or a name from the attachments when there is no prompt.
descriptionstring or nullA short synopsis, filled when the explainer is ready.
modestringlandscape, portrait or square.
privacystringprivate, unlisted or public.
languagestring or nullThe narration language as a tag such as en-us. null until the explainer is ready.
created_atstringWhen the explainer was made.
finished_atstring or nullWhen the generation finished; null while it runs or after it failed.
video_urlstring or nullAn MP4 of the explainer. Filled only when a video export exists and the explainer is unlisted or public.
embedstring or nullAn <iframe> snippet for your own pages. Filled only when the explainer is unlisted or public: an iframe of a private explainer shows the sign-in wall to everyone but you.
attachmentsarrayThe files sent with the request, in the order sent: {"ref": 1, "name": "server.ts", "type": "text/plain"}. ref is the number the explainer knows the file by.
duration_msnumberThe playback length in milliseconds; 0 until the explainer is ready. On POST and GET only, not in the list.

Attachments ​

A request may carry up to 5 files, as multipart parts named attachments. The explainer reads them as context and can show pictures from them.

KindAccepted asLimit
Picturespng, jpeg, gif, webp5 MB and 30 megapixels each
PDFsapplication/pdf5 MB each
Text and codeAny text/* type, or a name ending in txt, md, log, env, conf, cfg, ini, js, jsx, ts, tsx, py, json, imba, html, css30,000 characters each
Jupyter notebooksA name ending in .ipynbStored as markdown and measured as text: 30,000 characters

Images and PDFs are detected from the file contents. Other files use the declared content type, or the file contents if none is given. An unsupported file returns 415 unsupported_attachment, and a file over the size limit returns 413 too_large. Both errors name the file.

The whole request body is at most 32 MB. A JSON body is at most 256 KB.

Limits ​

LimitFreePro
Explainers a month, per account5100
Generations running at once, per account13
Requests a minute, all keys of the account together30120
watermark: falseNoYes

Pro means the account has a Scrimba Pro subscription. Some accounts have a custom limit; GET /api/v1/me shows the limit that applies to you.

The monthly count resets on the first of each month (UTC). GET /api/v1/me shows the exact time. Failed explainers and rejected requests don't count.

When you hit a limit, you get 429 with a Retry-After header in seconds. Wait that long before you retry.

Errors ​

Errors return JSON in this shape, with an HTTP status and an error code:

json
{"error": {"code": "invalid_prompt", "message": "The prompt is over 12000 characters"}}
StatusCodeMeaning
400invalid_prompt, invalid_mode, invalid_privacy, invalid_language, invalid_teacher, invalid_watermark, invalid_cite_attachments, too_many_attachments, invalid_json, invalid_multipart, invalid_idempotency_key, invalid_limit, invalid_cursorA field, a header or the body is wrong. The message says which.
401unauthorizedThe bearer token is missing, unknown or revoked.
403pro_requiredwatermark: false on an account without Pro.
404not_foundNo explainer with that id on this account, or it was deleted.
409not_readyThe transcript of an explainer that is still generating, or that failed.
413too_largeA JSON body is over 256 KB, a multipart body is over 32 MB, a picture or a PDF is over 5 MB or 30 megapixels, or a text file is over 30,000 characters. The message names the file.
415unsupported_attachment, unsupported_media_typeA file of a kind an explainer does not take, or a body that is neither JSON nor multipart.
429rate_limitedOver the account's requests a minute, or too many pictures are being uploaded site-wide. Wait for Retry-After.
429too_many_runningThe account already has its tier's generations running. Wait for Retry-After, or for one to finish.
429quota_exceededThe month's explainers are used up. The message says when the count resets.
503busyThe server holds too many uploads right now. Retry in a moment.
500internalA fault on Scrimba's side. Retry later; if it persists, contact Scrimba with the time and the explainer id if you have one.

Describes the Explainer API as of 20 October 2026.