Skip to main content
Work through this before your business owner creates the connection. Every box is one concrete test. Send a request to your own API, look at the response, and tick the box or fix the endpoint — no box asks you to judge, review, or intend something. Each links to the page that states the requirement, so you can read the full rule if a box is unclear. Anything that cannot be settled by looking at a response is deliberately not here. The design decisions you still have to make — which currency to send, which contact to put first, how long your text fields are — live in Limitations and are summarised at the bottom of this page.

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>/pipelines over HTTPS with a valid token returns 200. 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 return 401 until you issue a replacement. See Authentication.

Every collection endpoint

Run these against each of the seven endpoints you implement.
  • The 200 body has a top-level data array and a top-level boolean has_more. Nothing else is read. See The envelope.
  • On the last page has_more is false. It is never true on a page that has no successor. See Paging.
  • On a collection with more than 100 records, ?limit=100&page=1 and ?limit=100&page=2 return data arrays that share no id. See Paging.
  • An endpoint you implemented that currently has no data returns 200 with { "data": [], "has_more": false } — not 404. See 404 Not Found.
  • Every record in data has an id that is a non-empty string of at most 255 characters — never null, never absent. See Malformed records.
  • Fetch the same record on two separate runs: its id is identical both times. See IDs are strings.

Ordering & incremental filtering

For the five endpoints that receive updated_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_since exactly equal to one record’s updated_at: that record is returned. The boundary is inclusive. See Incremental sync.
  • Within a single page, updated_at never 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_at greater 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_at advanced. Repeat for a stage move and for a soft-delete. See Ordering and the watermark.
  • /pipelines and /users return your complete current list even when updated_since is sent — they ignore it. See Incremental sync.

GET /pipelines

  • Every pipeline object has a stages key holding an array. It is never absent and never null; an empty [] is valid. See Pipeline fields.
  • Every stage’s kind is exactly open, won, or lost. 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. No 1.5, no "high". See Pipeline fields.
  • is_archived is true or false, never a string. See Pipeline fields.
  • Every stage_id any lead can reference appears inside some pipeline’s stages[]. 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, and role are each a string or null. See Fields.
  • is_active is true or false on every user. See Fields.
  • Every responsible_user_id sent on a lead, contact, task, or call appears as an id in this response. See Fields.
  • A manager who has left is returned with is_active: false rather than dropped from the list. Dropping them does not delete them. See Deletion behaviour.

GET /contacts

  • Every contact has an updated_at that 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_ids entry appears as an id here. See Fields.

GET /leads

  • Every lead has an updated_at that 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_id resolves to a pipeline, and every stage_id to a stage inside that pipeline. See Fields.
  • contact_ids is 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 /events returns 404 — and the sync still reports success. See If this endpoint is missing.
  • Every event has a lead_id that 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_at or created_at, and that same field is the one you sort ascending on. See Incremental sync.
  • value_before and value_after are a scalar, an object, an array, or null — every one of those is accepted. See Fields.

GET /tasks (optional)

  • If you do not implement it, GET /tasks returns 404. See If this endpoint is missing.
  • Every task has an updated_at that is present and parses. See Fields.
  • Every task has a lead_id or a contact_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_completed is true or false. See Fields.

GET /calls (optional)

  • If you do not implement it, GET /calls returns 404. See If this endpoint is missing.
  • Every call has an updated_at that is present and parses. See Fields.
  • Every call has a lead_id or a contact_id. Unlike a task, a call with neither is rejected and fails its page. See Association.
  • duration_seconds is a plain integer on every call. A numeric string fails the page here, even though price on a lead tolerates one. See Fields.

Error responses

  • A transient failure returns 503 (or 502/504), never 500. Only those three are retried; a 500 fails the entity on the first response with no retry. See 429 and 5xx errors.
  • A throttled response returns 429 with a Retry-After header in whole seconds. See Back-pressure with 429.
  • Every error response body is JSON containing an error string. 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:
All ticked? Hand your base URL, token, and rate to the business owner and proceed to Go live.