API conventions

Routes in this section are relative to /v1/workspaces/{workspaceId}. Use Content-Type: application/json for JSON writes. Names use camelCase; unknown or repeated fields and query parameters are rejected.

Responses and dates

A resource returns {data, requestId}. A list returns {data: [], nextCursor, requestId}. Errors use application/problem+json and a stable code; also retain X-Request-Id for diagnosis.

Instants are UTC strings ending in Z, such as 2026-10-18T15:30:00.123456Z, with at most microsecond precision. Precision that persistence would truncate is rejected. Civil dates such as birthdays use YYYY-MM-DD. Profile timezones use an IANA name or UTC.

Pagination and filters

limit accepts integers from 1 to 100 and defaults to 50. Pass nextCursor unchanged as cursor, keeping the resource, workspace and filters. Stop when it is null; there are no page numbers.

Contacts accepts email, emailSubscriptionStatus and archived; emails accepts purpose, contactId and submissionStatus. Resources supporting archive show active entries by default. Analytics has its own filter contract without cursors.

Idempotency for mutations

Send Idempotency-Key on every mutation; it must be nonempty and at most 128 characters. Generate one per logical operation and retain the exact body and precondition when retrying a request whose response was lost.

The same identity, operation and key returns the stored result when input matches. Changing the body or revision returns 409 idempotency_conflict. A new operation needs a new key. Current authorization is checked even for replays.

Revision checks for edits

Read the resource and retain its ETag. Send it unchanged in If-Match for contact PATCH and commands requiring a revision, such as saving fields or audiences and archiving/restoring resources.

If-Match: "contact-CONTACT_ID-r3"
Idempotency-Key: profile-update-unique-operation

Contacts uses an ETag bound to its ID; other revisioned resources use "rN". Missing preconditions return 428; stale revisions return 412. Read again, reconcile changes and create another operation. Upsert by email does not require a revision and applies provided fields in commit order.

Asynchronous work and capacity

A 202 confirms admission, not completion. Use the ID and, when included, Location to inspect status. Email reserves capacity in its original product and UTC month; transport acceptance consumes it, known rejection releases it and uncertainty retains the reservation. See errors and usage.