details is an allow-listed subset per code, for example field and reason for invalid_input, resourceType for not_found, retryAfterSeconds for rate_limited, or platform, target, and a short hint for unsupported_object. Messages are safe to show to users; they never echo source diagnostics.
Page cursors report expired_cursor, malformed_cursor, invalid_signature, unsupported_version, or no_next_page in details.reason under invalid_input. A search cursor that continues another platform’s search reports platform_mismatch. The lists that have no cursor — the newest TikTok videos and an advertiser’s ads, and advertiser search — reject a cursor parameter with invalid_input.
details.reason on a list
The list surface names its object with a selector and reads only the parameters the selected platform supports, so invalid_input carries the reason the value was refused, the platform that judged it, and one plain hint.
A parameter the platform does not read is always refused, never quietly ignored:
not_found stays reserved for its own meaning: the platform says the account is gone.
Codes
The TypeScript SDK exposes the same information as
RefmatterError with status, code, requestId, retryable, and retryAfterSeconds.
Waiting after a 429
Arate_limited response carries details.retryAfterSeconds and a Retry-After header with the same whole number of seconds. The value reflects the real state of the source, not a fixed backoff: usually a few seconds, sometimes minutes or hours while a platform is limiting us. Wait that long before trying again.
Free errors
Item lookup charges for an item, never for a failure. Every error it returns — invalid input, an account link, a removed item, a rate limit, a timeout, a disabled route, even an access refusal — costs nothing and carriesRefmatter-Credits-Charged: 0. So does a fetch whose result could not be written. A transcript is never charged on its own, so asking for one that turns out to be empty adds nothing to the price of the item. There is never a partial charge and never a job to poll instead of an answer.
A 402 insufficient_credits is free in the same way: the workspace balance ran out, so the platform is never contacted and nothing is charged. Every charged endpoint reports the balance in Refmatter-Credits-Remaining, on the 402 as well, so you can see it reach zero before a request is refused. Work that costs nothing keeps answering at a zero balance: a repeat within your maxAge, an empty page, Resolve a URL and imports.
An unsupported_object from lookup or import says what you sent and what to pass instead:
target is item, account, or absent. The hint is a fixed sentence of at most 160 characters; it never echoes the URL you sent. Resolve a URL returns the same judgement as a 200 with a machine-readable reason, before you spend anything.