Skip to content
Klozzo API
OpenAPI Postman

Two-way sales integration

Outbound webhooks is the CRM telling you. This is you telling the CRM — chiefly: the customer went live on our side, so close the deal on yours.

1. Get a token

Issue one from Settings → API tokens, bound to the location it belongs to. A bound token sees nothing outside that location, and rows from another one answer 404 rather than 403 — telling an outsider "that exists but is not yours" is telling them it exists.

The whole of it is in Authenticating requests: one page, and it is the same for every endpoint in this API.

2. Map your identifiers with external_ref

companies, deals, quotes and payments each carry an optional external_ref, unique inside the location. Put your own identifier there and you never have to store ours.

  • Set it when you create the record (POST /api/v1/deals).
  • Set it afterwards for records that predate the integration: POST /api/v1/deals/{ulid}/external-ref.
  • Look records up by it: GET /api/v1/deals?external_ref=sub_88213.

URLs use ULIDs, never sequential ids — an enumerable id would leak how much business a tenant does.

3. Reconcile

POST /api/v1/deals/reconcile
Authorization: Bearer <token>
Idempotency-Key: rec-2026-08-02-88213
Content-Type: application/json

{
  "external_ref": "sub_88213",
  "status": "won",
  "accepted_quote_ids": [15],
  "amount": "25013.94",
  "paid_at": "2026-08-02",
  "reference": "SPEI-8891"
}

The deal closes, the quotes you named are marked accepted (the rest are withdrawn), the payment is recorded, and the deal's timeline says the external system did it — by token name, so a seller opening the record on Monday knows why it is closed.

If the deal itself carries no external_ref, the company's is used instead — provided that company has exactly one open deal. With two, you get 404 rather than a guess about which one the money was for.

3-bis. Record a sale that never had a deal

reconcile closes a deal that already exists. When the customer bought on their own and nobody here ever opened one, POST /api/v1/sales is the call — what it decides, how it answers a renewal and what meta.matched_by means are written down in Recording a sale.

4. Idempotency is mandatory

Every mutating call in this section requires an Idempotency-Key header. One key per logical operation, kept for 24 hours.

The rules — what a replay returns, what a mismatched body returns, what is never remembered — are the same everywhere and are written down once, in Idempotency.

5. Error codes

The full catalogue — the shape of the body, what every code means and which ones are worth retrying — is in Errors. Two of them mean something extra in this section:

Code Meaning here
404 Unknown external_ref, or a company with two open deals: there is no honest way to guess which one the money was for, so you get a 404 rather than a guess.
409 Idempotency conflict, or the deal was already closed by a person here. Read data.status — a human decision is never overwritten in silence.

6. The quote PDF

GET /api/v1/quotes/{ulid}/pdf returns the document with your token. The quote resource also carries pdf_url: a signed link, valid ~30 minutes, that needs no token — use it to show the document in your own UI without proxying credentials through a browser.

That link is not the customer's. The customer gets a DocumentLink with the quote's commercial validity, a branded viewer and read tracking behind it — which is how the seller finds out they opened it three times and never called back.