Zazu Ruby SDK

Ruby SDK for the Zazu API. Faraday + HTTPX adapter for HTTP/2 + persistent connections.

gem "zazu-ruby"

The gem is published as zazu-ruby on RubyGems but loaded as zazu in code (the zazu name was already taken by an unrelated 2014-era gem).

Quick start

require "zazu"

zazu = Zazu.new(api_key: ENV["ZAZU_API_KEY"])
# Or with explicit base URL (defaults to https://zazu.ma):
zazu = Zazu.new(api_key: ENV["ZAZU_API_KEY"], base_url: "https://zazu.africa")

entity = zazu.entity.get
# => #<Zazu::Response status=200 ...>
entity.body["name"]
# => "Acme Corp"

Environment variables ZAZU_API_KEY, ZAZU_BASE_URL, ZAZU_API_VERSION, and ZAZU_TIMEOUT are read by default.

Resources

zazu.entity.get

zazu.accounts.list(currency_code: "MAD", limit: 50)
zazu.accounts.get("019dde7d-...")
zazu.accounts.list_transactions("019dde7d-...", operation: "credit")
zazu.accounts.get_transaction("019dde7d-...", "01a0e1...")

zazu.customers.list(q: "acme")
zazu.customers.get("01a0...")
zazu.customers.create(
  customer_type: "business",
  company_name: "Acme Corp",
  email: "billing@acme.com",
  ice_number: "000000000000000"
)
zazu.customers.update("01a0...", email: "new@example.com")
zazu.customers.delete("01a0...")

zazu.invoices.list(status: "sent", limit: 50)
zazu.invoices.create(
  customer_id: "01a0...",
  currency_code: "MAD",
  issue_date: "2026-05-03",
  due_date: "2026-06-03",
  items: [{ description: "Consulting", quantity: 10, unit_price: "150.00" }]
)
zazu.invoices.send_invoice("01a0...")
zazu.invoices.mark_as_paid("01a0...")
zazu.invoices.cancel("01a0...")
zazu.invoices.credit_note("01a0...")
zazu.invoices.create_payment_link("01a0...", account_id: "019dde7d-...")

zazu.payment_links.list(status: "active")
zazu.payment_links.create(
  account_id: "019dde7d-...",
  amount: "1500.00",
  description: "March consulting",
  link_type: "single"
)
zazu.payment_links.cancel("01a0...")

zazu.webhook_endpoints.list
zazu.webhook_endpoints.create(
  url: "https://example.com/webhooks/zazu",
  events: ["invoice.sent", "payment_link.paid"]
)
zazu.webhook_endpoints.test_endpoint("01a0...")
zazu.webhook_endpoints.regenerate_secret("01a0...")
zazu.webhook_endpoints.enable("01a0...")
zazu.webhook_endpoints.disable("01a0...")

Pagination

Every list endpoint returns a Zazu::Page. The SDK enforces a hard cap of 100 records per page — there is no auto-pagination across pages.

page = zazu.invoices.list(limit: 100)
page.data         # => Array of invoice hashes
page.has_more     # => true / false
page.next_cursor  # => string or nil

# Walk pages explicitly:
while page
  page.data.each { |inv| process(inv) }
  page = page.next  # returns nil when has_more is false
end

For capped iteration, use the underlying each_page_record helper on a resource (private; access via send if you need it). The deliberate restriction is a guardrail — accidentally pulling 50,000 records in a single SDK call should be impossible without explicit per-page consent.

Errors

Every non-2xx response raises a subclass of Zazu::Error:

Status Class
401 Zazu::AuthenticationError
403 Zazu::ForbiddenError
404 Zazu::NotFoundError
422 Zazu::ValidationError
429 Zazu::RateLimitError (carries #retry_after)
5xx Zazu::ServerError
network Zazu::ConnectionError

Each error exposes #status, #request_id, #type, #param, and the raw #body.

begin
  zazu.invoices.get("does-not-exist")
rescue Zazu::NotFoundError => e
  e.status      # => 404
  e.request_id  # => "req_..."
  e.type        # => "not_found_error"
end

Versioning the API contract

zazu = Zazu.new(api_key: "...", api_version: "2026-03-27")

Or via env: ZAZU_API_VERSION=2026-03-27. The header is sent on every request; the API echoes it back in Zazu-Version.

Development

bundle install
bundle exec rspec
bundle exec rubocop

The spec suite is VCR-backed — cassettes live in spec/fixtures/cassettes/ and are committed to the repo.

To re-record cassettes against staging:

cp .env.example .env
# fill in ZAZU_STAGING_API_KEY and the ZAZU_FIXTURE_*_ID values
bundle exec rake fixtures:record

Cassettes are scrubbed before write — bearer tokens and request IDs are rewritten to placeholders. Even if a real key is in .env, the committed cassette never contains it.

Cassettes for other-language SDKs

Each release of zazu-ruby publishes the cassette directory as a tarball release asset:

https://github.com/getzazu/zazu-ruby/releases/download/v0.1.0/cassettes-v0.1.0.tar.gz

zazu-go, zazu-python, etc. pin a specific version in their .zazu-fixtures file and download the tarball during CI. This guarantees every SDK is tested against the same recorded API interactions, surfacing cross-SDK inconsistencies immediately.

VCR's YAML format is supported natively by:

  • Ruby — VCR (this gem)
  • Go — go-vcr
  • Python — VCR.py
  • PHP — PHP-VCR
  • Crystal — vcr-crystal / hi8.cr
  • Rust — http_replayer (or a small custom YAML reader)
  • JavaScript / TypeScript — Talkback or polly.js (slight format adapter needed)

License

MIT