Ruby SDK
Connect your Ruby application to customer records, events, and prepared campaigns in maxclicks. The client groups API operations by resource and provides structured errors and pagination.
The RubyGems release is pending. Point your Gemfile at the source repository
below, or follow the HTTP quickstart.
Current API coverage
This guide documents the existing source client interface. The current REST API also exposes event readiness, event identity changes, record deletion status, and workflow history cursors that this client does not yet wrap. Event receipts and deletion results have changed; older static models may omit fields or expect the former { id, deleted } shape.
Use direct HTTP for those operations and verify the current coverage table before adopting a wrapper. Auto-pagination still stops at the API's traversal cap; an iterator or method named all does not guarantee a full workspace export.
Install
Add the gem to your Gemfile, then run bundle install.
# The RubyGems release is not published yet; point at the source repository.
gem "maxclicks", git: "https://github.com/maxclicks-ai/maxclicks-ruby.git"
Client
Create a Maxclicks::Client with an API key. The key falls back to ENV["MAXCLICKS_API_KEY"].
require "maxclicks"
client = Maxclicks::Client.new(api_key: "max_...")
# Upsert a contact. Custom attribute keys are allowed alongside built-in fields.
contact = client.records.upsert("students", {
"email" => "[email protected]",
"firstName" => "Ada",
"loyaltyPoints" => 42,
})
puts contact.id
puts contact["loyaltyPoints"] # custom keys via []
# Fire an event.
client.events.fire("purchase", { "eventId" => "ord_1001", "amount" => 79.0 })
# Send a stored template. Ids are opaque strings from templates.list or the app.
client.templates.send_template("cmb3k1x2p0000s601abcdefgh",
data: { "contact" => { "email" => "[email protected]" } })
Options
- Name
api_key- Type
- string
- Description
Bearer key. Defaults to
ENV["MAXCLICKS_API_KEY"]. Required for every endpoint except the public form and email endpoints.
- Name
base_url- Type
- string
- Description
Defaults to
https://api.maxclicks.ai/v1. Stage ishttps://api-stage.maxclicks.ai/v1.
- Name
timeout- Type
- integer
- Description
Per-request timeout in seconds. Default 60.
- Name
max_retries- Type
- integer
- Description
Extra attempts on retryable failures. Default 2.
- Name
default_headers- Type
- hash
- Description
Headers merged into every request.
- Name
on_warning- Type
- proc
- Description
Callback
->(warnings, context)invoked when the API returns non-fatal warnings.
- Name
transport- Type
- object
- Description
Any object responding to
call(Maxclicks::HTTP::Request) -> Maxclicks::HTTP::Response. The seam for testing without a network or plugging in a different HTTP stack.
The public endpoints forms.submit, forms.confirm_double_opt_in, and emails.unsubscribe_one_click send no Authorization header.
Resources
Endpoints are grouped by resource on the client. send_template and send_broadcast carry a suffix so they do not shadow Ruby's Object#send; broadcasts.send is kept as an alias.
| Resource | Methods |
|---|---|
client.me | Current key context |
client.schemas | list get |
client.attributes | list |
client.records | create upsert list get update delete audit_trail |
client.suppressions | list create batch_create batch_delete delete |
client.events | list fire fire_batch |
client.domains | list get |
client.senders | list |
client.topics | list get |
client.segments | list create get delete count list_contacts |
client.broadcasts | list get update send_broadcast list_runs metrics |
client.templates | list get send_template |
client.webhooks | create list get update delete rotate_secret |
client.workflows | list get trigger pause unpause list_runs get_run |
client.forms | submit confirm_double_opt_in |
client.emails | unsubscribe_one_click |
Create schemas, attributes, domains, senders, topics, templates and broadcasts in the maxclicks app, then use the supported API operations to read them or act on prepared work. Manage API keys and CSV imports in the app too. The client's method list and the current API reference define what can be changed or triggered from code; use direct HTTP when a newer operation is not wrapped by this client.
Pagination
List endpoints return a Maxclicks::Page. Use .data for the current page, or any Enumerable method to walk every page automatically. Auto-pagination stops at has_more == false or the offset cap of 10,000.
page = client.records.list("students", limit: 100)
page.data # this page's records
page.pagination # #<Maxclicks::Pagination limit offset total_count has_more>
page.warnings
# Walk every page:
client.records.list("students").each { |record| puts record.id }
all = client.records.list("students").to_a
first_ten = client.records.list("students").first(10)
# Manual page walking:
page = page.next_page if page.next_page?
events.list is cursor-paginated and returns a Maxclicks::CursorPage. It exposes the same interface but advances by an opaque next_cursor with no total count or offset cap.
client.events.list(schema: "purchase", from: "2026-07-01T00:00:00Z").each do |event|
puts event.id
end
page = client.events.list(schema: "purchase", limit: 100)
page.pagination.next_cursor # opaque cursor, or nil on the last page
Errors
Every failure raises a subclass of Maxclicks::Error. Each error carries status, code (nullable), type, headers, raw (the parsed body), and issues (validation problems on multi-problem gates, else nil).
begin
client.records.get("students", "missing")
rescue Maxclicks::NotFoundError => e
puts e.status # 404
puts e.code # e.g. "record_not_found"
rescue Maxclicks::RateLimitError => e
sleep(e.retry_after || 1) # seconds parsed from Retry-After
rescue Maxclicks::Error => e
# any SDK failure
end
| Class | When |
|---|---|
Maxclicks::ConfigurationError | Programmer error (e.g. missing API key), raised before any request |
Maxclicks::BadRequestError | 400 |
Maxclicks::AuthenticationError | 401 |
Maxclicks::PaymentRequiredError | 402 (insufficient_credits, email_limit_reached) |
Maxclicks::PermissionError | 403 |
Maxclicks::NotFoundError | 404 |
Maxclicks::ConflictError | 409 |
Maxclicks::PayloadTooLargeError | 413 |
Maxclicks::UnsupportedMediaTypeError | 415 |
Maxclicks::UnprocessableEntityError | 422 |
Maxclicks::RateLimitError | 429 (carries retry_after) |
Maxclicks::ServerError | 5xx |
Maxclicks::ConnectionError | Transport failure (DNS, TCP, TLS, reset) |
Maxclicks::TimeoutError | Request exceeded the timeout (subclass of ConnectionError) |
The SDK retries reads on 429, any 5xx, and transport or timeout errors, up to max_retries extra attempts with exponential backoff and full jitter (base ~0.5s, cap ~8s). A write is retried on those only when it carries an idempotency_key:; without one, a timed-out write is raised to you rather than repeated, because it may already have landed. The source client retries every call on 429 within its retry budget. Check the error code: event receipt or identity capacity failures can need reconciliation instead of more automatic retries. A Retry-After header sets the delay floor. The API enforces roughly 100 read requests per second and 25 write requests per second per key. AI-backed writes (segments.create and webhooks.create/webhooks.update with a custom-filter condition) are limited to 10 requests per minute.
Idempotency
Mutating writes accept an optional idempotency_key:. Pass a unique string and the API de-duplicates retried requests: a replay returns the original result.
client.records.upsert("students",
{ "email" => "[email protected]", "firstName" => "Ada" },
idempotency_key: "signup-2026-07-18-ada")
This source client exposes a key on seven writes: records.create, records.upsert, suppressions.batch_create, suppressions.batch_delete, templates.send_template, broadcasts.send_broadcast, and workflows.trigger. Other methods do not expose a key argument. Use direct HTTP for the additional supported API writes; do not assume every update or deletion is safe to replay.
Endpoint notes
events.fire_batch: a422is a normal partial-failure result, returned as aMaxclicks::BatchFireResultwithresults,summary, and (onon_error: "abort")failed_index. It is not raised.broadcasts.send_broadcast: a422 broadcast_invalidis raised asMaxclicks::UnprocessableEntityError, withissueslisting what is missing.sendis an alias.templates.send_templateandworkflows.trigger: returnnil. Warnings arrive via theon_warningcallback.forms.confirm_double_opt_inandemails.unsubscribe_one_click: return aMaxclicks::RedirectResultwithstatusandredirect_url.
Type signatures
Full RBS signatures ship in sig/ and are packaged with the gem, so steep and rbs can type-check code that uses the SDK. Validate them with rbs -I sig validate.
Retry outcomes
A repeated write can return a recorded result, including an error. If the API reports idempotency_outcome_unknown, inspect the affected resource or run history before creating a new operation. Keep the original key and payload while you reconcile. See the idempotency guide for supported operations and retention.