Error handling
Error classes, retry semantics, and idempotency.
Every non-2xx response from the API turns into a Ruby exception. All
exceptions inherit from EasyLabs::Error, so a single rescue EasyLabs::Error catches everything; subclasses let you handle specific
failures explicitly.
begin
client.subscriptions.cancel("sub_xyz")
rescue EasyLabs::RateLimitError => e
sleep(e.retry_after_seconds || 1)
retry
rescue EasyLabs::AuthenticationError
# api key was rejected — bail out
rescue EasyLabs::Error => e
Rails.logger.error("[easy] #{e.status} #{e.code}: #{e.message}")
raise
endEvery instance exposes:
| Attribute | Type | Meaning |
|---|---|---|
#status | Integer | HTTP status of the response. |
#code | String, nil | Machine-readable code from the structured error envelope. |
#details | Object, nil | details payload from the envelope, when present. |
#retry_after_seconds | Integer, nil | Parsed from the Retry-After header (rate-limit only). |
#raw | Object, nil | Full parsed JSON body, for fields the SDK doesn't surface. |
Retryable vs. non-retryable
The SDK maps HTTP status codes to specific subclasses so you can branch on the exception class instead of inspecting status numbers:
| Status | Class | Retryable? |
|---|---|---|
| 400, 422 | EasyLabs::InvalidRequestError | No — fix the request. |
| 401 | EasyLabs::AuthenticationError | No — rotate the key. |
| 403 | EasyLabs::PermissionError | No. |
| 404 | EasyLabs::NotFoundError | No. |
| 409 | EasyLabs::ConflictError | Sometimes — re-read state and decide. |
| 429 | EasyLabs::RateLimitError | Yes — sleep retry_after_seconds, retry. |
| 5xx | EasyLabs::ServerError | Yes — exponential backoff. |
| anything else | EasyLabs::Error | Inspect #status. |
The webhook verifier raises EasyLabs::InvalidRequestError with a
specific #code (WEBHOOK_SIGNATURE_MISSING,
WEBHOOK_SIGNATURE_FORMAT_INVALID, WEBHOOK_SIGNATURE_MISMATCH,
WEBHOOK_BODY_INVALID_JSON) so signature failures are distinguishable
from API 400s.
Idempotency keys
The underlying HTTP layer accepts an Idempotency-Key header on every
request. Resource methods do not yet expose a public keyword for it —
For now, the SDK never retries internally — every method makes exactly one HTTP call. Implement idempotency in your own retry wrapper using a stable key per logical operation.
Network errors
Underlying network failures (DNS resolution, connection refused, TLS
handshake, read timeouts) propagate as the original Net::HTTP /
OpenSSL::SSL / SocketError exceptions — they are not wrapped in
EasyLabs::Error. Rescue them at the boundary if you need to:
begin
client.transfers.create(amount: 1000, currency: "USD", source: "pi_…")
rescue EasyLabs::Error => e
# API responded with a non-2xx
rescue Net::OpenTimeout, Net::ReadTimeout, SocketError => e
# never reached the API
endThe default open + read timeout is 30 seconds.