Send a reply, add a note, send an automated message, or log one
const url = 'https://api.threadkite.app/v1/messages';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"threadId":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","contactId":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","inboxId":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","contactExternalId":{"integration":"my-dms","externalId":"CUST-10442"},"to":{"email":"sarah.turner@example.co.uk","phone":"07911 123456","name":"Sarah Turner"},"kind":"message","channel":"email","subject":"example","text":"Hi Sarah, yes it is. Saturday at 10 works for us.","html":"example","cc":[{"address":"accounts@example.co.uk","name":"example"}],"bcc":[{"address":"accounts@example.co.uk","name":"example"}],"automationKey":"invoice","transactional":false,"purpose":"reply","link":{"recordType":"deal","integration":"my-dms","externalId":"DEAL-2231","label":"Deal #2231","url":"https://example.com","status":"open"},"alreadySent":false,"sentAt":"2026-04-15T12:00:00Z","externalId":"example","integration":"my-dms","record":{"type":"invoice","externalId":"8812"},"itemIds":["2489E9AD-2EE2-8E00-8EC9-32D5F69181C0"],"locationId":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","attachmentIds":["2489E9AD-2EE2-8E00-8EC9-32D5F69181C0"],"channelAccountId":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","mailboxConnectionId":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","template":{"templateId":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","variables":[]},"signature":true,"reopen":false}'};
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/messages \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "threadId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "contactId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "inboxId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "contactExternalId": { "integration": "my-dms", "externalId": "CUST-10442" }, "to": { "email": "sarah.turner@example.co.uk", "phone": "07911 123456", "name": "Sarah Turner" }, "kind": "message", "channel": "email", "subject": "example", "text": "Hi Sarah, yes it is. Saturday at 10 works for us.", "html": "example", "cc": [ { "address": "accounts@example.co.uk", "name": "example" } ], "bcc": [ { "address": "accounts@example.co.uk", "name": "example" } ], "automationKey": "invoice", "transactional": false, "purpose": "reply", "link": { "recordType": "deal", "integration": "my-dms", "externalId": "DEAL-2231", "label": "Deal #2231", "url": "https://example.com", "status": "open" }, "alreadySent": false, "sentAt": "2026-04-15T12:00:00Z", "externalId": "example", "integration": "my-dms", "record": { "type": "invoice", "externalId": "8812" }, "itemIds": [ "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0" ], "locationId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "attachmentIds": [ "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0" ], "channelAccountId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "mailboxConnectionId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "template": { "templateId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "variables": [] }, "signature": true, "reopen": false }'Replies and automated messages are checked against consent (transactional automated messages are exempt from opt-outs) and queued for delivery; their status follows in message.status webhooks. With alreadySent, the message is only logged. Basic seats can add notes only.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Header Parameters
Section titled “Header Parameters”Any unique value up to 255 characters (a UUID, or the id of the item in your outbox). For 24 hours a retry with the same key and request returns the first response, with Idempotent-Replayed: true, instead of doing it again. Works with an API key or a signed-in session; each has its own keys.
Request Bodyrequired
Section titled “Request Bodyrequired”object
The thread to add to
Or the contact: their thread is used (and created, for automated messages)
With contactId: the inbox whose thread to use (a contact has one thread per inbox). Your default inbox when left out
Or the contact’s id in your system
object
The other system, e.g. my-dms
Example
my-dmsThe contact’s id there
Example
CUST-10442Or an email address, for a new message to someone who has not written in: the contact with that address is used, or added. Messages only.
object
Example
sarah.turner@example.co.ukA phone number: logged messages only (alreadySent), for a text you sent
Example
07911 123456Example
Sarah Turnermessage: a reply to the contact. note: internal, never sent. automated: from a flow in your system (invoice, e-sign, receipt).
Defaults to the thread’s last channel when the contact can be reached on it (an address that has not failed, no opt-out, and for texts and WhatsApp a connected number), else the first channel they can be reached on, email first. Ignored for notes.
Example
Hi Sarah, yes it is. Saturday at 10 works for us.The message formatted, as HTML (bold, italics, underline, lists, links, quotes and headings are kept; everything else is removed). Email: sent as the HTML part, with text as the plain part. WhatsApp: sent with WhatsApp formatting made from it instead of text. Notes keep it. Ignored on other channels
object
Example
accounts@example.co.ukobject
Example
accounts@example.co.ukRequired for automated messages
Example
invoiceAutomated only: the contact needs it whatever they opted out of (invoices, receipts, e-sign)
Automated only: marketing (review requests, offers) needs opt-in; reminder (an appointment or renewal reminder) needs a reminders or marketing opt-in on the channel
A record in your system to link
object
Example
my-dmsExample
DEAL-2231Example
Deal #2231Example
openYou already sent it yourself: log it in the thread without sending
When a logged message was sent
Your system’s id for a logged message. Logging the same id again returns the message already logged; delivery updates name it (POST /v1/messages/by-external-id/{externalId}/status)
Your system, when you are not an app: the logged message’s externalId is yours under it (api when left out). An app’s are always its own
Example
my-dmsA record of yours it is about (sent with an event before): the message shows it
object
Example
invoiceExample
8812Catalogue items the message is about; added to the thread
Files uploaded with POST /v1/attachments (up to 10, 20 MB in all). Sent with email replies and kept on notes.
Texts and WhatsApp: the number to send from (GET /v1/threads/{id}/reply-channels). Defaults to the number the contact last wrote to, else the inbox’s
Email: send through this connected mailbox (your own, or the shared mailbox of the thread’s inbox) so it goes from that address and lands in its Sent folder. See senders on GET /v1/threads/{id}/reply-channels. Without it email goes from your Threadkite address
WhatsApp: send an approved template instead of free text (required more than 24 hours after their last message)
object
From GET /v1/threads/{id}/reply-channels
Values for the template’s variables, in order
Example
[ "Sarah"]Email: add the signature (the inbox’s, else the business’s, filled in for whoever sends). On unless false; never added twice when the text already ends with it
Messages only: reopen the thread when it is Done, as the message is sent (a won thread keeps its outcome). Without it a Done thread stays Done
Responses
Section titled “Responses”object
object
The chapter of the thread it is in
object
Logo address; null shows an initial badge
Counts as a lead in reports
A made-up lead from “Send a test lead”, never counted
A person marked (true) or unmarked (false) it as a lead; null when the rule decided
The catalogue items the message named, in order
object
Where the item came from: manual, csv, api or an integration key
The item’s id in its source
From the attributes
The availability: in_stock, out_of_stock, preorder or backorder for products; an industry pack’s item types have their own
object
Amount in minor units (pence, cents)
ISO 4217
How the price is charged: one_off (once), monthly (every month) or annual (every year)
Image links, main image first
When it was removed from the catalogue (no longer in stock). Removed items stay on the leads and threads that mention them
Advert or product page
Usually sold as an add-on to something else (a warranty, delivery, an onboarding package). Suggestions only
The item types it is usually added to; empty means anything
object
Google/Meta title
Google/Meta description
Product page. Google/Meta link
Main image first. Google/Meta image_link and additional_image_link
Google/Meta price
object
Amount in minor units (pence, cents)
ISO 4217
Google/Meta sale_price
object
Amount in minor units (pence, cents)
ISO 4217
Google/Meta availability
Google/Meta condition
Google/Meta brand
GTIN-8, 12, 13 or 14. Google/Meta gtin
Google/Meta mpn
Your own stock code
Google/Meta google_product_category
Google/Meta product_type
Shared by variants. Google/Meta item_group_id
Google/Meta size
Google/Meta color
Google/Meta material
Google/Meta pattern
Google/Meta custom_label_0
Google/Meta custom_label_1
Google/Meta custom_label_2
Google/Meta custom_label_3
Google/Meta custom_label_4
object
Where the item came from: manual, csv, api or an integration key
The item’s id in its source
From the attributes
The availability: in_stock, out_of_stock, preorder or backorder for products; an industry pack’s item types have their own
object
Amount in minor units (pence, cents)
ISO 4217
How the price is charged: one_off (once), monthly (every month) or annual (every year)
Image links, main image first
When it was removed from the catalogue (no longer in stock). Removed items stay on the leads and threads that mention them
Advert or product page
Usually sold as an add-on to something else (a warranty, delivery, an onboarding package). Suggestions only
The item types it is usually added to; empty means anything
object
Advert title; made from make, model and derivative when left out. Google/Meta title
Google/Meta description
Number plate. Meta vehicle_registration_plate
Google/Meta vin
Google brand, Meta make
Google/Meta model
Full version name
Google/Meta trim
Google/Meta year
First registration date, YYYY-MM-DD
Google mileage, Meta mileage.value and .unit
object
Google condition and certified_pre_owned, Meta state_of_vehicle
Google engine, Meta fuel_type
Meta transmission
Meta drivetrain
Exterior colour. Google color, Meta exterior_color
Meta interior_color
Google/Meta body_style
Google/Meta price
object
Amount in minor units (pence, cents)
ISO 4217
Google/Meta sale_price
object
Amount in minor units (pence, cents)
ISO 4217
object
object
Amount in minor units (pence, cents)
ISO 4217
object
Amount in minor units (pence, cents)
ISO 4217
Main image first. Google image_link/additional_image_link, Meta image[n].url
Advert page. Google link, Meta url
Google/Meta availability
object
Set when a person wrote it
Outbound: queued, sent, delivered, opened, read, failed… Inbound: received
Why delivery failed, when the status is failed, bounced or complained
Not sent after every retry: POST /v1/messages/{id}/retry tries again
Sent outside Threadkite and logged here
Email authentication: pass, fail, unverified
First reply: seconds it took
object
object
An inline image in the email’s HTML (a pasted photo, a signature logo), not a file attached
For an inline image: the Content-ID the HTML’s cid: image points at
Files the email came with that were not kept (a type we refuse, too large, or failed the virus check)
object
Why it was not kept, in plain words
object
The ad platform’s campaign id
The ad click id, as kind:value
The campaign the lead came from (its UTMs or its lead ad)
object
Where the visit came from, worked out when the lead arrived
Render-only extras, such as the fields of a form
object
object
The lead provider, as the email names it
object
What they wrote when forwarding it
A forwarded lead whose real source nobody has picked yet
object
object
Events: booked, moved, cancelled, confirmed, completed, no_show. Messages to the customer: confirmation, reminder_day, reminder_soon, updated, cancelled.
object
Seconds talking
The caller (null when withheld or erased)
The number they called
The menu option the caller chose
Numbers the call was forwarded to
They left a voicemail
The voicemail, as text
GET /v1/messages/{id}/recording gives a link to play it
The recording is held in this provider’s account and cannot be played here now (the connection is gone)
When the recording was paused during the call (for card details); it has silence for each
object
The recording is no longer available (deleted at the end of the retention period, on erasure, or in the provider account)
object
Set for a call made outside a connected phone system and logged in the mobile or web app (POST /v1/threads/{id}/calls)
For logged calls: what they picked after the call
The call (GET /v1/calls/{id}); null for a call logged in the app with no phone system behind it
A short summary of the call, made from its recording
GET /v1/calls/{id}/transcript gives who said what
For a call made in Threadkite: how it went, as the person said afterwards
For a call made in Threadkite: the notes saved after it
object
object
object
object
object
object
object
object
expired once it has been deleted (the message shows “Video expired”)
Signed poster image (valid a few hours), once the video is ready
Times the watch page was opened
They opened the watch page
They started watching
They watched at least half
They watched (nearly) all of it
object
object
In major units
object
The status before this event
The first line about the record on this thread: shown as a card
Arrived late: older than the record’s state, changed nothing
The line for people
The thread had no owner and you claimed it by replying
Example
{ "message": { "kind": "message", "direction": "in", "channel": "email", "source": { "key": "website-form", "name": "Website form" }, "leadType": "quote", "items": [ { "source": "my-dms", "externalId": "SKU-1042", "title": "Oak dining table", "status": "in_stock", "price": { "amountMinor": 1299500, "currency": "GBP" }, "billing": "one_off", "addOnFor": [ "product" ], "attributes": { "title": "Merino jumper", "price": { "amountMinor": 1299500, "currency": "GBP" }, "salePrice": { "amountMinor": 1299500, "currency": "GBP" }, "availability": "in_stock", "condition": "new" }, "type": "product" } ], "text": "Is this still available?", "author": { "name": "James Patel" }, "failureReason": "The address does not exist", "automationKey": "invoice", "participants": [ { "role": "from", "address": "sarah.turner@example.co.uk" } ], "attachments": [ { "fileName": "photo.jpg", "mimeType": "image/jpeg", "size": 182044, "scanStatus": "pending", "contentId": "ii_m1x2y3z40" } ], "skippedAttachments": [ { "fileName": "video.mov", "reason": "video.mov is larger than 20 MB." } ], "tracking": { "clickId": "gclid:Cj0KCQjw", "campaign": { "name": "Spring sale" }, "trafficChannel": "paid_search" }, "lead": { "format": "adf", "forwardedBy": { "name": "Chris Moore" } }, "reference": { "contactName": "James Turner" }, "appointment": { "action": "booked" }, "call": { "direction": "in", "outcome": "answered", "recordingHeldBy": "Twilio", "source": { "name": "Google Ads" }, "loggedFrom": "mobile", "loggedOutcome": "answered", "disposition": "answered" }, "template": { "name": "missed_call", "language": "en_GB", "category": "marketing" }, "video": { "status": "uploading" }, "record": { "icon": "invoice", "tone": "success", "amount": { "value": 289, "currency": "GBP" }, "text": "Invoice INV-8812: Awaiting payment → Paid" }, "pin": { "by": { "id": "usr_8f2c", "name": "James Patel" } } }}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" } ]}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" } ]}Refused: opted_out, invalid_address, no_marketing_consent or marketing_suppressed (the person asked not to receive marketing)
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}