Skip to content

Send up to 500 events at once

POST
/v1/events/batch
curl --request POST \
--url https://api.threadkite.app/v1/events/batch \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{ "events": [ { "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" }, "name": "order.paid", "occurredAt": "2026-04-15T12:00:00Z", "idempotencyKey": "my-dms:agreement:8812:ending-90", "properties": {}, "setFields": { "finance_end_date": "2027-01-03" }, "timeline": { "show": true, "text": "Finance agreement ends on 3 January" }, "createIfMissing": false, "threadId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "record": { "type": "invoice", "externalId": "8812", "label": "Invoice INV-8812", "url": "https://dms.example.com/invoices/8812", "status": "Paid", "tone": "success", "amount": { "value": 289, "currency": "GBP" }, "details": { "For": "Annual service", "Due": "5 Nov 2026" }, "integration": "my-dms" } } ] }'

Each is handled as POST /v1/events, in order. One that is refused does not stop the rest: each result says whether it was recorded, or why not.

Media typeapplication/json
object
events
required
Array<object>
>= 1 items <= 500 items
object
contact
required

Who it is about: their id, your id for them (externalId with integration), their email or their phone, tried in that order

object
id
string format: uuid
externalId

Its id in your system (with integration)

string
>= 1 characters <= 200 characters

Example

CUST-10442
integration

Your system, as used for external ids. Needed with externalId, except for an app (its external ids are always its own)

string
>= 1 characters <= 100 characters

Example

my-dms
email
string
<= 320 characters

Example

sarah.turner@example.co.uk
phone
string
<= 40 characters

Example

07911 123456
name

Their name, for a new contact

string
<= 200 characters

Example

Sarah Turner
name
required

object.past_tense_verb (or one word). Recorded as event.<name>, the name flows, segments, webhooks and the event bus know it by; a name already starting event. is kept as it is

string
<= 94 characters /^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/

Example

order.paid
occurredAt

When it happened; now when left out

string format: date-time
idempotencyKey

Sending the same key again records nothing new and returns the first event

string
>= 1 characters <= 200 characters

Example

my-dms:agreement:8812:ending-90
properties

What happened, as JSON (up to 16 KB). Kept for two years.

object
key
additional properties
any

Example

{
"endDate": "2027-01-03",
"monthlyPayment": 289,
"balloon": 9450
}
setFields

Contact field values to set at the same time (null clears one)

object
key
additional properties
One of:
string

Example

{
"finance_end_date": "2027-01-03"
}
timeline

Show it on the contact’s thread (also when its event type is set to show)

object
show
required

Add a line to the contact’s thread

boolean
text

The line’s words; the event type’s label when left out

string
>= 1 characters <= 300 characters

Example

Finance agreement ends on 3 January
createIfMissing

With an email or phone and nobody matching, add them as a new contact (an existing contact is never changed)

boolean
threadId

One of the contact’s threads it is about (else none)

string format: uuid
record

The thing in your system it happened to (an invoice, an e-sign request, a document): added, or brought up to date, and shown as a card on the contact’s thread. An event older than the record’s state is kept but changes nothing

object
type
required

What kind of thing it is, in your words (invoice, esign, document, job_card). Picks its icon and groups it

string
<= 50 characters /^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/

Example

invoice
externalId
required

Its id in your system. With the type (and your app), identifies the record

string
>= 1 characters <= 200 characters

Example

8812
label
required

What staff see it called. Sent with every event about it

string
>= 1 characters <= 200 characters

Example

Invoice INV-8812
url

Where it opens in your system (https). Null clears it

string format: uri
nullable <= 2000 characters

Example

https://dms.example.com/invoices/8812
status

Its status, in your words. Null clears it

string
nullable >= 1 characters <= 40 characters

Example

Paid
tone

How the status looks: success, warning, danger or neutral (the default)

string
nullable
Allowed values: success warning danger neutral

Example

success
amount
One of:
object
value
required
One of:
number
currency
required
string
/^[A-Z]{3}$/

Example

GBP
details

Up to 4 label and value pairs shown on its card, for people only (never used by automations). Replaces the details as a whole; null clears them

object
<= 4 properties
key
additional properties
string
<= 200 characters

Example

{
"For": "Annual service",
"Due": "5 Nov 2026"
}
integration

Your system, when you are not an app (an app’s records are always its own). api when left out

string
/^[a-z0-9][a-z0-9_.-]{0,49}$/

Example

my-dms
Media typeapplication/json
object
results
required
Array<object>
object
index
required

Its position in the batch, from 0

number
ok
required
boolean
result
required
One of:
object
event
required
object
id
required
string format: uuid
contactId
required
string format: uuid
threadId
required
string format: uuid
nullable
name
required

The name everywhere in Threadkite: event.<the name sent> for events from other systems

string
label
required

The event type’s label

string
occurredAt
required
string format: date-time
origin
required

internal (Threadkite), api, connection or flow

string
Allowed values: internal api connection flow
properties
required

What it said (empty once cleared, two years after the event)

object
key
additional properties
any
messageId
required

The line it added to the thread, if any

string format: uuid
nullable
createdAt
required
string format: date-time
duplicate
required

The idempotency key was used before: nothing new was recorded

boolean
createdContact
required

A new contact was added (createIfMissing)

boolean
record
required
One of:
object
id
required
string format: uuid
source
required

The app (its slug) or the integration that sent it

string
sourceName
required

The app’s name, or the integration

string
appId
required

The app that sent it, if an app did

string format: uuid
nullable
type
required
string
icon
required

The icon its type gets (link for types without their own)

string
Allowed values: invoice quote signature document payment booking order link
externalId
required
string
label
required
string
url
required
string
nullable
status
required
string
nullable
tone
required
string
Allowed values: success warning danger neutral
amount
required
One of:
object
value
required

In major units

number
currency
required
string
details
required
object
key
additional properties
string
contactIds
required

The contacts it is about

Array<string>
threadId
required

The thread it shows on, if any (and if you can see it)

string format: uuid
nullable
chapterId
required
string format: uuid
nullable
inboxId
required

The inbox of the thread it shows on

string format: uuid
nullable
stateAt
required

When the event its current state came from happened

string format: date-time
createdAt
required
string format: date-time
updatedAt
required
string format: date-time
error
required
One of:
object
code
required
string
message
required
string
accepted
required

How many were recorded (duplicates included)

number
rejected
required

How many were refused

number

Example

{
"results": [
{
"result": {
"event": {
"name": "event.order.paid",
"label": "Finance agreement ending",
"origin": "internal"
},
"record": {
"source": "acme-crm",
"sourceName": "Acme CRM",
"type": "invoice",
"icon": "invoice",
"externalId": "8812",
"label": "Invoice INV-8812",
"status": "Paid",
"tone": "success",
"amount": {
"value": 289,
"currency": "GBP"
}
}
},
"error": {
"code": "contact_not_found",
"message": "No contact matches."
}
}
]
}
Media typeapplication/json
object
statusCode
required
number
code
required

Stable machine-readable reason

string
message
required
string
errors

With code invalid: every problem with the request, by field (message is the first)

Array<object>
object
path
required

Dotted path inside the item, e.g. attributes.price.amountMinor

string
message
required
string

Example

{
"statusCode": 403,
"code": "forbidden",
"message": "You do not have permission to do that.",
"errors": [
{
"message": "Enter the make"
}
]
}

Missing or invalid API key

Media typeapplication/json
object
statusCode
required
number
code
required

Stable machine-readable reason

string
message
required
string
errors

With code invalid: every problem with the request, by field (message is the first)

Array<object>
object
path
required

Dotted path inside the item, e.g. attributes.price.amountMinor

string
message
required
string

Example

{
"statusCode": 403,
"code": "forbidden",
"message": "You do not have permission to do that.",
"errors": [
{
"message": "Enter the make"
}
]
}

Sending events needs a plan with the API (code upgrade_required, feature api)

The organisation is on the Free plan (code upgrade_required)

Media typeapplication/json
object
statusCode
required
number
code
required

Stable machine-readable reason

string
message
required
string
feature
required

The feature that needs Pro

string
nullable
Allowed values: extraSeats connections aiAssistant aiReplies rules fullReports api inboxes bulkMessages videoMessages
upgradePath
required

Where to upgrade or pay, in the web app

string

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)

Media typeapplication/json
object
statusCode
required
number
code
required

Stable machine-readable reason

string
message
required
string
errors

With code invalid: every problem with the request, by field (message is the first)

Array<object>
object
path
required

Dotted path inside the item, e.g. attributes.price.amountMinor

string
message
required
string

Example

{
"statusCode": 403,
"code": "forbidden",
"message": "You do not have permission to do that.",
"errors": [
{
"message": "Enter the make"
}
]
}

Rate limit reached

Media typeapplication/json
object
statusCode
required
number
code
required

Stable machine-readable reason

string
message
required
string
retryAfter
required

Seconds until another request will be accepted

number

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
}