Easy Labs
SDKsRuby

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
end

Every instance exposes:

AttributeTypeMeaning
#statusIntegerHTTP status of the response.
#codeString, nilMachine-readable code from the structured error envelope.
#detailsObject, nildetails payload from the envelope, when present.
#retry_after_secondsInteger, nilParsed from the Retry-After header (rate-limit only).
#rawObject, nilFull 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:

StatusClassRetryable?
400, 422EasyLabs::InvalidRequestErrorNo — fix the request.
401EasyLabs::AuthenticationErrorNo — rotate the key.
403EasyLabs::PermissionErrorNo.
404EasyLabs::NotFoundErrorNo.
409EasyLabs::ConflictErrorSometimes — re-read state and decide.
429EasyLabs::RateLimitErrorYes — sleep retry_after_seconds, retry.
5xxEasyLabs::ServerErrorYes — exponential backoff.
anything elseEasyLabs::ErrorInspect #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
end

The default open + read timeout is 30 seconds.

On this page