← Watch Tonight / API
Get a token

Drive Watch Tonight from your own code

Everything the web app does goes through one public surface. Base URL:

https://api.skillsafe.ai/v1/app-api

Every request carries Authorization: Bearer <token> and Content-Type: application/json. Every response is a JSON envelope: {"ok":true,"data":{…}} on success and {"ok":false,"error":{"code":"…","message":"…"}} on failure. Read error.code, not only the HTTP status.

Picks come from the model's general knowledge. Nothing checks that a title is real or where it streams; the web app checks each pick against the person's own limits and lists, and code calling the API should do the same.

Errors

CodeMeaningWhat to do
UNAUTHORIZEDMissing, expired or wrong-app token.Mint a guest token or sign in again.
INSUFFICIENT_CREDITSBalance below min_credits.Top up. /estimate is free and tells you the hold.
VALIDATION_ERRORThe input object failed validation.Check error.details. The body is the input object itself — never wrap it in {"input": …}.
RATE_LIMITEDToo many requests.Back off and retry.
JOB_FAILEDThe run started and did not complete.Retry with the same Idempotency-Key.

The task field comes first

One prompt serves two turns, chosen by task:

Input

One flat object of strings. task is required; everything else may be empty. Lists and objects travel as JSON text.

FieldTypeMeaning
taskstring, required"pick" or "refine".
loved, dislikedstringOne title per line, optionally Title (year) - note (1,800 and 1,200 characters; long lists keep the first and last lines).
moodsstringComma-separated, e.g. Cozy, Funny.
formatstringmovies, shows or both.
runtimestringany, under_100, under_120, under_150, short_episodes (35 min or less), limited_series.
erastringany, classic (before 1980), 1980_1999, 2000_2014, recent (2015+).
language, language_notestringany, english, non_english or specific with the language in language_note.
audiencestringsolo, partner, friends, family_kids, family_teens.
avoidstringComma-separated content to keep out, e.g. Gore, Jump scares.
countstring8, 10 or 12.
notesstringAnything else (800).
seenstringTitles already watched, one per line; never picked.
previousstring (JSON)Refine only: [{"title","year","kind"}] of earlier picks.
feedbackstring (JSON)Refine only: {"more_like":[],"less_like":[],"seen":[],"keep":[],"note":""}.
prescanstring (JSON)The web app's free check: counts, parsed limits, conflicts and exclude (titles never to pick). Send {} if you have none.
retry_notestringSent only on the one automatic reformat retry.

Output

The output (output.output) is one JSON object: task, status (ok · need_more · off_topic), message, title, taste_read, changes (refine only), picks[] of {title, year, kind, runtime_min, seasons, language, country, genres[], fit, anchor, why, content_notes, confidence} with kind in movie · series · limited and fit in safe · stretch · wildcard, left_out[] of {title, reason} and followups[].

runtime_min is a movie's length or a series' typical episode length. status: "need_more" means there was too little to go on: show message and the 3 or 4 starter picks. The model may wrap the object in a code fence; strip it before parsing.

1. Get a token

A guest token is enough for /me and /estimate. Running is metered and needs a personal token from signing in. The tokens page shows yours and copies it. Replace YOUR_TOKEN below with it.

2. Check the session and the balance

/me returns subject_type, subject_id and credits. Signed in means subject_type === "user".

3. Price it before you run it

/estimate is free and starts no job. hold_credits is a reservation priced against the longest possible reply, not the price. A first set (task: "pick"):

A refine round (task: "refine") carries the last set and the marks:

4. Run and poll

/run returns a job. Always send an Idempotency-Key — the same key is the same run, so a retry cannot bill twice. The web app derives it from the task and a hash of the input, plus an attempt number for its one automatic reformat retry.

5. Stream it

/run-stream answers with server-sent events and ends with a done frame carrying the whole output, charged_credits and truncated. If truncated is true the balance ran low mid-answer: parse the picks that arrived whole and top up for the rest.