Transport & auth
These two boxes trace to Authentication and Errors rather
than to an endpoint page. They are here because they are the two most common reasons a
first connection fails.
-
GET <your-base-url>/pipelinesover HTTPS with a valid token returns200. The same base URL serves all seven endpoints. See Quickstart. - The same request with one character changed in the token returns
401, and revoking the token makes every endpoint return401until you issue a replacement. See Authentication.
Every collection endpoint
Run these against each of the seven endpoints you implement.- The
200body has a top-leveldataarray and a top-level booleanhas_more. Nothing else is read. See The envelope. - On the last page
has_moreisfalse. It is nevertrueon a page that has no successor. See Paging. - On a collection with more than 100 records,
?limit=100&page=1and?limit=100&page=2returndataarrays that share noid. See Paging. - An endpoint you implemented that currently has no data returns
200with{ "data": [], "has_more": false }— not404. See 404 Not Found. - Every record in
datahas anidthat is a non-empty string of at most 255 characters — nevernull, never absent. See Malformed records. - Fetch the same record on two separate runs: its
idis identical both times. See IDs are strings.
Ordering & incremental filtering
For the five endpoints that receiveupdated_since — /contacts, /leads, /tasks,
/calls, /events.
- Pick a timestamp that falls between two of your records, send it as
updated_since, and confirm the older record is absent from the response. See Incremental sync. - Send an
updated_sinceexactly equal to one record’supdated_at: that record is returned. The boundary is inclusive. See Incremental sync. - Within a single page,
updated_atnever decreases from one record to the next. See Ordering and the watermark. - Across a page boundary, the first record of page 2 has an
updated_atgreater than or equal to the last record of page 1. A single backwards step fails the entity. See Ordering and the watermark. - Edit one field on one record in your CRM, re-fetch it, and confirm
updated_atadvanced. Repeat for a stage move and for a soft-delete. See Ordering and the watermark. -
/pipelinesand/usersreturn your complete current list even whenupdated_sinceis sent — they ignore it. See Incremental sync.
GET /pipelines
- Every pipeline object has a
stageskey holding an array. It is never absent and nevernull; an empty[]is valid. See Pipeline fields. - Every stage’s
kindis exactlyopen,won, orlost. Search your response for any other value — one"win"stops the whole sync. See Stage kind classification. - Every
sort, on pipelines and on stages, is an integer. No1.5, no"high". See Pipeline fields. -
is_archivedistrueorfalse, never a string. See Pipeline fields. - Every
stage_idany lead can reference appears inside some pipeline’sstages[]. A lead pointing at a stage you do not return cannot be classified won or lost. See Stage fields.
GET /users
- Every user has an
id;name,email, androleare each a string ornull. See Fields. -
is_activeistrueorfalseon every user. See Fields. - Every
responsible_user_idsent on a lead, contact, task, or call appears as anidin this response. See Fields. - A manager who has left is returned with
is_active: falserather than dropped from the list. Dropping them does not delete them. See Deletion behaviour.
GET /contacts
- Every contact has an
updated_atthat is present and parses as one of the documented forms. See Timestamp format. -
custom_fields, when sent, is a JSON object. An array, a string, a number, or{}is silently discarded with no error. See Fields. - Every contact referenced by a lead’s first
contact_idsentry appears as anidhere. See Fields.
GET /leads
- Every lead has an
updated_atthat is present and parses. See Timestamp format. -
price, where present, is a number or a numeric string such as"48000000". Never"abc","12abc", or""— a present non-numeric value fails the page. See Fields. - Every
pipeline_idresolves to a pipeline, and everystage_idto a stage inside that pipeline. See Fields. -
contact_idsis an array, and its first element is the contact you want linked — the rest are dropped. See only the first is kept. - A lead deleted in your CRM comes back with
is_deleted: true. Omitting it from the response does not delete it. See Deletion behaviour.
GET /events (optional)
- If you do not implement it,
GET /eventsreturns404— and the sync still reports success. See If this endpoint is missing. - Every event has a
lead_idthat is present, non-null, and resolves to a lead. One event without it fails the whole page. See Fields. - Every event carries a valid
updated_atorcreated_at, and that same field is the one you sort ascending on. See Incremental sync. -
value_beforeandvalue_afterare a scalar, an object, an array, ornull— every one of those is accepted. See Fields.
GET /tasks (optional)
- If you do not implement it,
GET /tasksreturns404. See If this endpoint is missing. - Every task has an
updated_atthat is present and parses. See Fields. - Every task has a
lead_idor acontact_id. A task with neither is ingested without any error and is then invisible in every report — this box is the only thing that catches it. See Association. -
is_completedistrueorfalse. See Fields.
GET /calls (optional)
- If you do not implement it,
GET /callsreturns404. See If this endpoint is missing. - Every call has an
updated_atthat is present and parses. See Fields. - Every call has a
lead_idor acontact_id. Unlike a task, a call with neither is rejected and fails its page. See Association. -
duration_secondsis a plain integer on every call. A numeric string fails the page here, even thoughpriceon a lead tolerates one. See Fields.
Error responses
- A transient failure returns
503(or502/504), never500. Only those three are retried; a500fails the entity on the first response with no retry. See 429 and 5xx errors. - A throttled response returns
429with aRetry-Afterheader in whole seconds. See Back-pressure with 429. - Every error response body is JSON containing an
errorstring. See Error body shape.
Not on this checklist
These are real requirements, but none can be ticked by looking at a response — they are decisions about your data, not properties of your API. Read them, then move on:- Which currency
priceis in. A connection is effectively single-currency;currencyis not stored. See Currency is not stored. - Which contact goes first in
contact_ids. Only the first is kept. See only the first is kept. - How long your text fields are. Over-length text is silently truncated, never rejected, so no response reveals the problem. See Field truncation limits.
- Which entities you can delete. Only leads have a tombstone. See Tombstones apply to leads only.
All ticked? Hand your base URL, token, and rate to the business owner and proceed to
Go live.