Skip to content

Import contacts from a CSV

POST
/v1/contacts/imports
curl --request POST \
--url 'https://api.threadkite.app/v1/contacts/imports?columns=name%2Cemail%2Cphone%2Ccompany_name%2C%2Cconsent%3Aemail' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form file=@file

Checks the file and its columns as the preview does (a file that cannot be imported is refused at once), stores it and starts the import, which runs in the background: follow it with GET /v1/contacts/imports/{id}. The import adds a contact for every new person and fills the blanks of the contacts you already have (nothing is overwritten), finds or adds the companies they work for and links them (their primary company when they have none), and records consent, as given in an import, where a consent column says yes (an opt-out already recorded stands). Rows with problems are skipped and reported. Says contact.created, contact.updated, company.created and contact.consent_changed as usual. Importing the same file again changes nothing. The file is deleted once the import has run.

columns

What each column holds, in the order of the columns, separated by commas: name, first_name, last_name, email, phone, company_name, company_number, company_domain, job_title, role, field:<contact field key> or consent:<email|sms|whatsapp|phone> (yes records an opt-in), or nothing to leave a column out. Left out: guessed from the headers (the preview says what it guessed)

string
<= 4000 characters

Example

name,email,phone,company_name,,consent:email

What each column holds, in the order of the columns, separated by commas: name, first_name, last_name, email, phone, company_name, company_number, company_domain, job_title, role, field:<contact field key> or consent:<email|sms|whatsapp|phone> (yes records an opt-in), or nothing to leave a column out. Left out: guessed from the headers (the preview says what it guessed)

Media typemultipart/form-data
object
file
required

CSV, up to 5 MB and 10,000 rows, with a header row

string format: binary
Media typeapplication/json
object
id
required

Pass to GET /v1/contacts/imports/{id} to follow it

string format: uuid
status
required

queued (waiting to start), running, done (see result) or failed (see error)

string
Allowed values: queued running done failed
rows
required

Data rows in the file

integer
>= -9007199254740991 <= 9007199254740991
rowsDone
required

Rows gone through so far

integer
>= -9007199254740991 <= 9007199254740991
result
required
One of:
object
rows
required

Data rows in the file

integer
>= -9007199254740991 <= 9007199254740991
contactsCreated
required

New contacts

integer
>= -9007199254740991 <= 9007199254740991
contactsUpdated
required

Contacts you already had, matched by email address then phone number, with blanks filled, a company linked or consent recorded (nothing is overwritten)

integer
>= -9007199254740991 <= 9007199254740991
contactsUnchanged
required

Contacts you already had that the file adds nothing to

integer
>= -9007199254740991 <= 9007199254740991
contactsSkipped
required

Rows matching a contact in an inbox you are not in: left as they are, and not shown

integer
>= -9007199254740991 <= 9007199254740991
companiesCreated
required

New companies

integer
>= -9007199254740991 <= 9007199254740991
companiesMatched
required

Companies you already had, matched by company number, then domain, then name

integer
>= -9007199254740991 <= 9007199254740991
rowsWithErrors
required

Rows that cannot be imported (see errors)

integer
>= -9007199254740991 <= 9007199254740991
errors
required

Rows that were skipped and why (the first 100), without the values they quote: “This is not an email address”

Array<object>
object
line
required

The line in the file (the header is 1)

integer
>= -9007199254740991 <= 9007199254740991
messages
required
Array<string>
error
required

Why it failed, when it did

string
nullable
createdAt
required
string format: date-time
startedAt
required
string format: date-time
nullable
finishedAt
required
string format: date-time
nullable

Example

{
"status": "queued",
"rows": 10000,
"rowsDone": 2400,
"result": {
"rows": 120,
"contactsCreated": 80,
"contactsUpdated": 25,
"contactsUnchanged": 10,
"contactsSkipped": 0,
"companiesCreated": 12,
"companiesMatched": 30,
"rowsWithErrors": 5,
"errors": [
{
"line": 7,
"messages": [
"No email address or phone number"
]
}
]
}
}
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"
}
]
}

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"
}
]
}
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
}