openapi: 3.1.0
info:
description: |
# Overview
Welcome to the developer documentation for the Knak Send API.
We provide a RESTful interface to key resources within Knak Send to enable your own automation workflows.
This API will allow you to automate processes regarding contact and field management within your Knak Send environment.
You can download the formal definition of this public interface in OpenAPI 3 (formerly Swagger) format using the link above.
## Endpoint
`https://send.knak.io/api/public/v1`
## Authentication
All requests are authenticated using a Bearer token in the `Authorization` header:
```
curl --location --request GET 'https://send.knak.io/api/public/v1/contacts' \
--header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbG...'
```
A token can be created as a non-expiring token through the Enterprise UI, via the [API Access menu](https://enterprise.knak.io/account/api-access). The account associated with the token must have Knak Send access enabled.
## Errors
Errors in requests made to the API can be viewed directly from the response code that is returned.
Below are a list of the common error responses returned and an explanation of what they mean.
| Code | Reason |Description |
| ----------------| ----------- | ----------- |
| **400** | **Bad Request** | The request was rejected (for example, a contact's email domain is not in the account's allow list). |
| **401** | **Unauthenticated** | The access token is missing, invalid, or expired. |
| **403** | **Forbidden** | The account does not have Knak Send access enabled, or the token lacks the required scope. |
| **404** | **Not Found** | The requested resource could not be found. Verify that the resource exists and that you are using the correct identifier. |
| **409** | **Conflict** | The resource could not be created because it already exists (for example, a field with the same generated key). |
| **422** | **Unprocessable Entity** | One or more request fields failed backend validation. The `meta` object lists the offending fields. |
| **429** | **Too Many Requests** | The rate limit for the endpoint has been exceeded. |
| **503** | **Service Unavailable** | An upstream authentication service is temporarily unavailable. This can occur on any request; retry after a short delay. |
Three error response shapes are used. Where an error `code` (or `identifier`) is present, branch on it
programmatically; the human-readable `message`/`details` text is subject to change.
Authentication, authorization, bad-request, not-found, conflict and service errors
(400, 401, 403, 404, 409, 503) return a compact `error` object with a machine-readable `code`:
```json
{ "error": { "code": "UNAUTHENTICATED", "message": "Invalid or expired access token." } }
```
Validation errors (422) return an `errors` array:
```json
{
"error_at": "2026-06-02T13:56:31+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 (429) errors return a `message`/`type` object:
```json
{ "message": "Too Many Attempts.", "type": "HttpException" }
```
## Rate limiting
Requests are rate limited per API client. Standard endpoints allow up to **1000 requests per minute**.
Bulk endpoints (such as `POST /contacts/bulk`) are limited to **60 requests per minute**. Exceeding a limit
returns a `429 Too Many Requests` response.
## Pagination
List endpoints that can return large result sets are paginated using the `page` query parameter:
- `page[number]` — the page to return (1-based, default `1`).
- `page[size]` — the number of records per page (default `25`, maximum `100`).
Paginated responses include a `meta` object describing the result window (`total`, `current_page`,
`last_page`, `per_page`, `from`, `to`).
## Filtering
List endpoints support filtering on specific fields using the `filter` query parameter, in the form
`filter[field_name]=value`. The fields that can be filtered are listed on each endpoint.
For simple equality, pass the value directly: `filter[first_name]=Jane`. For other comparisons, pass an
`operator` and `value`: `filter[first_name][operator]=startsWith&filter[first_name][value]=Ja`. Supported
operators include `=`, `!=`, `>`, `<`, `~`, `contains`, `notContains`, `startsWith`, `between`, `exists`
and `notExists`.
## Sorting
List endpoints support sorting via the `sort` query parameter. Pass one or more field names; prefix a field
with `-` for descending order. For example `sort[]=last_name&sort[]=-created_at`. The fields that can be
sorted are listed on each endpoint.
version: V1
title: Knak Send Contacts API Reference — Contacts
x-logo:
url: https://s3.amazonaws.com/assets.knak.io/img/Knak-Logo-Medium.png
servers:
- url: https://send.knak.io/api/public/v1
description: production
tags:
- name: Contacts
description: Create, retrieve and list contacts in Knak Send.
paths:
/contacts:
get:
tags:
- Contacts
summary: List contacts
description: |
Returns a paginated list of contacts. Supports pagination, sorting and filtering.
- **Sortable fields:** `email`, `first_name`, `last_name`, `created_at`
- **Filterable fields:** `email`, `first_name`, `last_name`
operationId: listContacts
parameters:
- $ref: '#/components/parameters/PageNumber'
- $ref: '#/components/parameters/PageSize'
- name: sort[]
in: query
required: false
description: 'Fields to sort by, passed as a repeated `sort[]` parameter. Prefix a field with
`-` for descending order. Allowed fields: `email`, `first_name`, `last_name`, `created_at`.'
style: form
explode: true
schema:
type: array
items:
type: string
enum:
- email
- first_name
- last_name
- created_at
- -email
- -first_name
- -last_name
- -created_at
example:
- -created_at
- name: filter[email]
in: query
required: false
description: Filter by email address.
schema:
type: string
example: jane.doe@example.com
- name: filter[first_name]
in: query
required: false
description: Filter by first name.
schema:
type: string
- name: filter[last_name]
in: query
required: false
description: Filter by last name.
schema:
type: string
responses:
'200':
description: A paginated list of contacts.
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/Contact'
meta:
$ref: '#/components/schemas/PaginationMeta'
examples:
default:
value:
data:
- id: 9b1c2d3e-4f56-7890-abcd-ef1234567890
first_name: Jane
last_name: Doe
email: jane.doe@example.com
avatar_url: https://cdn.example.com/avatars/jane.png
supervisor_id: 1a2b3c4d-5e6f-7890-abcd-ef0987654321
created_at: '2026-05-01T12:00:00+00:00'
updated_at: '2026-05-20T09:30:00+00:00'
meta:
total: 1
current_page: 1
last_page: 1
per_page: 25
from: 1
to: 1
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/TooManyRequests'
post:
tags:
- Contacts
summary: Create a contact
description: Creates a single contact. To create or update many contacts at once, use `POST /contacts/bulk`.
operationId: createContact
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ContactInput'
examples:
default:
value:
email: jane.doe@example.com
first_name: Jane
last_name: Doe
avatar_url: https://cdn.example.com/avatars/jane.png
field_values:
department: Engineering
startDate: '2026-01-15'
responses:
'201':
description: The contact was created.
content:
application/json:
schema:
type: object
properties:
data:
$ref: '#/components/schemas/Contact'
'400':
$ref: '#/components/responses/EmailDomainNotAllowed'
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
'409':
$ref: '#/components/responses/ContactConflict'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/TooManyRequests'
/contacts/{id}:
get:
tags:
- Contacts
summary: Retrieve a contact
description: Returns a single contact by ID, including its field values.
operationId: getContact
parameters:
- $ref: '#/components/parameters/ContactId'
responses:
'200':
description: The requested contact.
content:
application/json:
schema:
type: object
properties:
data:
$ref: '#/components/schemas/ContactDetail'
examples:
default:
value:
data:
id: 9b1c2d3e-4f56-7890-abcd-ef1234567890
first_name: Jane
last_name: Doe
email: jane.doe@example.com
avatar_url: https://cdn.example.com/avatars/jane.png
supervisor_id: 1a2b3c4d-5e6f-7890-abcd-ef0987654321
created_at: '2026-05-01T12:00:00+00:00'
updated_at: '2026-05-20T09:30:00+00:00'
field_values:
- field_id: f1e2d3c4-b5a6-7890-abcd-ef1234567890
key: department
name: Department
type: string
value: Engineering
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
delete:
tags:
- Contacts
summary: Delete a contact
description: Soft-deletes a contact by ID. The contact is removed from listings but retained internally.
operationId: deleteContact
parameters:
- $ref: '#/components/parameters/ContactId'
responses:
'204':
description: The contact was deleted. The response body is empty.
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
/contacts/{id}/field-values:
get:
tags:
- Contacts
summary: List a contact's field values
description: Returns the field values associated with a single contact.
operationId: getContactFieldValues
parameters:
- $ref: '#/components/parameters/ContactId'
responses:
'200':
description: The contact's field values.
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/FieldValue'
examples:
default:
value:
data:
- field_id: f1e2d3c4-b5a6-7890-abcd-ef1234567890
key: department
name: Department
type: string
value: Engineering
- field_id: a9b8c7d6-e5f4-3210-abcd-ef0987654321
key: startDate
name: Start Date
type: date
value: '2026-01-15'
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'429':
$ref: '#/components/responses/TooManyRequests'
put:
tags:
- Contacts
summary: Replace a contact's field values
description: |
Replaces the field values for a single contact. Each key in `field_values` must match the `key` of an
existing field (see `GET /fields`) and its value is validated against the field's type. Between 1 and 20
field values may be provided.
operationId: replaceContactFieldValues
parameters:
- $ref: '#/components/parameters/ContactId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/FieldValuesInput'
examples:
default:
value:
field_values:
department: Engineering
startDate: '2026-01-15'
responses:
'200':
description: The contact's updated field values.
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/FieldValue'
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/TooManyRequests'
/contacts/bulk:
post:
tags:
- Contacts
summary: Bulk upsert contacts
description: |
Creates or updates up to 1000 contacts in a single request, matching existing contacts by email.
Validation is performed per contact. The request as a whole succeeds with a `200` response even when
some individual contacts fail; the response body reports counts of `created`, `updated` and `failed`
contacts, along with an `errors` array describing each failure by its index in the submitted array.
This endpoint is subject to the stricter bulk rate limit (60 requests per minute).
operationId: bulkUpsertContacts
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- contacts
properties:
contacts:
type: array
minItems: 1
maxItems: 1000
items:
$ref: '#/components/schemas/ContactInput'
examples:
default:
value:
contacts:
- email: jane.doe@example.com
first_name: Jane
last_name: Doe
field_values:
department: Engineering
- email: john@notallowed.com
first_name: John
last_name: Smith
responses:
'200':
description: |
The bulk operation was processed. Inspect `created`, `updated`, `failed` and `errors` to determine
the outcome of each contact.
content:
application/json:
schema:
type: object
properties:
data:
$ref: '#/components/schemas/BulkUpsertResult'
examples:
default:
value:
data:
created: 1
updated: 0
failed: 1
errors:
- index: 1
email: john@notallowed.com
details:
email:
- Domain is not allowed
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/TooManyRequests'
delete:
tags:
- Contacts
summary: Bulk delete contacts
description: |
Deletes up to 1000 contacts in a single request, identified by their IDs. The response reports the
number of contacts actually deleted; IDs that do not match an existing contact are ignored rather than
causing the request to fail.
This endpoint is subject to the stricter bulk rate limit (60 requests per minute).
operationId: bulkDeleteContacts
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- ids
properties:
ids:
type: array
minItems: 1
maxItems: 1000
items:
type: string
maxLength: 255
examples:
default:
value:
ids:
- 9b1c2d3e-4f56-7890-abcd-ef1234567890
- 1a2b3c4d-5e6f-7890-abcd-ef0987654321
responses:
'200':
description: The bulk delete was processed.
content:
application/json:
schema:
type: object
properties:
data:
type: object
properties:
deleted:
type: integer
description: The number of contacts that were deleted.
examples:
default:
value:
data:
deleted: 2
'401':
$ref: '#/components/responses/Unauthenticated'
'403':
$ref: '#/components/responses/Forbidden'
'422':
$ref: '#/components/responses/ValidationError'
'429':
$ref: '#/components/responses/TooManyRequests'
components:
responses:
Forbidden:
description: Knak Send access is not enabled for the account, or the token lacks the required scope.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorObject'
example:
error:
code: ACCESS_DENIED
message: Knak Send access is not enabled for this account.
TooManyRequests:
description: |
The rate limit for the endpoint has been exceeded. A `Retry-After` header indicates how many seconds
to wait before retrying.
content:
application/json:
schema:
$ref: '#/components/schemas/HttpError'
example:
message: Too Many Attempts.
type: HttpException
NotFound:
description: The requested resource could not be found.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorObject'
example:
error:
code: CONTACT_NOT_FOUND
message: Contact not found.
EmailDomainNotAllowed:
description: The contact's email domain is not in the account's allow list.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorObject'
example:
error:
code: EMAIL_DOMAIN_NOT_ALLOWED
message: The address john@notallowed.com has a domain that is not in your allow list.
ValidationError:
description: One or more request fields failed backend validation.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorList'
example:
error_at: '2026-06-02T13:56:31+00:00'
errors:
- identifier: ValidationError
details: One or more of the given request fields failed backend validation.
meta:
email:
- The email field is required.
Unauthenticated:
description: The access token is missing, invalid or expired.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorObject'
example:
error:
code: UNAUTHENTICATED
message: Invalid or expired access token.
ContactConflict:
description: A contact with the given email already exists.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorObject'
example:
error:
code: CONTACT_ALREADY_EXISTS
message: A contact with this email already exists.
schemas:
BulkUpsertResult:
type: object
description: The outcome of a bulk upsert operation.
properties:
created:
type: integer
description: The number of contacts created.
updated:
type: integer
description: The number of existing contacts updated.
failed:
type: integer
description: The number of contacts that failed validation or processing.
errors:
type: array
description: One entry per failed contact.
items:
type: object
properties:
index:
type: integer
description: The 0-based position of the contact in the submitted `contacts` array.
email:
type:
- string
- 'null'
description: The email of the failed contact, if available.
details:
type: object
description: A map of field name to an array of validation messages.
additionalProperties:
type: array
items:
type: string
ErrorObject:
type: object
description: The compact error shape returned for authentication, authorization and conflict errors.
properties:
error:
type: object
properties:
code:
type: string
description: A machine-readable error code.
message:
type: string
description: A human-readable description of the error.
FieldValue:
type: object
description: A single field value belonging to a contact.
properties:
field_id:
type: string
description: The ID of the field this value belongs to.
key:
type:
- string
- 'null'
description: The generated key of the field.
name:
type:
- string
- 'null'
description: The human-readable name of the field.
type:
description: The data type of the field. May be `null` if the field's metadata is unavailable.
oneOf:
- $ref: '#/components/schemas/FieldType'
- type: 'null'
value:
type: string
description: The field value, represented as a string.
PaginationMeta:
type: object
description: Pagination metadata describing the current result window.
properties:
total:
type: integer
description: The total number of matching records.
current_page:
type: integer
last_page:
type: integer
per_page:
type: integer
from:
type:
- integer
- 'null'
description: The index of the first record on the current page.
to:
type:
- integer
- 'null'
description: The index of the last record on the current page.
ContactDetail:
type: object
description: A contact with its associated field values, returned when retrieving a single contact.
allOf:
- $ref: '#/components/schemas/Contact'
- type: object
properties:
field_values:
type: array
items:
$ref: '#/components/schemas/FieldValue'
FieldValuesInput:
type: object
description: The payload used to replace a contact's field values.
required:
- field_values
properties:
field_values:
type: object
minProperties: 1
maxProperties: 20
description: |
A map of field key to scalar value. Each key must match the `key` of an existing field
(see `GET /fields`). Between 1 and 20 entries may be provided. Replaces the contact's existing
field values.
additionalProperties:
type:
- string
- number
- boolean
example:
department: Engineering
startDate: '2026-01-15'
ContactInput:
type: object
description: The payload used to create or upsert a contact.
required:
- email
- first_name
- last_name
properties:
email:
type: string
format: email
maxLength: 255
description: The contact's email address. Used as the match key for bulk upserts.
first_name:
type: string
maxLength: 255
last_name:
type: string
maxLength: 255
avatar_url:
type:
- string
- 'null'
format: uri
description: |
URL of the contact's avatar image. For `POST /contacts` this must be a valid HTTP(S) URL.
For bulk upserts (`POST /contacts/bulk`) the value is accepted as any string and is not validated
as a URL.
field_values:
type: object
description: |
A map of field key to scalar value. Each key must match the `key` of an existing field
(see `GET /fields`). Values are validated against the field's type.
additionalProperties:
type:
- string
- number
- boolean
example:
department: Engineering
startDate: '2026-01-15'
HttpError:
type: object
description: The error shape returned for rate-limit (429) errors.
properties:
message:
type: string
description: A human-readable description of the error.
type:
type: string
description: The error type. Always `HttpException` for this shape.
FieldType:
type: string
description: The data type of a field.
enum:
- string
- number
- date
- datetime
- boolean
Contact:
type: object
description: A contact as returned in list and create responses.
properties:
id:
type: string
description: The unique identifier of the contact.
first_name:
type: string
last_name:
type: string
email:
type: string
format: email
avatar_url:
type:
- string
- 'null'
format: uri
description: URL of the contact's avatar image, if set.
supervisor_id:
type:
- string
- 'null'
description: The ID of this contact's supervisor, if one is assigned.
created_at:
type:
- string
- 'null'
format: date-time
description: ISO 8601 creation timestamp.
updated_at:
type:
- string
- 'null'
format: date-time
description: ISO 8601 last-updated timestamp.
ErrorList:
type: object
description: The error shape returned for validation and not-found errors.
properties:
error_at:
type: string
format: date-time
description: ISO 8601 timestamp of when the error occurred.
errors:
type: array
items:
type: object
properties:
identifier:
type: string
description: A machine-readable identifier for the error type.
details:
type: string
description: A human-readable description of the error.
meta:
type: object
description: Additional context. For validation errors, a map of field name to validation
messages.
parameters:
PageNumber:
name: page[number]
in: query
required: false
description: The page of results to return (1-based).
schema:
type: integer
minimum: 1
default: 1
PageSize:
name: page[size]
in: query
required: false
description: The number of records per page.
schema:
type: integer
minimum: 1
maximum: 100
default: 25
ContactId:
name: id
in: path
required: true
description: The unique identifier of the contact.
schema:
type: string
example: 9b1c2d3e-4f56-7890-abcd-ef1234567890