Errors
An error from this API is a request you can send. Where one concrete call would have worked, the refusal carries a suggestion object holding it, ready to spread straight back into the retry. That is the difference between an agent that recovers and one that apologises.
Last updated
The shape of an error
Two fields are always present. The third is what makes an agent able to recover on its own.
{
"error": {
"type": "window_too_long",
"message": "This call reads up to 600s at a time. You asked for 900s.",
"suggestion": { "window": { "from": 0, "to": 600 } },
"max_window_seconds": 600
}
}- Branch on this, never on the message. A message is written for a person to read and will be improved. A type will not change under you.
- What went wrong, written to be acted on. It names the limit and what was asked for, not just that a limit exists.
- Present when one concrete call would have worked. It is an arguments object, so the recovery is a spread rather than a parse.
- Present when no single call is the answer. It is a different approach, in words, for you to choose from.
import { WindowTooLong } from "@playhead/sdk";
try {
await playhead.ask({ url, question, window: { from: 0, to: 900 } });
} catch (error) {
if (error instanceof WindowTooLong && error.suggestion) {
await playhead.ask({ url, question, ...error.suggestion });
} else {
throw error;
}
}Every type
13 of them. The last column is the point of the table: an error type with no fix beside it is only a label.
| Type | Status | When | What to do |
|---|---|---|---|
invalid_schema | 400 | The schema passed with a question is not a well-formed JSON Schema. | The message names what is wrong with it. This is checked before any work, so there is no charge. |
invalid_window | 400 | end is before start, or the window falls outside the video. | Clamp against video.duration, which any earlier response reports. |
gone | 410 | An endpoint that was part of an earlier shape of this product. | The message names the call to make instead. POST /v1/watch_video answers almost all of them. |
invalid_request | 400 | A malformed timestamp, or neither url nor upload_id. | The message names the field. Timestamps take seconds or MM:SS. |
unauthorized | 401 | No credential, a malformed one, or an expired token. | Send Authorization: Bearer sk_live_…. Keys are created in the dashboard. |
insufficient_credits | 402 | The wallet this credential spends is empty. | Buy credit or wait for the monthly renewal. This is not a retry: the balance has to change first. |
insufficient_scope | 403 | An OAuth token that was never granted video:read. | Reconnect the account and approve the scope. |
tier_limit | 403 | The plan does not allow this, such as a video longer than the plan's limit. | Ask about a window of it instead, or move to a plan that allows it. |
video_not_found | 404 | A video, job or upload id that names nothing you own. | Ids are scoped to an organization. Check which key you are sending. |
source_unavailable | 422 | The video could not be reached: private, deleted, or behind a sign-in. | Upload the file instead. The message says which of these it was, so you know whether a different link would help. |
no_video_stream | 422 | The file cannot be read as a video, or it holds neither video nor audio. | Camera raw is refused by name, with the export to make instead. Anything else means a broken or incomplete file. |
rate_limited | 429 | Too many requests, or more jobs at once than the plan allows. | Back off and retry. Honour Retry-After when it is sent. |
processing_failed | 500 | A fault on our side, and not something to fix in the request. | Retrying is safe and is the right move. |
Retrying
Only two of them, plus a connection that never landed. The SDK does this for you, and anything building its own queue needs the same rule.
| Retry | Do not retry |
|---|---|
rate_limited (429). Back off, and honour Retry-After. | insufficient_credits (402). The balance has to change first. |
processing_failed (500). A fault on our side, and safe to repeat. | window_too_long (400). Send the suggestion instead. |
| A connection that never landed | source_unavailable (422). Upload the file. |
Silence that is not an error
Three fields come back with a 200 and still mean “do not conclude anything from this”. Read them before you act on an answer.
- What you asked for and the video does not show. Each entry names the field and why. An empty value here is a real absence, not a value nobody looked for, which is the whole reason the field exists.
- The stretches the reading could not settle, and what to send to settle them.
fromandtogo back aswindow, andregiongoes back asregion. Asking again costs the seconds you name, not the video. - The audio was music or noise rather than speech. A transcript still comes back, and it reads fluently and is entirely invented. Treat it as absent.
The SDK turns each wire type into a class, so these become instanceof checks rather than string comparisons.
Questions
- Which errors should I retry?
- Only 429 and 5xx, plus a connection that never landed. Everything else needs something to change first: a shorter window, or a balance topped up. Retrying one of those burns the rate limit to arrive at the same refusal.
- What is the suggestion field?
- Arguments for a call that would work, not prose. When a request asks for more than one call allows, suggestion holds the version that fits, ready to spread straight back into the request.
- What do I do when a video cannot be reached?
- Upload the file. A video that is private, deleted or behind a sign-in cannot be fetched from a link, and no amount of retrying changes that. It arrives as source_unavailable with a 422, and reason says which of those it was.
- What is the reason field?
- The narrow code under type, where one type covers several causes. On source_unavailable it is one of unavailable, auth, geo, no_video, empty, bot, ratelimit and unknown, and each one is a different next step: no_video is a still advert and nothing to retry, geo and auth are an upload, empty and ratelimit are a wait. Branch on this rather than on the message. The code is stable and the message is written for a person to read.
- The answer left out something I asked for. Is that an error?
- No, and it is deliberate. What the video does not show is named in unanswered with the reason, rather than guessed at. What the reading could not settle is in unresolved, with exactly the window and region to send back to settle it.