Errors and retries
How failed requests are reported, and which requests are retried.
Errors
Every error the SDK raises for a failed request is a Synthient::Error.
| Class | Superclass | Raised when |
|---|---|---|
Synthient::Error | StandardError | The base class. It is also raised directly when an export listing repeats a cursor or an export download hits a redirect it cannot follow. |
Synthient::ConnectionError | Synthient::Error | The request could not be sent, or the response could not be read. The underlying exception is available as cause. |
Synthient::TimeoutError | Synthient::ConnectionError | Connecting, sending, or waiting for response data took longer than the client's timeout. |
Synthient::DecodeError | Synthient::Error | A successful response did not match the API schema. |
Synthient::APIError | Synthient::Error | The API, or export storage, answered with an error status. |
begin
client.lookup_ip("8.8.8.8")
rescue Synthient::APIError => e
case e.status
when 402
puts "out of lookup credits"
when 429
puts e.retry_after ? "rate limited, retry after #{e.retry_after}s" : "monthly request budget spent"
else
puts "request #{e.request_id} failed: #{e.detail || e.title}"
end
end
- Before a request: Invalid arguments raise
ArgumentErrororTypeErrorinstead, before any request is sent. - Other errors: Requests on a closed client raise
IOError, and exceptions raised by aresponse_hookpropagate unchanged. Neither is aSynthient::Error.
See Errors for what each status means.
APIError
- Name
status- Type
- Integer
- Description
The HTTP status code of the response.
- Name
type- Type
- String?
- Description
A URI identifying the problem type.
- Name
title- Type
- String?
- Description
A short summary of the problem.
- Name
detail- Type
- String?
- Description
Explains this occurrence of the problem. For a
text/plainerror, such as the edge's 401 for a malformed key, it is the response body.
- Name
errors- Type
- Hash[String, Array[String]]
- Description
Validation messages by request field, from a 422. It is empty when the response has none.
- Name
request_id- Type
- String?
- Description
Identifies the request when contacting Synthient support. It comes from the
X-Request-Idheader, or the problem'sinstancewhen the header is absent.
- Name
retry_after- Type
- Numeric?
- Description
Seconds the server asked the client to wait before retrying, or
nilif it did not say.
- Logging: The message holds only the status code and its standard description, such as
"429 Too Many Requests", so it is safe to log. - Problem details: The problem details the API sent are available only through the attributes above.
- Name
Synthient::APIError::STATUS_TEXT- Type
- Hash[Integer, String]
- Description
The standard descriptions used in messages, by status code. A status without one, such as
418, is shown as the bare number.
APIError.new builds an error, for example to stub the client in tests:
def initialize: (status: Integer, ?type: String?, ?title: String?, ?detail: String?,
?errors: Hash[String, Array[String]], ?request_id: String?, ?retry_after: Numeric?) -> void
Retries and timeouts
Which requests are retried:
- Billed lookups (
lookup_ip,lookup_ips, andlookup_domain) 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. They are never resent after a network failure, because the API may already have done the work. - Account and feed requests are retried on 500, 502, 503, and 504 responses, on network failures and timeouts before a response arrives (except TLS errors), and on 429 responses that carry
Retry-After. - A 429 without
Retry-Aftermeans the key's monthly request budget is spent or it has too many open streams, so it is raised without a retry. - Downloads from the export storage host are never retried, and neither is a failure while reading a stream or a download.
Net::HTTP's own retry is disabled, so a stale keep-alive connection never replays a billed lookup.
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 60 seconds raises the error instead of retrying. - Set the retry count with
max_retries:.
open_timeout: bounds each wait for a connection and timeout: bounds each read or write. Exceeding either raises Synthient::TimeoutError. The client sets no overall deadline, because streams and downloads are long-lived. A quiet stream receives only a blank keepalive line about every 15 seconds, so keep timeout: above that.
client = Synthient::Client.new(timeout: 30, open_timeout: 5)
begin
puts client.lookup_ip("8.8.8.8").ip
rescue Synthient::TimeoutError
warn "Synthient did not answer in time"
ensure
client.close
end