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.

A request that asked for more than one call allowsjson
{
  "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
  }
}
type
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.
message
What went wrong, written to be acted on. It names the limit and what was asked for, not just that a limit exists.
suggestion
Present when one concrete call would have worked. It is an arguments object, so the recovery is a spread rather than a parse.
alternative
Present when no single call is the answer. It is a different approach, in words, for you to choose from.
Recovering, with the SDKts
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.

Every error type the API returns
TypeStatusWhenWhat to do
invalid_schema400The 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_window400end is before start, or the window falls outside the video.Clamp against video.duration, which any earlier response reports.
gone410An 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_request400A malformed timestamp, or neither url nor upload_id.The message names the field. Timestamps take seconds or MM:SS.
unauthorized401No credential, a malformed one, or an expired token.Send Authorization: Bearer sk_live_…. Keys are created in the dashboard.
insufficient_credits402The 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_scope403An OAuth token that was never granted video:read.Reconnect the account and approve the scope.
tier_limit403The 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_found404A video, job or upload id that names nothing you own.Ids are scoped to an organization. Check which key you are sending.
source_unavailable422The 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_stream422The 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_limited429Too many requests, or more jobs at once than the plan allows.Back off and retry. Honour Retry-After when it is sent.
processing_failed500A 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.

Which errors are worth retrying
RetryDo 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 landedsource_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.

unanswered
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.
unresolved
The stretches the reading could not settle, and what to send to settle them. from and to go back as window, and region goes back as region. Asking again costs the seconds you name, not the video.
speech.is_speech === false
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.