Knak Send Contacts API Guide
The Knak Send Contacts API lets you create, update, and remove contacts, define custom fields, and set field values on contacts directly from your own application. This guide walks through authentication, the available endpoints, and a recommended end-to-end flow.
High-Level Flow
Section titled “High-Level Flow”Working with the Contacts API generally involves three concepts:
- Contacts: the people you send to. Every contact has a set of core attributes (
email,first_name,last_name, and an optionalavatar_url). - Fields: custom attributes you define (for example “Department” or “Start Date”). Each field has a
key, a displayname, and atype. - Field values: the value of a given field for a given contact (for example
department = "Engineering").
A typical integration:
- defines any custom fields it needs (once)
- creates or updates contacts, optionally setting field values in the same request
- updates field values or removes contacts over time as your source data changes
Authentication
Section titled “Authentication”The Contacts API authenticates requests with a bearer token. Use a permanent API key generated for an API user.
-
Create an API user and generate a key by following Setting up an API User.
-
The account used must have Knak Send access enabled. If you are unsure whether your account has Knak Send, contact your administrator.
-
Send the token in the
Authorizationheader of every request:Authorization: Bearer YOUR_ACCESS_TOKEN
Requests without a valid, unexpired token receive a 401 UNAUTHENTICATED response. If your account does not have Knak Send enabled, you will receive a 403 ACCESS_DENIED response.
Base URL
Section titled “Base URL”All endpoints in this guide are relative to the following base URL:
https://send.knak.io/api/public/v1Requests and responses use JSON. Successful list and detail responses wrap their payload in a data key, and paginated responses include a meta object.
Creating a Field
Section titled “Creating a Field”Before you can set a custom field value on a contact, the field must exist. Create one with a name and a type.
Supported field types are string, number, date, datetime, and boolean.
Example request:
POST https://send.knak.io/api/public/v1/fieldsAuthorization: Bearer YOUR_ACCESS_TOKENContent-Type: application/json
{ "name": "Department", "type": "string"}Example response (201 Created):
{ "data": { "id": "c0a8012e-1f2a-4b3c-9d4e-5f6a7b8c9d0e", "key": "department", "name": "Department", "type": "string", "directory_id": "9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e" }}Knak derives the field key from the name. A single word is lowercased ("Department" becomes department), and a multi-word name is converted to camel case ("Start Date" becomes startDate). Always use the key returned in the response when setting field values on contacts.
Listing Fields
Section titled “Listing Fields”Retrieve all fields available to your company to discover their keys and types. This endpoint is not paginated.
Example request:
GET https://send.knak.io/api/public/v1/fieldsAuthorization: Bearer YOUR_ACCESS_TOKENExample response:
{ "data": [ { "id": "c0a8012e-1f2a-4b3c-9d4e-5f6a7b8c9d0e", "key": "department", "name": "Department", "type": "string", "directory_id": "9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e" }, { "id": "d1b9123f-2a3b-4c5d-ae6f-7a8b9c0d1e2f", "key": "startDate", "name": "Start Date", "type": "date", "directory_id": "9b1c2d3e-4f5a-6b7c-8d9e-0f1a2b3c4d5e" } ]}Creating a Contact
Section titled “Creating a Contact”Create a single contact with POST /contacts. The email, first_name, and last_name fields are required; avatar_url and field_values are optional.
field_values is an object keyed by field key, with scalar values. Each key must reference a field that exists in your API directory, and the value must match the field’s type.
Example request:
POST https://send.knak.io/api/public/v1/contactsAuthorization: Bearer YOUR_ACCESS_TOKENContent-Type: application/json
{ "email": "john@example.com", "first_name": "John", "last_name": "Doe", "avatar_url": "https://example.com/avatar.jpg", "field_values": { "department": "Engineering", "startDate": "2026-01-15" }}Example response (201 Created):
{ "data": { "id": "5f6a7b8c-9d0e-1f2a-3b4c-5d6e7f8a9b0c", "first_name": "John", "last_name": "Doe", "email": "john@example.com", "avatar_url": "https://example.com/avatar.jpg", "supervisor_id": null, "created_at": "2026-01-15T10:30:45+00:00", "updated_at": "2026-01-15T10:30:45+00:00" }}A few behaviors to be aware of:
POST /contactsis create-only. If an active contact with the same email already exists, the request returns409 CONTACT_ALREADY_EXISTS. To update an existing contact, use the bulk endpoint or update its field values.- If a contact with that email was previously deleted, creating it again restores and updates the contact rather than creating a duplicate.
- If your company has a recipient domain allowlist configured, an email whose domain is not on the allowlist returns
400 EMAIL_DOMAIN_NOT_ALLOWED. - Referencing an unknown field key, or supplying a value that does not match the field’s type (for example a non-numeric value for a
numberfield), returns422.
Creating and Updating Contacts in Bulk
Section titled “Creating and Updating Contacts in Bulk”Use POST /contacts/bulk to create or update many contacts in a single request. Contacts are matched by email: an email that does not yet exist is created, and an existing one is updated. You can submit between 1 and 1000 contacts per request.
This endpoint supports partial success — valid rows are processed even when other rows fail. The response reports how many contacts were created, updated, and failed, along with per-row error details.
Example request:
POST https://send.knak.io/api/public/v1/contacts/bulkAuthorization: Bearer YOUR_ACCESS_TOKENContent-Type: application/json
{ "contacts": [ { "email": "alice@example.com", "first_name": "Alice", "last_name": "Smith", "field_values": { "department": "Sales" } }, { "email": "bob@example.com", "first_name": "Bob", "last_name": "Jones" } ]}Example response (200 OK):
{ "data": { "created": 2, "updated": 0, "failed": 0, "errors": [] }}When some rows fail, those rows are reported in errors with their original index, email, and validation details, while the valid rows are still processed:
{ "data": { "created": 1, "updated": 0, "failed": 1, "errors": [ { "index": 1, "email": "not-an-email", "details": { "email": ["The email must be a valid email address."] } } ] }}Updating a Contact’s Field Values
Section titled “Updating a Contact’s Field Values”Set or replace the custom field values on an existing contact with PUT /contacts/{id}/field-values. The field_values object accepts between 1 and 20 entries, keyed by field key.
This endpoint uses replace-all semantics for your API directory: any field value that exists for the contact in the API directory but is not included in the request is removed.
Example request:
PUT https://send.knak.io/api/public/v1/contacts/5f6a7b8c-9d0e-1f2a-3b4c-5d6e7f8a9b0c/field-valuesAuthorization: Bearer YOUR_ACCESS_TOKENContent-Type: application/json
{ "field_values": { "department": "Marketing", "startDate": "2026-02-01" }}Example response (200 OK):
{ "data": [ { "field_id": "c0a8012e-1f2a-4b3c-9d4e-5f6a7b8c9d0e", "key": "department", "name": "Department", "type": "string", "value": "Marketing" }, { "field_id": "d1b9123f-2a3b-4c5d-ae6f-7a8b9c0d1e2f", "key": "startDate", "name": "Start Date", "type": "date", "value": "2026-02-01" } ]}Retrieving Contacts
Section titled “Retrieving Contacts”List contacts with GET /contacts. Results are paginated (default 25 per page, maximum 100).
Example request:
GET https://send.knak.io/api/public/v1/contacts?page[number]=1&page[size]=25Authorization: Bearer YOUR_ACCESS_TOKENSupported query parameters:
| Parameter | Example | Description |
|---|---|---|
page[number] | 2 | Page to retrieve (1-indexed, default 1). |
page[size] | 50 | Items per page (default 25, maximum 100). |
sort[] | email or -email | Sort by email, first_name, last_name, or created_at. Prefix with - for descending. |
filter[email] | john@example.com | Filter by email (also available for first_name and last_name). Defaults to an exact match; additional operators such as startsWith, contains, and between are also supported (see the API Reference for the full operator syntax). |
search | john | Free-text search across email, first_name, and last_name. |
Example response:
{ "data": [ { "id": "5f6a7b8c-9d0e-1f2a-3b4c-5d6e7f8a9b0c", "first_name": "John", "last_name": "Doe", "email": "john@example.com", "avatar_url": "https://example.com/avatar.jpg", "supervisor_id": null, "created_at": "2026-01-15T10:30:45+00:00", "updated_at": "2026-01-15T10:30:45+00:00" } ], "meta": { "current_page": 1, "per_page": 25, "total": 1, "last_page": 1, "from": 1, "to": 1 }}To retrieve a single contact along with its field values, use GET /contacts/{id}:
{ "data": { "id": "5f6a7b8c-9d0e-1f2a-3b4c-5d6e7f8a9b0c", "first_name": "John", "last_name": "Doe", "email": "john@example.com", "avatar_url": "https://example.com/avatar.jpg", "supervisor_id": null, "created_at": "2026-01-15T10:30:45+00:00", "updated_at": "2026-01-15T10:30:45+00:00", "field_values": [ { "field_id": "c0a8012e-1f2a-4b3c-9d4e-5f6a7b8c9d0e", "key": "department", "name": "Department", "type": "string", "value": "Engineering" } ] }}To fetch only a contact’s field values, use GET /contacts/{id}/field-values, which returns the same array found under field_values above.
Listing Values for a Field
Section titled “Listing Values for a Field”To see the distinct values currently in use for a field across your contacts, use GET /fields/{id}/values. Results are paginated.
Example request:
GET https://send.knak.io/api/public/v1/fields/c0a8012e-1f2a-4b3c-9d4e-5f6a7b8c9d0e/valuesAuthorization: Bearer YOUR_ACCESS_TOKENExample response:
{ "data": ["Engineering", "Marketing", "Sales"], "meta": { "current_page": 1, "per_page": 25, "total": 3, "last_page": 1, "from": 1, "to": 3 }}Deleting Contacts
Section titled “Deleting Contacts”Delete a single contact with DELETE /contacts/{id}. A successful delete returns 204 No Content with an empty body. Deletes are soft deletes; the contact is removed from queries but can be restored by creating it again with the same email.
DELETE https://send.knak.io/api/public/v1/contacts/5f6a7b8c-9d0e-1f2a-3b4c-5d6e7f8a9b0cAuthorization: Bearer YOUR_ACCESS_TOKENTo delete many contacts at once, use DELETE /contacts/bulk with an array of contact IDs (1 to 1000 per request). The response reports how many contacts were actually deleted (IDs that do not exist or were already deleted are not counted).
Example request:
DELETE https://send.knak.io/api/public/v1/contacts/bulkAuthorization: Bearer YOUR_ACCESS_TOKENContent-Type: application/json
{ "ids": [ "5f6a7b8c-9d0e-1f2a-3b4c-5d6e7f8a9b0c", "6a7b8c9d-0e1f-2a3b-4c5d-6e7f8a9b0c1d" ]}Example response (200 OK):
{ "data": { "deleted": 2 }}Using the Contacts API
Section titled “Using the Contacts API”The general flow that we recommend for keeping your contacts in sync via the API is as follows:
-
Create an API user and generate an access token
- Follow Setting up an API User, and make sure the account has Knak Send access enabled
-
Define any custom fields your contacts need
- Create each field with
POST /fieldsand note thekeyreturned in the response - Or call
GET /fieldsto discover the keys and types of fields that already exist
- Create each field with
-
Create or update your contacts
- For a single contact, use
POST /contacts - To create and update many contacts at once (matched by email), use
POST /contacts/bulk - Include a
field_valuesobject in either request to set custom field values, keyed by fieldkey
- For a single contact, use
-
Keep field values up to date as your source data changes
- Use
PUT /contacts/{id}/field-valuesto replace a contact’s custom field values
- Use
-
Read back your data to verify
- List contacts with
GET /contacts, retrieve one withGET /contacts/{id}, or fetch a contact’s values withGET /contacts/{id}/field-values
- List contacts with
-
Remove contacts that should no longer receive sends
- Delete one with
DELETE /contacts/{id}, or many withDELETE /contacts/bulk
- Delete one with
Rate Limits
Section titled “Rate Limits”Requests are rate limited per API client:
- Standard endpoints: up to 1000 requests per minute
- Bulk endpoints (
POST /contacts/bulkandDELETE /contacts/bulk): up to 60 requests per minute
Exceeding a limit returns 429 Too Many Requests. Use the bulk endpoints to keep large synchronization jobs well within these limits.
Error Responses
Section titled “Error Responses”Most application errors (HTTP 400, 401, 403, 404, 409, and 503) are returned with an error object containing a machine-readable code and a human-readable message. Branch on the code programmatically; the message text is subject to change.
{ "error": { "code": "CONTACT_NOT_FOUND", "message": "Contact not found." }}Common error codes:
| Code | HTTP Status | Meaning |
|---|---|---|
UNAUTHENTICATED | 401 | Missing, invalid, or expired bearer token. |
ACCESS_DENIED | 403 | Knak Send access is not enabled for the account. |
EMAIL_DOMAIN_NOT_ALLOWED | 400 | The email’s domain is not on your recipient domain allowlist. |
CONTACT_NOT_FOUND | 404 | No contact with the given ID exists for your company. |
FIELD_NOT_FOUND | 404 | No field with the given ID exists for your company. |
CONTACT_ALREADY_EXISTS | 409 | An active contact with the given email already exists (single create). |
FIELD_ALREADY_EXISTS | 409 | A field with the given key already exists in your API directory. |
SERVICE_UNAVAILABLE | 503 | A dependent Knak service is temporarily unavailable. |
Validation errors (422)
Section titled “Validation errors (422)”Validation errors (for example a missing required field or a value of the wrong type) use a different envelope: a top-level errors array rather than a single error object. Each entry has an identifier, a human-readable details string, and a meta object that lists the offending fields and their messages.
{ "error_at": "2026-01-15T10:30:45+00:00", "errors": [ { "identifier": "ValidationError", "details": "One or more of the given request fields failed backend validation.", "meta": { "email": ["The email field is required."] } } ]}Rate-limit errors (429)
Section titled “Rate-limit errors (429)”When you exceed a rate limit, the response uses a message/type object rather than the error shape:
{ "message": "Too Many Attempts.", "type": "HttpException" }Next Steps
Section titled “Next Steps”That’s it! You’re ready to keep Knak Send contacts and their field values in sync with your own systems.
For a complete reference of every endpoint, see the Knak Send API Reference.
Need help? Reach out via the chat bubble in Knak or email support@knak.com.