Errors and retries

View as markdown

How failed requests are reported, and which requests are retried.

Errors

A request that fails with a non-success HTTP status returns an *APIError. Inspect it with errors.AsType:

_, err := client.LookupIP(ctx, netip.MustParseAddr("8.8.8.8"))
apiErr, ok := errors.AsType[*synthient.APIError](err)
if ok {
	switch {
	case apiErr.StatusCode == http.StatusPaymentRequired:
		fmt.Println("out of lookup credits")
	case apiErr.StatusCode == http.StatusTooManyRequests && apiErr.RetryAfter > 0:
		fmt.Println("rate limited, retry after", apiErr.RetryAfter)
	case apiErr.StatusCode == http.StatusTooManyRequests:
		fmt.Println("monthly request budget spent")
	default:
		fmt.Println("request", apiErr.RequestID, "failed:", cmp.Or(apiErr.Detail, apiErr.Title))
	}
}
  • *APIError: The API, or the storage host during Download, returned a non-success status.
  • ErrMissingAPIKey: NewClient was given an empty or all-whitespace key.
  • Context errors: A canceled or expired context returns an error that matches context.Canceled or context.DeadlineExceeded with errors.Is.
  • Transport errors: A network failure on an API request is returned as the *url.Error the HTTP client reported. Download removes that wrapper from storage failures to keep the presigned URL out of the message, and a failure while reading a response is wrapped with a synthient: prefix.
  • Other errors: An invalid argument, such as an invalid address, more than 1000 addresses, or a malformed export ID, is rejected before any request is sent, and a response that can't be decoded also fails. These are plain errors whose messages start with synthient:.

See Errors for what each status means.

APIError

  • Name
    StatusCode
    Type
    int
    Description

    The HTTP status code of the response.

  • Name
    Type
    Type
    string
    Description

    A URI identifying the problem type, for example "https://docs.synthient.com/errors/unauthorized".

  • Name
    Title
    Type
    string
    Description

    A short summary of the problem type.

  • Name
    Detail
    Type
    string
    Description

    Explains this occurrence of the problem. For a plain-text error, such as the edge's 401 for a malformed key, it is the response body.

  • Name
    Errors
    Type
    map[string][]string
    Description

    Maps request fields to validation messages.

  • Name
    RequestID
    Type
    string
    Description

    Identifies the request when contacting Synthient support. It comes from the X-Request-ID header, or the problem's instance when the header is absent.

  • Name
    RetryAfter
    Type
    time.Duration
    Description

    How long the server asked the client to wait before retrying, or zero if it did not say.

  • Logging: Error() returns only the HTTP status code and its standard description, such as synthient: 402 Payment Required, so it is safe to log.
  • Untrusted fields: Title, Detail, Errors, and RequestID contain untrusted server data and may include sensitive information, so inspect them explicitly.
  • Unwrapping: Unwrap() returns the error from reading the response body, if any, which preserves errors.Is checks for cancellation and deadlines.

Retries and timeouts

Which requests are retried:

  • Account and feed requests are retried on 500, 502, 503, and 504 responses, on transport errors other than TLS certificate failures and HTTP responses to HTTPS requests, and on 429 responses that carry Retry-After. A failure while reading a response body is not retried, and neither is the request Download makes to the storage host for the file itself.
  • Billed lookups (LookupIP, LookupIPs, and LookupDomain) are retried only on 429 responses that carry Retry-After, which the rate limiter returns before doing any work, so a lookup is never charged twice.
  • Health is retried the same way as billed lookups, so an unhealthy API is reported at once.
  • A 429 without Retry-After means the key's monthly request budget is spent or it has too many open streams, so it is returned without a retry.

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-After longer than a minute, or a wait that would pass the context deadline, returns the response instead of retrying.
  • Waits stop when the context is done.
  • The client retries a failed request up to twice by default. Set the count with WithMaxRetries, where 0 disables retries.

The client sets no overall request timeout, because streams and downloads are long-lived. Bound calls with a context deadline instead:

ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()

info, err := client.LookupIP(ctx, netip.MustParseAddr("8.8.8.8"))
if err != nil {
	log.Fatal(err)
}
fmt.Println(info.IP)

The default HTTP client waits at most one minute for response headers.