API integration guide
Upload, list, embed and play media with deployment-specific API examples.
On this page
Before you start
Use the API from your server to upload media, read your library and create playback links. The examples below use this deployment’s API and playback addresses. Request and response bodies are JSON unless an endpoint documents a different format; file bytes go directly to storage.
| Setting | Value |
|---|---|
| API base address | https://lanternaut.com/v1 |
| Playback domain | https://media.lanternaut.com |
| API key | Create one in API keys. Send it as Authorization: Bearer lnt_…. |
export LANTERNAUT_API="https://lanternaut.com/v1"
export LANTERNAUT_KEY="lnt_..." # Replace with your API key.| Scope | Allows |
|---|---|
assets:read | Read assets, thumbnails, analytics and settings, and create playback or embed links. |
assets:write | Read assets, upload, encode, update and delete media, and make thumbnails, GIFs and stickers. |
usage:read | Read account usage and balance. |
settings:write | Change allowed playback domains and the defaults for new uploads. |
billing:write | Create checkouts and purchase wallet credit. |
The hosts a viewer’s browser contacts. Every URL the API returns for viewers is on the playback domain: HLS playlists and segments, captions, posters, image links and the short-lived links they redirect to, embed pages and the player module. It is the only host to allow in a Content-Security-Policy:
| Directive | Allow |
|---|---|
frame-src | https://media.lanternaut.com — embeds |
connect-src · media-src | https://media.lanternaut.com (and blob: in media-src) — your own player fetching HLS |
img-src | https://media.lanternaut.com — images and posters |
script-src | https://media.lanternaut.com — only if you load the Lanternaut player module |
Inside an embed (its own document, not your page) the player may load Google’s Cast library from www.gstatic.com when a viewer casts. The API address is for your server only. An older *.workers.dev address still answers so that embeds made before the playback domain existed keep working; don’t build on it, and treat any other host in a link as an error.
| Default | Value |
|---|---|
| Playback token lifetime | 6 hours, unless you send ttlSeconds (at most 24 hours). Read expiresAt in the answer: an operator can change the default. |
| API rate limit | 300 requests a minute per key and 1,200 per account. Past either: 429 with Retry-After. |
| Upload part URLs | Valid for one hour each. |
| Unfinished uploads | Closed after 48 hours, with an upload.expired webhook. |
Full endpoint definitions are available in OpenAPI 3.1. For dashboard instructions, visit the documentation home. GIF, sticker and thumbnail tools are available in Creator Studio, and their files can be hosted through the API too (see below).
List your videos and images
GET /assets returns the newest assets first, with 50 per page by default. Set limit up to 200. Pass the response’s nextCursor as cursor to fetch the next page, until it is null.
curl --fail-with-body -sS "$LANTERNAUT_API/assets?status=ready&kind=video" \
-H "Authorization: Bearer $LANTERNAUT_KEY"{
"assets": [
{
"id": "01J9Z…",
"title": "Launch film",
"kind": "video",
"status": "ready",
"durationSeconds": 134.2,
"width": 3840,
"height": 2160,
"statusDetail": {
"playable": true,
"message": "Ready to play.",
"nextStep": "none"
}
}
],
"nextCursor": null
}Use kind=image for images, or omit kind for both. A status filter can contain several states, such as status=queued,processing. Set project_id=main or a project ID to select a location. A project-restricted key only sees its own location.
| statusDetail.nextStep | What to do |
|---|---|
none | The asset needs no action. |
wait | Processing is in progress; check again later. |
finish_upload | Send the remaining parts and complete the upload. |
encode | Start or retry with POST /assets/{id}/encode. |
top_up | Add wallet credit, then request an encode. |
upload_again | Upload a new source file. |
contact_support | Contact support with the asset ID and request ID. |
Read statusDetail.message for the explanation. For background integrations, subscribe to asset.ready and asset.errored webhooks instead of continuously polling. Webhook configuration requires an account-wide key.
| status | Means |
|---|---|
created | Just made; an upload is being opened for it. |
uploading | Parts are being sent. Closed after 48 hours. |
uploaded | Stored, not encoding: sent with encode:false, or its encoded files were removed. |
queued | Waiting for an encoder. |
processing | Encoding. |
ready | Playable. |
errored | Failed; errorReason says why. Encode again, or upload again. |
blocked | Refused by moderation. Not charged, and cannot be played. |
awaiting_funds | The wallet did not cover the encode. Top up, then POST /assets/{id}/encode. |
deleted | Gone. Listed only when asked for by id or with updated_since. |
Polling many assets at once. ids= takes up to 200 comma-separated asset IDs and answers for all of them in one request, deleted ones included. updated_since= (an ISO 8601 time) lists only what changed since then, deleted assets included. Every list answer has asOf: keep the asOf of the first page and send it as the next poll’s updated_since. It is set a little before the read, so a poll can repeat an asset; compare updatedAt.
curl --fail-with-body -sS -G "$LANTERNAUT_API/assets" \
--data-urlencode "updated_since=2026-10-24T08:00:00Z" \
-H "Authorization: Bearer $LANTERNAUT_KEY"A video with an encode under way carries encoding in the list, as in GET /assets/{id}: its stage, percent, etaSeconds and watchableSeconds. Otherwise it is null.
Embed a video
Request a ready video’s embed once, then paste the returned html into your site. The embed address has no scheduled expiry and obtains fresh playback tokens for viewers. Account status, deletion and allowed-domain restrictions still apply.
# Replace this value with a ready video ID from your library.
ASSET_ID="your-video-asset-id"
curl --fail-with-body -sS "$LANTERNAUT_API/assets/$ASSET_ID/embed" \
-H "Authorization: Bearer $LANTERNAUT_KEY"{
"url": "https://media.lanternaut.com/e/01J9Z…?a=…&s=…",
"posterUrl": "https://media.lanternaut.com/poster/01J9Z…?a=…&s=…",
"html": "<iframe …></iframe>"
}posterUrl never expires either: it always shows the video’s current poster (the thumbnail you chose, or a frame from the video), only on your allowed domains. Use it for a list of lessons or a link preview. Change the poster with PATCH /assets/{id}/thumbnail.
Append these options to the returned URL without changing its signed parameters:
| URL option | Behavior |
|---|---|
&autoplay=1&muted=1 | Start automatically when the browser permits muted autoplay. |
&loop=1 | Restart when the video ends. |
&background=1 | Muted, autoplaying and looping; fills its container without controls. |
&color=ff0066 | The accent colour, for this embed. |
&title=0 | Hide (or =1 show) the title; also byline, portrait, logo. |
&controls=0 | No controls; also hide one: progress_bar, volume, cc, speed, quality_selector, pip, fullscreen, chapters, transcript. |
&t=1m30s | Start at that moment. |
&skipping_forward=0 | Viewers can’t skip past what they have watched. |
&max_quality=1080p | Cap (or floor, with min_quality) the stream the player picks. |
&dnt=1 | No viewing statistics, nothing remembered. |
These are Vimeo’s names, so an embed moved over from Vimeo keeps its look. The full list, and the look for every video, are under Customise the player.
Preserve the iframe’s allow="autoplay; fullscreen" attribute. For interactive videos, use a descriptive iframe title so viewers using assistive technology can identify the player.
Customise the player
The dashboard’s Player page sets how every video looks: colours, which controls show, the title, your name and picture, your logo, how it starts and what shows when a video ends. Each video’s page adds its chapters, cards, its own end screen and an email form. The API changes the same settings, and embeds already on your sites update within a minute.
curl --fail-with-body -sS -X PATCH "$LANTERNAUT_API/player" \
-H "Authorization: Bearer $LANTERNAUT_KEY" -H "Content-Type: application/json" \
-d '{"colors":{"accent":"#ff0066"},"show":{"title":true},
"logoImageId":"your-image-asset-id","logoUrl":"https://example.com"}'curl --fail-with-body -sS -X PATCH "$LANTERNAUT_API/assets/$ASSET_ID/player" \
-H "Authorization: Bearer $LANTERNAUT_KEY" -H "Content-Type: application/json" \
-d '{"chapters":[{"start":0,"title":"Intro"},{"start":"1:30","title":"Setup"}],
"leadForm":{"at":"end","heading":"Get the next lesson","askName":true}}'One look per project. Your account’s look is the global one: every video plays with it unless its project has a look of its own. PATCH /projects/{id}/player takes the same settings and gives the project its own look, starting from the account’s; GET on the same address says which look the project uses (own), and DELETE puts it back on the account’s. In the dashboard, pick the project at the top of the Player page.
curl --fail-with-body -sS -X PATCH "$LANTERNAUT_API/projects/$PROJECT_ID/player" -H "Authorization: Bearer $LANTERNAUT_KEY" -H "Content-Type: application/json" -d '{"colors":{"accent":"#2f80ed"}}'Answers to email forms are at GET /leads, and each new one fires the lead.captured webhook. The whole list of settings is in the API reference.
Use a background video
Place the iframe behind your hero content. Add background=1 to the embed URL returned by the API. In this example, replace the sample src with that complete signed URL.
<section class="hero">
<iframe class="hero-video"
src="https://media.lanternaut.com/e/01J9Z…?a=…&s=…&background=1"
title="" allow="autoplay; fullscreen"
tabindex="-1" aria-hidden="true"></iframe>
<div class="hero-text"><h1>Your headline</h1></div>
</section>
<style>
.hero { position: relative; min-height: 80vh; overflow: hidden; }
.hero-video { position: absolute; inset: 0; width: 100%; height: 100%;
border: 0; pointer-events: none; }
.hero-text { position: relative; z-index: 1; }
</style>The player crops the video to fill the section and adapts stream quality to its displayed size and available bandwidth. pointer-events: none keeps buttons over the background clickable.
Show a responsive image
The embed endpoint also works for ready images. It returns a permanent link for each available width and an <img> with srcset, allowing the browser to choose the appropriate size.
ASSET_ID="your-image-asset-id"
curl --fail-with-body -sS "$LANTERNAUT_API/assets/$ASSET_ID/embed" \
-H "Authorization: Bearer $LANTERNAUT_KEY"{
"url": "https://media.lanternaut.com/image/01JA2…/1920.webp?a=…&s=…",
"html": "<img …>",
"variants": [
{ "width": 320, "height": 180, "url": "…" }
]
}Each permanent link redirects to a short-lived image address and respects allowed domains. Keep the generated srcset, adjust sizes to match your layout, and set descriptive alt text. GIFs and stickers saved as hosted images use the image delivery flow once processing is complete.
To make an image in the dashboard, open Creator Studio. A local download must be saved to your library before it has a hosted embed URL.
Use playback links with your own player
Mint a short-lived playback link on your server with POST /assets/{id}/playback-token. Video links are HLS streams suitable for hls.js, Video.js or Safari’s native player. Return the playback URL to your browser, keeping the API key on the server.
ASSET_ID="your-video-asset-id"
curl --fail-with-body -sS -X POST "$LANTERNAUT_API/assets/$ASSET_ID/playback-token" \
-H "Authorization: Bearer $LANTERNAUT_KEY" \
-H "Content-Type: application/json" \
-d '{"ttlSeconds": 3600, "domains": ["www.example.com"]}'{
"token": "eyJ…",
"url": "https://media.lanternaut.com/v/01J9Z…/master.m3u8?token=eyJ…",
"posterUrl": "https://media.lanternaut.com/v/01J9Z…/…/poster.jpg?token=eyJ…",
"expiresIn": 3600,
"expiresAt": "2026-10-10T19:00:00.000Z",
"domains": ["www.example.com"]
}| Field | Behavior |
|---|---|
ttlSeconds | Positive integer, up to 86,400 seconds. Omitted: the current deployment default. |
domains | Optional hostnames. These can narrow the account’s allowed domains, never widen them. |
expiresAt | Renew the link before this timestamp if playback needs to continue. |
posterUrl is the video’s poster on the same lifetime (null while a first encode is under way). Renewing without a rebuffer: every playlist and segment request is checked on its own, and any valid token for the same video is accepted. So keep playing and swap the token in each request: fetch a new link from your server before expiresAt, and have hls.js put its token on every request from then on. Nothing reloads and the position is kept. (Safari’s built-in player can’t do this; give it a lifetime longer than the lesson.)
let token = firstLink.token; // from your server
const hls = new Hls({
xhrSetup(xhr, url) {
xhr.open('GET', url.replace(/([?&]token=)[^&]*/, (_, key) => key + encodeURIComponent(token)), true);
},
});
hls.loadSource(firstLink.url);
hls.attachMedia(video);
// A few minutes before expiresAt, ask your server for a new link.
async function renew() {
const next = await fetch('/my-server/playback-link?video=' + videoId).then((r) => r.json());
token = next.token;
setTimeout(renew, Date.parse(next.expiresAt) - Date.now() - 5 * 60_000);
}
setTimeout(renew, Date.parse(firstLink.expiresAt) - Date.now() - 5 * 60_000);<video id="v" playsinline controls></video>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1"></script>
<script>
// Set playbackUrl to the URL returned by your own server.
const playbackUrl = "PASTE_PLAYBACK_URL_HERE";
const video = document.getElementById('v');
if (Hls.isSupported()) {
const hls = new Hls({ capLevelToPlayerSize: true });
hls.loadSource(playbackUrl);
hls.attachMedia(video);
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = playbackUrl;
}
</script>The Lanternaut player is also available as an ES module at https://media.lanternaut.com/player/lanternaut-player.js. It exports createPlayer(element, { src, poster, background, autoplay, muted, loop }).
A ready image’s playback-token response includes variants and a URL for the widest image. A video’s first encode can return an early HLS playlist once some footage is watchable; otherwise an unready asset returns 409 not_ready. Embeds require the asset to be ready.
Upload media directly to storage
Upload media in parts directly to storage. The API creates the upload and signs each part; media bytes do not pass through the API. You need an assets:writekey, an allowed upload destination and available storage.
| Accepted | Formats |
|---|---|
| Video | Any video/… content type: MP4, MOV, WebM, MKV, AVI, MPEG-TS, 3GP and more, in H.264, HEVC (H.265), VP8, VP9, AV1 or ProRes. |
| Images | image/jpeg, image/png, image/webp, image/gif (the first frame), image/avif, image/tiff. |
| Not accepted | HEIC/HEIF photos (convert to JPEG first), SVG, sound-only files, documents. |
POST /uploads checks content_type before anything is sent and answers 415 unsupported_format, with what is accepted, for anything else. With a generic application/octet-stream the file name’s extension decides. The encoder reads the file itself to be sure, and an upload it cannot read ends errored without charge.
Start the upload
Send the filename, byte size and content type to
POST /uploads. Save the returnedassetId,partSizeandpartCount.Sign and upload each part
For part numbers starting at 1, call
POST /uploads/{assetId}/parts/{partNumber}. PUT that part’s bytes to the returned URL.Complete the upload
After every part succeeds, call
POST /uploads/{assetId}/completewith{}. The server lists and assembles the stored parts.
Run this example in Bash with curl, jq, dd and wc installed. It uploads video.mp4 from the current directory and stops if a request fails. Set the API environment variables from “Before you start” first.
set -euo pipefail
SIZE=$(wc -c < video.mp4 | tr -d '[:space:]')
BODY=$(jq -n --argjson size "$SIZE" \
'{filename: "video.mp4", size: $size, content_type: "video/mp4"}')
curl --fail-with-body -sS -X POST "$LANTERNAUT_API/uploads" \
-H "Authorization: Bearer $LANTERNAUT_KEY" \
-H "Content-Type: application/json" \
-d "$BODY" > upload.json
ASSET=$(jq -er '.upload.assetId' upload.json)
PART=$(jq -er '.upload.partSize' upload.json)
COUNT=$(jq -er '.upload.partCount' upload.json)
for ((n = 1; n <= COUNT; n++)); do
URL=$(curl --fail-with-body -sS -X POST "$LANTERNAUT_API/uploads/$ASSET/parts/$n" \
-H "Authorization: Bearer $LANTERNAUT_KEY" | jq -er '.url')
dd if=video.mp4 bs="$PART" skip="$((n - 1))" count=1 2>/dev/null | \
curl --fail-with-body -sS -X PUT --data-binary @- "$URL"
done
curl --fail-with-body -sS -X POST "$LANTERNAUT_API/uploads/$ASSET/complete" \
-H "Authorization: Bearer $LANTERNAUT_KEY" \
-H "Content-Type: application/json" -d '{}'Alternatively, complete with { "parts": [{ "partNumber": 1, "etag": "…" }] }. Read each PUT response’s ETag header and supply all parts in ascending order, with unique numbers. The S3 field names PartNumber and ETag are also accepted.
| Optional start field | Purpose |
|---|---|
project_id | Omit to use the key’s upload destination. Null selects Main only if the key permits access to Main. |
title | Display name; defaults to the filename. |
encode | True by default. False stores the source for a later encode. |
max_height | 240, 480, 720, 1080, 1440 or 2160. Premium quality requires a plan that includes it. |
delivery_mode | adaptive or single; omitted uses the account default. |
single_resolution | Requested quality for single delivery. |
keep_original | Retain the source after processing; omitted uses the account default. |
transcribe | True requests captions with the video’s encode, where supported by the plan. |
transcription_language | Optional language with transcribe:true; null or omitted detects the language. |
Resume using GET /uploads/{assetId} and GET /uploads/{assetId}/parts, then send missing parts. Abandon an open upload with DELETE /uploads/{assetId}. Unfinished uploads are closed after 48 hours: the asset becomes errored (upload_abandoned) and the upload.expired webhook fires. GET /assets?status=uploading lists the uploads still open. Completion can be retried for the same asset without starting another encode.
PARTS=$(seq 1 "$COUNT" | jq -cs '{partNumbers: .}')
curl --fail-with-body -sS -X POST "$LANTERNAUT_API/uploads/$ASSET/parts" \
-H "Authorization: Bearer $LANTERNAUT_KEY" -H "Content-Type: application/json" \
-d "$PARTS"
# → { "parts": [{ "partNumber": 1, "url": "…", "method": "PUT", "expiresAt": "…" }, …] }Retrying safely. Send an Idempotency-Key header (any unique string, such as a UUID) with POST /uploads, POST /imports or POST /projects. If the connection drops, send the same request with the same key: you get the first answer back (marked Idempotent-Replayed: true) instead of a second upload. Keys are kept for 24 hours. The same key with a different request is refused 422 idempotency_key_reused.
If a file’s processing stops moving, send it again with POST /assets/{id}/requeue (assets:write). It goes back in line and an encoder is asked for at once; nothing new is charged. The answer is requeued or waiting, or running when an encoder is working on it right now. Lost jobs are also put back automatically every minute.
Set a video’s thumbnail
A video’s thumbnail is the picture its player shows before it plays. Upload any image (JPEG, PNG or WebP), wait until it is ready, then make it the thumbnail. It applies at once, in every embed.
curl -X PATCH "$LANTERNAUT_API/assets/VIDEO_ID/thumbnail" \
-H "Authorization: Bearer $LANTERNAUT_KEY" \
-H "Content-Type: application/json" \
-d '{"image_id":"IMAGE_ID"}'Send {"image_id":null} to go back to the automatic frame. GET /assets/{id}/thumbnail returns the current thumbnail with a permanent link to each of its sizes, or null while the automatic frame shows. Reading takes assets:read; changing it assets:write. A key limited to one location can only use images from that location.
Host thumbnails, GIFs and stickers
Prepared a thumbnail, sticker or GIF yourself? Host it like the dashboard’s Create tools do: start a creation, upload its bytes with the ordinary multipart upload, then queue it. We keep a WebP copy in several sizes.
| kind | File | Limits |
|---|---|---|
thumbnail | A still PNG | 16 to 2,048 pixels a side |
sticker | A still PNG, transparency kept | 16 to 2,048 pixels a side |
gif | An animated GIF | At most 640 pixels a side, 15 seconds and 180 frames |
Start the creation
POST /creator/uploadswithkind,titleandsize(bytes, at most 20 MB). For a thumbnail, addthumbnail_forwith the video’s ID. The answer’suploadis an ordinary multipart upload.Upload and complete it
Sign and send each part, then complete the upload, exactly as in Upload media directly to storage.
Queue it and wait
POST /creator/{assetId}/queue, then readGET /creator/{assetId}untilstatusisready(orfailed, witherror). A thumbnail then applies itself to its video.
GET /creator lists your newest creations and GET /creator/status says whether hosting is available. At most five can be in progress at once. Reading takes assets:read; the rest assets:write.
Read video analytics
Plays and watch time come from the player’s own reports, in embeds and the Lanternaut player. They are kept for 90 days, in UTC days. Both calls take assets:read; a key limited to one location counts only its videos.
curl "$LANTERNAUT_API/analytics?days=7" \
-H "Authorization: Bearer $LANTERNAUT_KEY"| Field | Meaning |
|---|---|
plays | Sessions that started playing. |
watchedSeconds | Total time watched. |
completed | Plays that reached the end. |
errors | Playback errors. |
GET /analytics returns totals, previous (the period before; null for 90 days), daily and videos ranked by plays, 50 a page with page and hasMore. days is 7, 30 (the default) or 90. GET /assets/{id}/analytics returns the same for one video.
Manage webhooks
Webhooks tell your server when a video is ready, fails or is deleted, and when captions are ready, instead of polling. They belong to the account, so a key limited to one location cannot manage them. You can also manage them on the Webhooks page.
| Request | Does |
|---|---|
POST /webhooks | Add one; its signing secret is shown once. |
GET /webhooks | List them. |
PATCH /webhooks/{id} | Change its url or events, or pause with {"active": false} and resume with true. A paused one gets nothing. |
POST /webhooks/{id}/rotate-secret | A new signing secret, shown once. Deliveries are signed with it from then on. |
DELETE /webhooks/{id} | Remove one. |
GET /webhooks/deliveries | The latest deliveries and how each went: pending, delivered or failed. |
POST /webhooks/deliveries/{id}/resend | Send a failed delivery again, unchanged, within a minute. |
Each delivery is a POST of { "id", "type", "createdAt", "data" }. data by event:
| Event | data |
|---|---|
asset.ready | assetId, jobId, kind (initial or reencode), durationSeconds |
asset.errored | assetId, jobId, kind, status (errored or blocked), errorReason |
asset.deleted | assetId |
transcript.ready | assetId, jobId, language, segmentCount |
transcript.errored | assetId, jobId, errorReason |
lead.captured | leadId, assetId, email, name, atSeconds, createdAt |
upload.expired | assetId, title, declaredBytes, startedAt, errorReason (upload_abandoned) |
usage.warning | dimension (storage, delivery or encode), stage (near at 80%, reached at 100%, throttled), used, limit, unit, percent, message, walletAvailableUsd, periodStart, periodEnd |
balance.low | availableUsd, heldUsd, overageSpendUsd, topupMinimumUsd, message, periodStart, periodEnd |
usage.warning and balance.low are each sent once per stage per billing cycle, checked every 15 minutes: subscribe to them to hear before delivery is slowed or encodes wait. A field may be added to data at any time; ignore what you don’t know.
Check the signature before trusting a delivery. X-Lanternaut-Signature-V2: t=<unix seconds>,v2=<hex> is the HMAC-SHA256, with your secret, of the time, a dot and the raw body. Refuse it when t is more than 5 minutes from your clock: an old delivery replayed by someone else fails. (X-Lanternaut-Signature: sha256=<hex>, the body alone, is still sent for receivers written before.)
import { createHmac, timingSafeEqual } from 'node:crypto';
// rawBody: the request body exactly as received, before any JSON parsing.
function verify(rawBody, header, secret, toleranceSeconds = 300) {
const parts = Object.fromEntries((header ?? '').split(',').map((p) => p.split('=')));
const t = Number(parts.t);
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
const a = Buffer.from(parts.v2 ?? '');
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}
// verify(rawBody, req.headers['x-lanternaut-signature-v2'], process.env.LANTERNAUT_WEBHOOK_SECRET)Answer with any 2xx within 5 seconds. Otherwise the delivery is tried again 1 minute, 5 minutes, 30 minutes and 2 hours after the attempt before: the first attempt and four retries, five in all, the last about 2.5 hours after the first. Every attempt has the same event id (use it to skip one you have handled) and a fresh t. After rotating a secret, accept the old and the new one for a few minutes; a delivery your endpoint refused meanwhile is retried on the schedule above.
Read usage and balance
GET /usage (usage:read) returns this billing cycle against your plan, in the same terms the dashboard shows: what you store now, what has been delivered and encoded, the plan’s limits, what is left, and the wallet.
curl --fail-with-body -sS "$LANTERNAUT_API/usage" \
-H "Authorization: Bearer $LANTERNAUT_KEY"| Field | Meaning |
|---|---|
plan, planName, period | The plan and the cycle’s start and end. |
storageGb | Stored now, the figure the storage allowance checks. |
deliveryGb, encodeAllowanceMinutesUsed | Delivered this cycle, and standard encoding minutes used. |
limits, remaining | The allowances (with deliveryGraceGb, the free delivery past the allowance) and what is left of each. |
over, throttled | Which allowances are used up, and whether delivery is slowed now (past allowance and grace with an empty wallet). |
uploadsBlocked, uploadsBlockedReason | Whether new uploads are refused, and why (storage or nonpayment). |
balance | availableUsd, heldUsd (reserved by encodes under way) and overageSpendUsd this cycle. |
To be told rather than ask, subscribe a webhook to usage.warning and balance.low (above). Past the plan, delivery uses its free grace, then the wallet; with the wallet empty delivery is slowed, never cut, and new encodes wait in awaiting_funds. Prices are in GET /catalog and on the pricing page.
Import from a link or from Vimeo
Instead of uploading, give Lanternaut the address of a file and it fetches it into your storage, then encodes it like any upload. Imports need an assets:write key and count toward your storage like uploads.
curl --fail-with-body -sS -X POST "$LANTERNAUT_API/imports" \
-H "Authorization: Bearer $LANTERNAUT_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com/launch.mp4", "title": "Launch"}'The link must download the file itself and say its size; a web page, or a link that needs a sign-in, fails with the reason in error. The answer is the import, status waiting. Follow it with GET /imports/{id}: it moves to copying (with percent), then copied, when assetId is the new file, and done once it plays. The usual asset.ready webhook fires as well.
| Call | What it does |
|---|---|
GET /imports | Newest first; filter with status=failed and so on. |
POST /imports/{id}/retry | Tries a failed import again from the start. |
DELETE /imports/{id} | Cancels one still waiting or copying. |
POST /imports/vimeo | With { "videos": [{ "id": "76979871" }] }: imports from the Vimeo account connected on the dashboard’s Import page. |
GET /imports/vimeo/videos?page=1 | A page of that Vimeo library, each video marked if already imported. |
Imports take the same optional fields as an upload: project_id, encode, max_height, delivery_mode, single_resolution and keep_original. A Vimeo video brings its title, its captions and its custom thumbnail once it plays.
Projects and API keys
Main is the default library. Create projects to organize related videos and images. Each file belongs to one location; moving it preserves its asset ID and embed links. Each API key has an upload destination and an optional restriction to that location. A new key with a selected project starts restricted by default.
| Operation | Endpoint or field |
|---|---|
| List or create projects | GET /projects · POST /projects |
| Read progress or rename a project | GET /projects/{id} · PATCH /projects/{id} |
| Move a file | PATCH /assets/{id}/project |
| Create an additional key | POST /keys |
| Change a key’s settings | PATCH /keys/{id} |
| Rotate only the selected key | POST /keys/{id}/rotate |
On upload, omit project_id to use the key’s destination. Set it to a project ID, or null for Main, only when the key permits that location. On listing, use project_id=main or a project ID. Without a filter, an unrestricted key lists all locations and a restricted key lists only its own.
A key’s raw secret is shown only when it is created or rotated. Store the new secret securely and update the integration when rotating; the old key is revoked. Key creation accepts projectId, restrictToProject, scopes and rateLimitPerMinute. New keys omit billing:write by default.
Project deletion requires the exact project name in confirmationName. It revokes linked keys and runs cleanup in the background. Read the project to check progress; repeat deletion after deletion_failed to retry. Main cannot be deleted as a project. See OpenAPI for complete request schemas.
Limit playback to allowed domains
Open Settings → Allowed domains and enter the sites where your media may appear, one per line. An empty list allows any site. The restriction applies to video and image playback.
| Entry | Matches |
|---|---|
example.com | Only example.com. |
*.example.com | Subdomains such as www.example.com, but not example.com itself. |
www.example.com | Only www.example.com. |
localhost · 127.0.0.1 | Local development. Other IP addresses are not supported. |
List both example.com and *.example.com if you need the root and its subdomains. Entries name hosts; schemes, ports and paths are ignored. A playback token’s optional domains list may narrow these permissions, never widen them.
curl -X PATCH "$LANTERNAUT_API/settings" \
-H "Authorization: Bearer $LANTERNAUT_KEY" \
-H "Content-Type: application/json" \
-d '{"allowed_domains":["example.com","*.example.com"]}'PATCH /settings replaces the whole list and needs a key for the whole account with the settings:write scope; keys made before 12 October 2026 need it added on the API keys page. The same call sets the defaults for new uploads (keep_original, delivery_mode), and GET /settings reads them all.
Handle errors and retries
API errors contain a machine-readable error code. A human-readable message is optional; validation errors can include issuesinstead. Every API response carries an x-request-id header. Keep it with the asset ID when contacting support.
{
"error": "invalid_request",
"issues": [
{ "path": ["size"], "message": "Expected number, received string" }
]
}| Status | Action |
|---|---|
| 400 | Correct the request fields or incomplete part list; inspect error and issues. |
| 401 | Check that the bearer key is valid and has not been revoked. |
| 402 | Read the error details and check wallet balance or payment status before retrying. |
| 403 | Check key scopes, project restrictions, account status and plan features. Uploads may also require email confirmation. |
| 404 | Check the endpoint, asset ID and key’s permitted location. Files outside the key’s boundary also return 404. |
| 405 | Use a method listed in the Allow response header. |
| 409 | The current state prevents the operation. For not_ready, read statusDetail. |
| 410 | The upload expired. Start a new upload and send the file again. |
| 413 | The upload exceeds its size limit or available storage. |
| 415 | unsupported_format: the file type is not one we can read; see accepted. |
| 422 | The stored upload is larger than its declared size (start again with the correct byte count), or idempotency_key_reused. |
| 429 | Wait the number of seconds in Retry-After, then retry within the rate limit. |
| 5xx | A server operation failed. Record the request ID and apply the retry guidance below. |