Tell Threadkite a customer bought
const url = 'https://api.threadkite.app/v1/sales';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"contact":{"id":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","externalId":"CUST-10442","integration":"my-dms","email":"sarah.turner@example.co.uk","phone":"07911 123456","name":"Sarah Turner"},"threadId":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","dealId":"DEAL-8812","integration":"my-dms","value":12500,"currency":"GBP","itemId":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","itemExternalId":"example","soldAt":"2026-04-15T12:00:00Z"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.threadkite.app/v1/sales \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "contact": { "id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "externalId": "CUST-10442", "integration": "my-dms", "email": "sarah.turner@example.co.uk", "phone": "07911 123456", "name": "Sarah Turner" }, "threadId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "dealId": "DEAL-8812", "integration": "my-dms", "value": 12500, "currency": "GBP", "itemId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "itemExternalId": "example", "soldAt": "2026-04-15T12:00:00Z" }'Records a sale for the sales reports, credited to the contact’s leads in the look-back (first and last touch). Say who bought (contact, or the threadId it was won in); without threadId it goes on the thread the same deal was recorded in, else the contact’s thread with the latest lead. The same dealId reported twice, or also linked to the thread as a won deal, counts once (duplicate: true). A new sale is sent to webhooks as sale.recorded (also to you: check actor to ignore your own).
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”object
Who bought: their id, your id for them (externalId with integration), their email or their phone. Leave out with threadId.
object
Its id in your system (with integration)
Example
CUST-10442Your system, as used for external ids. Needed with externalId, except for an app (its external ids are always its own)
Example
my-dmsExample
sarah.turner@example.co.ukExample
07911 123456Their name, for a new contact
Example
Sarah TurnerThe thread it was won in; otherwise the contact’s thread with the latest lead
The deal’s id in your system: the same deal reported twice (or also a won deal link) counts once
Example
DEAL-8812Your system, as for external ids; api when left out
Example
my-dmsWhat it sold for, as a decimal amount
Example
12500ISO code; your organisation’s when left out
Example
GBPThe catalogue item sold
The item’s id in your system, instead of itemId
When it was sold; now when left out
Responses
Section titled “Responses”object
object
The thread it was won in, if any
The deal’s id in the system that recorded it, if known
What it sold for, in minor units of currency (pence for GBP)
How it was first recorded: outcome (closed as Won), link (a deal link marked won) or api (reported with POST /v1/sales)
Who it is credited to
First touch: the earliest lead’s source
Last touch: the source of the latest lead before the sale
Leads from the contact in the look-back
The sale was recorded already (the same deal); nothing new was counted
Example
{ "sale": { "dealId": "DEAL-8812", "integration": "my-dms", "valueMinor": 1250000, "currency": "GBP", "origin": "outcome" }}object
Stable machine-readable reason
With code invalid: every problem with the request, by field (message is the first)
object
Dotted path inside the item, e.g. attributes.price.amountMinor
Example
{ "statusCode": 403, "code": "forbidden", "message": "You do not have permission to do that.", "errors": [ { "message": "Enter the make" } ]}Missing or invalid API key
object
Stable machine-readable reason
With code invalid: every problem with the request, by field (message is the first)
object
Dotted path inside the item, e.g. attributes.price.amountMinor
Example
{ "statusCode": 403, "code": "forbidden", "message": "You do not have permission to do that.", "errors": [ { "message": "Enter the make" } ]}The organisation is on the Free plan (code upgrade_required)
object
Stable machine-readable reason
The feature that needs Pro
Where to upgrade or pay, in the web app
Example
{ "statusCode": 402, "code": "upgrade_required", "message": "Connections are part of Pro. Upgrade to connect texts, calls and more.", "feature": "extraSeats", "upgradePath": "/settings/billing"}The key does not have the scope (or the person the permission)
object
Stable machine-readable reason
With code invalid: every problem with the request, by field (message is the first)
object
Dotted path inside the item, e.g. attributes.price.amountMinor
Example
{ "statusCode": 403, "code": "forbidden", "message": "You do not have permission to do that.", "errors": [ { "message": "Enter the make" } ]}No contact or thread matches
object
Stable machine-readable reason
With code invalid: every problem with the request, by field (message is the first)
object
Dotted path inside the item, e.g. attributes.price.amountMinor
Example
{ "statusCode": 403, "code": "forbidden", "message": "You do not have permission to do that.", "errors": [ { "message": "Enter the make" } ]}Rate limit reached
object
Stable machine-readable reason
Seconds until another request will be accepted
Example
{ "statusCode": 429, "code": "rate_limited", "message": "Too many requests. Each key can make 120 requests a minute; try again in 12 seconds.", "retryAfter": 12}