zeridion-flare (Ruby)

Ruby SDK for the Zeridion Flare managed background-jobs API.

Gem Version License: MIT

Full documentation at docs.zeridion.com/flare

Installation

gem install zeridion-flare

Or add to your Gemfile:

gem "zeridion-flare", "~> 0.2"

Requires Ruby 3.2+. Zero runtime dependencies — uses stdlib Net::HTTP, OpenSSL, and JSON only. The opt-in worker runtime (require "zeridion_flare/worker") ships in the same gem and adds no dependencies.

Quick start

require "zeridion_flare"

client = Zeridion::Flare::Client.new(api_key: "zf_live_sk_...")
# Or, with FLARE_API_KEY set in the environment:
#   client = Zeridion::Flare::Client.new

job = client.create_job(
  "job_type" => "SendWelcomeEmail",
  "payload"  => { "email" => "alice@example.com" },
  "queue"    => "default",
)

puts job["id"], job["state"]  # "job_abc123", "pending"

Client.new(...)

Keyword Default Notes
api_key: ENV["FLARE_API_KEY"] Required — pass directly or via env var
base_url: https://api.zeridion.com Override for dev / staging environments
max_retries: 3 Set 0 to disable retries
retry_base_delay_ms: 500 Base for exponential schedule
retry_max_delay_ms: 30_000 Cap on a single backoff wait (and on Retry-After)
timeout_seconds: 30 Per-request read timeout
transport: NetHttpTransport.new Swap for tests or to plug in a logging proxy

Every public method accepts optional idempotency_key: and request_id: keyword arguments, sent as the Idempotency-Key and X-Request-Id headers.

client.create_job(body, idempotency_key: "optional", request_id: "optional")
client.get_job(id)                                # nil if 404
client.list_jobs(state: "failed", limit: 25)
client.cancel_job(id)                             # nil if 409
client.retry_job(id)                              # nil if 409
client.register_worker(body)                      # raises on non-2xx
client.poll_workers(body)
client.heartbeat(body)                            # {"status" => "ok" | "cancel"}
client.ack_worker(body)

Automatic retries

The SDK auto-retries HTTP 429 / 502 / 503 / 504 responses and transient network errors with full-jitter exponential backoff capped at retry_max_delay_ms. The Retry-After header is honored when present.

See the stable error-code registry for every error.code string the API can return.

Error handling

All API errors inherit from Zeridion::Flare::FlareError:

begin
  client.create_job("job_type" => "MyJob")
rescue Zeridion::Flare::RateLimitError => e
  sleep(e.retry_after.to_i)        # honor X-RateLimit-Reset
rescue Zeridion::Flare::AuthError
  # 401 — invalid API key
rescue Zeridion::Flare::ConflictError => e
  case e.code
  when "idempotency_key_reuse"
    # Same Idempotency-Key reused with a different body
  end
rescue Zeridion::Flare::FlareError => e
  warn "Error #{e.status_code}: #{e.code}#{e.message} (req #{e.request_id})"
end

Each error exposes #status_code, #code, #request_id, and #message. RateLimitError additionally exposes #limit, #remaining, #retry_after.

Verifying webhook signatures

If you've configured outbound webhooks via the /flare/v1/webhooks API, verify the X-Zeridion-Signature header on each incoming delivery:

require "zeridion_flare"

post "/hooks/zeridion" do
  payload = request.body.read
  header  = request.env["HTTP_X_ZERIDION_SIGNATURE"].to_s

  unless Zeridion::Flare::Webhook.verify(payload, header, ENV["ZERIDION_WEBHOOK_SECRET"], tolerance_seconds: 300)
    halt 400, "invalid signature"
  end
  # ... process the event ...
  200
end

Webhook.verify is HMAC-SHA256 over <unix_timestamp>.<raw_body>, constant-time-compared (via OpenSSL.secure_compare) against every v1= value in the header (supports secret rotation). The optional tolerance_seconds: parameter rejects replays older than that many seconds.

Worker runtime

Beyond the thin client, the gem ships an opt-in background-worker runtime. Define job classes and let it register, long-poll, dispatch, heartbeat with progress, honor server cancellation, and drain gracefully on shutdown — instead of hand-writing the poll/ack loop.

require "zeridion_flare/worker"

class SendWelcomeEmail
  include Zeridion::Flare::Job
  flare_options queue: "email", max_attempts: 5, timeout: 120

  def perform(payload, ctx)
    return if ctx.cancelled?
    return if AlreadySent.exists?(ctx.job_id)   # at-least-once → idempotent
    Mailer.welcome(payload["email"]).deliver_now
    ctx.report_progress(1.0)
  end
end

# Recurring (cron) jobs are payloadless and run on a schedule:
class NightlyCleanup
  include Zeridion::Flare::RecurringJob
  flare_options cron: "0 3 * * *", queue: "maintenance", timezone: "UTC"

  def perform(ctx)
    PurgeExpired.run
  end
end

worker = Zeridion::Flare::Worker::Worker.new(concurrency: 5)
worker.run   # blocks; SIGTERM/SIGINT → graceful drain

Including the Job / RecurringJob mixin registers the class automatically. The worker reads the same FLARE_API_KEY fallback and base-URL default as the thin client. Handlers run at least once — dedupe on ctx.job_id to stay idempotent.

Full reference: docs.zeridion.com/flare/sdks/ruby/worker.

Sample app

A runnable worker starter (one payload job + one recurring job + graceful drain, plus an enqueue.rb feeder) lives at samples/ruby-starter/.