Fields are snake_case, and typos are loud
Requests and responses usesnake_case. Unknown request fields are rejected,
not ignored — a camelCase body fails with 400 VALIDATION_ERROR rather than
silently doing nothing.
Every collection pages the same way
List endpoints return one envelope:next_cursor back as cursor to get the next page. null means you’re
on the last one. limit defaults sensibly and caps at 100.
Slow work returns a Job
Anything that can’t finish inside a request returns202 with a Job instead of
making you wait: sourcing leads, enrichment, generating copy, buying domains,
provisioning mailboxes, chat messages.
Poll GET /jobs/{job_id} honoring next_poll_at, or
subscribe to job.* webhooks and skip polling entirely.
The Job object
Statuses, polling, cancelling, credits, and needs_input.
Destructive calls preview first
Operations that delete things or spend money return a preview of what would happen unless you passconfirm=true. Nothing is destroyed and no card is
charged on the preview call.
confirm=true applies to deleting a list,
project, or
campaign, removing a
campaign sender, clearing an
exclusion list or a
template, and
buying domains, where the preview
comes back priced.
Some operations use a dry_run body flag for the same idea:
launching a campaign with
{"dry_run": true} reports exactly the gates a real launch would check, and
removing or stopping a person reports what it would affect.
Retries are safe if you ask for it
AnyPOST may carry an Idempotency-Key header. Replaying the same key with
the same body returns the original result instead of doing the work twice.
Use one key per logical operation — a UUID generated when your job starts, not
per HTTP attempt.
Errors have one shape
error is for humans, code is for your switch statement. Branch on code —
the message text can change.
handoff appears when a person can fix the problem in the Origami dashboard but
your code can’t: connecting a mailbox, re-authorizing one that expired, adding a
payment method, upgrading a plan, or confirming a card charge. Forward the URL
to whoever owns the account rather than treating it as a hard failure.
Scoping to a project
Keys are parent-wide. Sendx-origami-project: <project_id> to act inside a
child project instead of the parent:
Rate limits
300 requests/minute per IP and 100/minute per organization, both keyed to the parent org even for project-scoped requests. The binding constraint for agent work is usually concurrent runs, not requests — exceeding that returns429 CONCURRENT_LIMIT_EXCEEDED.
Check where you stand at any time with
GET /account/rate-limits. Full
details, including response headers, are in
authentication.