Python SDK
Keep customer data and events in sync from your Python service. The client provides resource methods, structured errors, and pagination helpers for the maxclicks Public API.
- Zero required runtime dependencies (pure standard library
urllib). - Fully type-hinted, ships a
py.typedmarker. - Python 3.9+.
The PyPI release is pending. Install from the source repository below, or follow the HTTP quickstart to connect without a client library.
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
The PyPI release is not published yet. Until it is, install from the source repository:
pip install git+https://github.com/maxclicks-ai/maxclicks-python.git
Initialize
Pass your API key, or set MAXCLICKS_API_KEY and construct with no arguments.
from maxclicks import Maxclicks
client = Maxclicks("max_...") # or set MAXCLICKS_API_KEY
me = client.me()
print(me["space"])
client.me() identifies the calling key, its owner, the bound space, and role.
Configuration
Construct the client with any of these options.
| Option | Default | Notes |
|---|---|---|
api_key | MAXCLICKS_API_KEY env | Bearer key. Required for all but public endpoints. |
base_url | https://api.maxclicks.ai/v1 | Use maxclicks.STAGE_BASE_URL for staging. |
timeout | 60.0 | Per-request timeout in seconds. |
max_retries | 2 | Extra attempts on 429, plus 5xx and transport/timeout errors for reads and keyed writes. |
default_headers | {} | Merged into every request. |
on_warning | None | (warnings: list[str], ctx) callback. |
Response shape
Responses are the unwrapped payload of the API's { "data": ... } envelope, returned as plain dict and list values. Wire keys are preserved verbatim in camelCase, for example record["firstName"] and schema["namePlural"], because record and event payloads carry user-defined custom-attribute keys. SDK method names and keyword arguments use snake_case.
Example
Upsert a contact, iterate every record, fire an event, and send a template.
contact = client.records.upsert("students", {"email": "[email protected]", "firstName": "Ada"})
print(contact["id"])
for record in client.records.list("students"):
print(record["id"], record.get("email"))
records.upsert creates or updates by identity (contact: id/userId/email/phone; object: id/externalId). records.create never updates an existing record and returns 409 on conflict.
Template, broadcast, segment and webhook ids are opaque strings the API returns; do not assume a uuid or a fixed prefix. Schemas take either a slug ("students") or an id, and workflows.trigger takes the reference id of the workflow's incoming-webhook step.
Pagination
List methods return a Page that holds the first page and iterates lazily across every following page. Iteration stops when hasMore is false or the offset cap of 10,000 is reached.
page = client.records.list("students", limit=100)
page.data # this page's items
page.pagination # {"limit", "offset", "totalCount", "hasMore"}
page.warnings # warnings for this page
for record in page: # every item across all pages, auto-fetching
...
everything = client.records.list("students").all() # collect into a list
events.list is cursor-paginated and returns a CursorPage with the same interface. Its pagination is {"limit", "nextCursor", "hasMore"}.
for event in client.events.list("purchase_completed", limit=200):
print(event["id"])
Errors
Every failure raises a typed exception. All API errors carry status, code, type, message, headers, and the raw body.
| Exception | Status |
|---|---|
MaxclicksBadRequestError | 400 |
MaxclicksAuthenticationError | 401 |
MaxclicksPermissionError | 403 |
MaxclicksNotFoundError | 404 |
MaxclicksConflictError | 409 |
MaxclicksUnprocessableEntityError | 422 |
MaxclicksRateLimitError | 429 (has .retry_after_seconds) |
MaxclicksServerError | 5xx |
MaxclicksConnectionError | transport failure |
MaxclicksTimeoutError | timeout |
MaxclicksConfigurationError | programmer error, raised before any request |
MaxclicksError is the base class for all of them.
from maxclicks import MaxclicksNotFoundError, MaxclicksRateLimitError, MaxclicksError
try:
client.records.get("students", "missing-id")
except MaxclicksNotFoundError as error:
print(error.status, error.code, error.message)
except MaxclicksRateLimitError as error:
print("retry after", error.retry_after_seconds, "seconds")
except MaxclicksError as error:
print("something went wrong:", error)
Reads are retried on 429, any 5xx, and transport/timeout errors, up to max_retries extra attempts, using exponential backoff with full jitter (base ~0.5s, cap ~8s). A write is retried on those only when you pass idempotency_key; without one, a timed-out write is reported to you rather than repeated, because it may already have landed. The client also retries 429 responses according to its configured retry budget. A Retry-After header sets the delay floor.
Idempotency
Write endpoints that support it accept an idempotency_key. Retrying with the same key returns the original result instead of acting twice.
client.records.upsert(
"students",
{"email": "[email protected]", "firstName": "Ada"},
idempotency_key="signup-2026-07-18-ada",
)
This source client exposes a key on seven operations: records.create, records.upsert, suppressions.batch_create, suppressions.batch_delete, templates.send, broadcasts.send, 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.
Warnings
The API may attach warnings to a response, in the body's warnings array or, for empty-body endpoints like workflows.trigger, in the URL-encoded maxclicks-Warning-Message response header. Provide on_warning to receive them.
client = Maxclicks(on_warning=lambda warnings, ctx: print(ctx.method, ctx.path, warnings))
Rate limits
Per API key: 100 requests/second for reads and 25 requests/second for writes. Exceeding a limit returns 429, which the SDK retries with the server's Retry-After delay.
Resource surface
The v1 API is 55 operations across 16 groups, reachable as me, schemas, attributes, records, suppressions, events, domains, senders, topics, segments, broadcasts, templates, webhooks, workflows, forms, and emails.
| Resource | Methods |
|---|---|
client.me() | Current key context (a method on the client itself) |
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 | fire fire_batch list |
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 list_runs get_metrics |
client.templates | list get send |
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.
Set the entity up once in the app, then trigger, read and measure it from Python: build a broadcast in the app and call broadcasts.send, design a template in the app and call templates.send, publish a workflow in the app and call workflows.trigger.
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.