Errors and retries
How failed requests are reported, and which requests are retried.
Errors
Failed requests reject with a SynthientError or one of its subclasses. Check them with instanceof:
import { APIError } from "@synthient/synthient";
try {
await client.lookupIP("8.8.8.8");
} catch (error) {
if (!(error instanceof APIError)) {
throw error;
}
switch (error.status) {
case 402:
console.log("out of lookup credits");
break;
case 429:
if (error.retryAfter === undefined) {
console.log("monthly request budget spent");
} else {
console.log(`rate limited, retry after ${error.retryAfter} ms`);
}
break;
default:
console.log("request", error.requestID, "failed:", error.detail || error.title);
}
}
| Class | Extends | Thrown when |
|---|---|---|
SynthientError | Error | A request failed or a response was invalid, such as a body that is not a JSON object. It is the base class of the others. |
APIError | SynthientError | The API, or the storage host serving an export download, answered with a non-success HTTP status. A storage error carries only status and retryAfter. |
APIConnectionError | SynthientError | The connection failed before a response arrived or while its body was being read, for example because of a DNS, connection, or TLS failure, or a stream that dropped. The underlying error is its cause, except when the storage host that serves an export download fails, where it is omitted to keep presigned URLs out of errors. |
APITimeoutError | APIConnectionError | No data arrived for the client's timeout while waiting for a response or reading its body. It has no cause. |
- Names: Each class sets
nameto its own name, such as"APIError". - Before a request: Invalid arguments and configuration fail with a
TypeErrororRangeErrorinstead, before any request is sent. - Cancellation: A canceled call rejects with its
AbortSignal's reason.
See Errors for what each status means.
APIError
- Name
status- Type
- number
- Description
The HTTP status code, such as
402when the account is out of credits.
- Name
type- Type
- string
- Description
The problem type URI, such as
"https://docs.synthient.com/errors/unauthorized", or"".
- Name
title- Type
- string
- Description
A short summary of the problem type, or
"".
- Name
detail- Type
- string
- Description
An explanation of this occurrence of the problem, or
"". For a plain-text error, such as the edge's 401 for a malformed key, it is the response body.
- Name
errors- Type
- Record<string, string[]>
- Description
Validation messages by request field, set on 422 responses. It is an empty object when the response has none.
- Name
requestID- Type
- string
- Description
Identifies the request when contacting Synthient support, or
"". It comes from theX-Request-IDheader, or the problem'sinstancewhen the header is absent.
- Name
retryAfter- Type
- number | undefined
- Description
Milliseconds the server asked the caller to wait before retrying, or
undefinedif it did not say.
The fields are read-only.
- Logging:
messagecontains only the HTTP status code and its standard description, such as402 Payment Required, so it is safe to log. - Untrusted fields: The other fields come from the server's problem details and can contain sensitive data, so inspect them explicitly.
APIError can be constructed, for example to simulate failures in tests.
constructor(init: APIErrorInit, options?: ErrorOptions);
- Input: Only
init.statusis required.optionstakes the standardErroroptions, such ascause.
APIErrorInit
- Name
status- Type
- number
- Description
Sets
APIError.status.
- Name
type?- Type
- string | undefined
- Description
Sets
APIError.type. Defaults to"".
- Name
title?- Type
- string | undefined
- Description
Sets
APIError.title. Defaults to"".
- Name
detail?- Type
- string | undefined
- Description
Sets
APIError.detail. Defaults to"".
- Name
errors?- Type
- Record<string, string[]> | undefined
- Description
Sets
APIError.errors. Defaults to an empty object.
- Name
requestID?- Type
- string | undefined
- Description
Sets
APIError.requestID. Defaults to"".
- Name
retryAfter?- Type
- number | undefined
- Description
Sets
APIError.retryAfter, in milliseconds.
Retries and timeouts
Which requests are retried:
- Account and feed requests (
account, the export methods, and stream handshakes) are retried on 500, 502, 503, and 504 responses, on 429 responses that carryRetry-After, on idle timeouts, and on connection failures with one of these codes:ECONNRESET,ECONNREFUSED,EPIPE,ETIMEDOUT,ENOTFOUND,EAI_AGAIN,ENETUNREACH,EHOSTUNREACH,UND_ERR_SOCKET, orUND_ERR_CONNECT_TIMEOUT. - An error thrown by a custom
fetchis retried the same way when it, or an error in itscausechain, has one of these codes as itscode. - A failure while reading the JSON body of an
account,listExports, orexportMetaresponse is retried the same way, within the same retry budget. - Other failures, such as a TLS certificate error, are not retried.
- Billed lookups (
lookupIP,lookupIPs, andlookupDomain) andhealthare retried only on 429 responses that carryRetry-After, which the rate limiter returns before doing any work, so a retried lookup is never charged twice. - A 429 without
Retry-Aftermeans the key's monthly request budget is spent or it has too many open streams, so it rejects without a retry. - Once a stream is open, and for downloads from the export storage host, nothing is retried.
How retries wait:
- Retries back off exponentially from one second, doubling up to 30 seconds, with 25% jitter.
- The wait is never shorter than
Retry-After. - A
Retry-Afterlonger than a minute rejects with the response'sAPIErrorinstead of retrying. - Aborting the call's
signalstops a wait. - Set the retry count with
maxRetries.
The client's timeout is an idle timeout, 60 seconds by default.
- It bounds each wait for response headers and for the next part of a body.
- It runs only while the client waits on the network, so a slow consumer does not trip it.
- An idle timeout fails the attempt with an
APITimeoutError.
The timeout does not limit a whole call. Bound a call, including its retries, with AbortSignal.timeout:
const info = await client.lookupIP("8.8.8.8", { signal: AbortSignal.timeout(10_000) });
console.log(info.ip);
When the signal fires, the call rejects with its reason, which for AbortSignal.timeout is a DOMException named "TimeoutError".