{object} is whatever GET /v1/objects returned — leads, or
object:property (URL-encoded as object%3Aproperty).
Read the object model first. The field names, types and which ones
are required all come from the object’s schema, and they differ per tenant.
Listing and paging
next_cursor back:
next_cursor is null you have reached the end. That is the only signal
you need — do not compare counts.
limit is 1–100 and defaults to 25. Out-of-range values are clamped rather
than rejected.
Treat the cursor as opaque
It is a base64 blob and its contents are not part of the contract. Do not parse it, increment it, or build one yourself. The reason it exists in this form: paging is anchored to a record, not to a position in the list. So if records are created or deleted while you are paging, you still will not see the same record twice or miss one. Offset paging (?page=2)
cannot promise that — one insert shifts every row down and the last row of page 1
silently reappears at the top of page 2.
If you are syncing data, that guarantee is the difference between a clean sync and
duplicated records.
Creating
Idempotency-Key is required. Send a unique value per logical create and
reuse it on retries: if the first attempt actually succeeded but the response
never reached you, retrying with the same key returns the original record instead
of creating a second one. Omitting the header returns 428.
Built-in fields and custom fields are sent the same way, flat in the body. You do
not need to know which is which.
Updating
PATCH is partial: omit what you are not changing.
Custom fields are merged, not replaced — patching one custom field leaves the
others intact.
Validation
A bad payload returns422, and the response names every field that is wrong:
- Unknown fields are rejected, not ignored. A typo fails loudly instead of silently dropping your data.
id,created_atandupdated_atare read-only. Sending them is an error rather than a no-op, so you never believe you set an id you did not.requiredis per tenant. A field that is optional for one customer can be mandatory for another. Read the schema; do not assume.- Numeric strings (
"42") are accepted for number fields."yes"is not a boolean;trueand"true"are.
Creating many at once
{ "records": [...] }.
Idempotency-Key is required, as it is for a single create.
Partial success is normal — always read failed
200 even when some items failed. One bad row in a nightly
import should not throw away the other 99 — but it does mean the HTTP status
alone no longer tells you whether everything was written. failed is always
present (empty when nothing failed), so check it every time.
index is the position in the array you sent. It is the only way to find the
offending row in your own data, since a record that failed to be created has no id.
What fails the whole request instead
These return a 4xx and write nothing:Duplicates within a batch
If the workspace has duplicate checking configured, items that duplicate an earlier item in the same batch on the configured fields are reported asDUPLICATE_IN_BATCH instead of being written. The first occurrence wins, and the
message tells you which index it collided with.
This only looks within the request. It is not a check against records already
in the CRM.
Retrying
Reuse the sameIdempotency-Key when you retry a batch that timed out. The
replay returns without creating anything new.
If some items failed on validation, fix those rows and send them as a new
batch with a new key — reusing the old key replays the old outcome rather
than processing your corrections.
A note for SDK authors
Wrap this so callers cannot ignorefailed. Returning a plain response object
invites if (res.success) and silent data loss. Prefer either raising on any
failure, or returning a result type that forces the caller to handle both lists.
Filtering and sorting
Sorting
- means descending. Only
fields the object’s schema publishes can be sorted on; anything else is a 400
naming the field.
Filtering
filter takes a JSON object, URL-encoded.
A single condition:
and / or, nested at most 3 levels deep:
Values must match the field’s type. The schema tells you what each field is —
priority on leads is a number, so {"op":"eq","value":"High"} is a 400, not
a silent empty result. This is checked before the query runs, so a type mistake
never reaches the database.
Unknown fields are a 400 naming the field, rather than being ignored. An
ignored filter would return more rows than you asked for and look successful,
which is the worst possible failure for a sync.
Values are always treated as literal text, never as query syntax. Commas,
dots, parentheses, quotes and SQL fragments in a value match themselves and
nothing else — searching for 50% finds the string “50%”, not “50 followed by
anything”.
Filtering on custom fields
Custom fields work the same way. Because they are stored as JSON, comparisons on them are textual — a custom number field sorts and compares lexically ("10" < "9"). If ordering matters on a custom numeric field, pad the values or
keep it in a built-in field.
Notes, tasks and activities on a record
Notes
content is required.
Tasks
Creating a task also creates its link to the record, so it appears on that
record’s task list in the app — not just in a global task list.
The task is owned by and attributed to the user your API key belongs to. A key
with no owning user cannot create tasks or notes; re-create the key from a user
account.
Activities
Read-only here. Activities are logged by the CRM’s own workflows; this endpoint lets you read the interaction history for a record. Use/v1/activity-types to discover the types a workspace has.
Errors
Same rules as the record itself: a record you cannot see returns404 for its
notes, tasks and activities too — including on write. There is no way to attach
anything to a record you could not have read.
Errors, and the 403/404 distinction
The difference between 403 and 404 is deliberate and useful:
- 404 — you cannot see it. Either it does not exist, or it belongs to another tenant, or it falls outside your key’s data scope (own / team / branch). These are indistinguishable on purpose: telling them apart would confirm that someone else’s record exists.
- 403 — you can see it, but you are not allowed to do that to it. For example
reading a lead works, but deleting it needs
leads:deleteon your key.
Suggested sync loop
GET /v1/objectsto discover what you can access.GET /v1/objects/{object}/schemafor each object you touch, and cache it.- Page through records with
cursoruntilnext_cursoris null. - Write with
Idempotency-Key, retrying the same key on network failure. - Re-fetch the schema when a write starts failing with
422— a tenant may have added a mandatory field.
Related
- The object model — discovering objects and fields
- Idempotency
- Authentication and scopes
- Errors