Errors
The error envelope, every error code with its HTTP status and when it happens, and which errors are worth retrying.
Every error the API answers uses the same envelope, and its HTTP status follows from the machine-readable code:
{
"error": {
"code": "INVALID_ENUM_VALUE",
"message": "Invalid value(s) for 'type': prospect. Allowed values: new_business, upsell, cross_sell, renewal, migration.",
"available_values": ["new_business", "upsell", "cross_sell", "renewal", "migration"]
}
}| Member | Always | Meaning |
|---|---|---|
code | yes | Machine-readable code, listed below. Branch on it. |
message | yes | For people. Its wording can change, and a few messages follow the request's Accept-Language (English by default). Never parse it. |
suggestion | no | What to do next |
available_values | no | The allowed values, on INVALID_ENUM_VALUE |
field_errors | no | [{ "field", "message" }], one entry per refused field |
existing_id | no | On DUPLICATE_RECORD: the record that already holds the value, when the integration can read it |
matching_ids | no | On AMBIGUOUS_IDENTITY: every record the identity matches |
Timing never travels in the body: when the API wants you to wait, it says so in the standard Retry-After header (seconds).
X-Request-Id
Every response carries an X-Request-Id header, errors included: a unique id for that request. Log it with the request on your side, and quote it when you contact support.
Codes by status
A code that is not listed in a status below is a 400. If you meet a code you do not know, act on its HTTP status.
400 Bad Request
| Code | When |
|---|---|
INVALID_BODY | The body is missing, is not valid JSON, or is not a JSON object |
INVALID_PARAMETER | A query parameter has the wrong shape: limit outside 1 to 100, sort_dir other than asc or desc, include_total other than true or false, filters that is not a JSON filter group, sort that is not a JSON array of { "field", "direction" }, since missing, unreadable, or older than 30 days. Also sent when Idempotency-Key and X-Idempotency-Key carry different values. |
INVALID_CURSOR | The cursor is not one the API issued. Also: a cursor of a list sorted by creation date (or of /deleted) replayed with other filters, another direction, another object or another since; or such a cursor sent to a search or a custom sort. |
DEEP_PAGINATION_LIMIT | A search or a custom sort paged past 10,000 rows. Narrow the query with filters. |
INVALID_QUERY_FIELD | A filter, a sort or a grouping names a field the object does not have, a field the integration cannot read (outside its field list, or confidential), or one that cannot be filtered or sorted. These answer the same, so an error never tells that a field exists. The message names it. |
INVALID_FILTER_OPERATOR | A filter uses an unknown operator, or one the field's type cannot carry (contains on a boolean, greater_than on a multi-select…). The message names the field. |
INVALID_FILTER_VALUE | A filter value does not fit its field (text on a date, a non-UUID on a relation...) |
ORGANIZATION_HEADER_NOT_SUPPORTED | The request carries X-Kasar-Organization. An integration belongs to one workspace: remove the header. |
DESTINATION_LIMIT_REACHED | The workspace already has the maximum number of webhook destinations |
401 Unauthorized
| Code | When |
|---|---|
UNAUTHENTICATED | No Authorization: Bearer ... header |
INVALID_TOKEN | The key is malformed, unknown, closed or expired, or its integration was revoked. One code for all, on purpose. |
403 Forbidden
| Code | When |
|---|---|
FORBIDDEN_GRANT | The integration lacks the right the route needs. The message names it: contacts:write, schema:read... |
PERMISSION_DENIED | A write touches a field the integration can read but not write: a pipeline whose status alone was chosen. The message names the field. A field it cannot read at all is 422 UNKNOWN_FIELD. |
WORKSPACE_INACCESSIBLE | The integration's workspace no longer exists |
PLAN_LIMIT_REACHED | The workspace plan's limit is reached (records of an object...). suggestion gives the upgrade path when there is one. |
PLAN_FEATURE_NOT_INCLUDED | The workspace plan does not include the feature the request needs |
404 Not Found
| Code | When |
|---|---|
INVALID_OBJECT | On the schema routes, an object that does not exist, that the API does not serve, or that the integration cannot read: the three answer the same. On the record routes, an object that is granted but not served by the API. |
RECORD_NOT_FOUND | The record in the path does not exist, or its id is not a UUID. A DELETE of a record already deleted answers it too. |
FIELD_NOT_FOUND | The {field} of /records/{id}/{field} is not an email, phone or LinkedIn field of the object, or one the integration cannot read: the two answer the same. suggestion lists the ones it can. |
NOT_FOUND | A webhook destination or a delivery that does not exist; an email, phone or LinkedIn entry that does not exist or belongs to another record; or an unknown action segment in the path (the message lists the valid ones) |
409 Conflict
| Code | When |
|---|---|
DUPLICATE_RECORD | The write would create a second record with the same identity (a contact's email, a company's domain...) or the same unique value, or it adds to a record an email, a phone or a LinkedIn profile that identifies another record. existing_id names that record when the integration can read it. |
AMBIGUOUS_IDENTITY | A PUT upsert matches several records. Nothing is written; matching_ids lists them. |
DELETE_BLOCKED | The record cannot be deleted: records point to it through a required relation. The message names them. |
IDEMPOTENCY_KEY_REUSED | The idempotency key was already used, in the last 24 hours, with a different request |
IDEMPOTENCY_KEY_IN_PROGRESS | A request with the same idempotency key is still running. Comes with Retry-After: 2. |
DESTINATION_IN_USE | The webhook destination is used by published workflows, named in the message |
CONFLICT | The resource is not in the state the gesture needs: a delivery that is not abandoned, and similar |
413 Payload Too Large
| Code | When |
|---|---|
PAYLOAD_TOO_LARGE | The body is over 256 KB (262,144 bytes). Refused before it is read when Content-Length announces it. |
422 Unprocessable Entity
| Code | When |
|---|---|
VALIDATION_ERROR | One or more values are refused: a malformed email, a value of the wrong type, a missing required value, a body that does not match the expected shape, a pipeline step that does not belong to the pipeline written with it. Details in field_errors. |
UNKNOWN_FIELD | The body names a key the object does not have, or one the integration cannot read (outside its field list, or confidential): the two answer the same. The message names it. |
READONLY_FIELD | The body names a read-only key: id, created_at, updated_at, a computed field, a pipeline status... Leave them out of writes, even unchanged. |
INVALID_ENUM_VALUE | A select value outside the field's options. available_values lists the allowed ones. |
INVALID_REFERENCE | A record cited in the body (a relation id, a link, a pipeline or a pipeline step) does not exist. For a link of a many-to-many field, also a record the integration cannot read: the two answer the same. Same code on create, update and upsert. |
MISSING_REQUIRED_FIELDS | Moving a record to this pipeline step requires fields that are empty. field_errors lists them. |
IDENTITY_KEY_NOT_SATISFIED | A PUT upsert body does not fully provide any identity key of the object, or the object has no identity rule |
429 Too Many Requests
| Code | When |
|---|---|
RATE_LIMITED | A rate limit is exceeded. Retry-After gives the wait, X-RateLimit-Policy the limit. See Rate limits. |
500 Internal Server Error
| Code | When |
|---|---|
WRITE_FAILED | A write failed for a reason that is not a refusal of your data, or a delete failed for another reason than a required relation |
INTERNAL_ERROR | An unexpected error. A database error is reported as "A database error occurred.", without its SQL. |
503 Service Unavailable
| Code | When |
|---|---|
SERVICE_UNAVAILABLE | Kasar could not check the key because its database is unavailable. Comes with Retry-After: 5. Your key is still valid. |
Examples
A duplicate, with the record that holds the value:
{
"error": {
"code": "DUPLICATE_RECORD",
"message": "A record already exists with the same value for: emails",
"existing_id": "3c1d6f0a-8b2e-4f57-9a61-0e4b7c2d5f18"
}
}An ambiguous upsert:
{
"error": {
"code": "AMBIGUOUS_IDENTITY",
"message": "2 records match emails: none was written.",
"suggestion": "Merge the duplicates, or update the right one by id (PATCH).",
"matching_ids": ["3c1d6f0a-8b2e-4f57-9a61-0e4b7c2d5f18", "9e0b2a47-1d3c-4f86-b5a2-7c6e1f0d3b94"]
}
}What the API will not tell you
Some silences are deliberate:
401 INVALID_TOKENnever says whether a key was unknown, closed or expired.- On the schema routes, an object the integration cannot read answers
404 INVALID_OBJECTexactly like one that does not exist. - On the record routes, an object the integration was not granted answers
403 FORBIDDEN_GRANT, whether it exists or not. DUPLICATE_RECORDcarriesexisting_idonly when the integration can read that record.- A filter or a sort on a field the integration cannot read answers
INVALID_QUERY_FIELD, like a field that does not exist; writing it answersUNKNOWN_FIELD, like a misspelled key. A many-to-many link to a record it cannot read answersINVALID_REFERENCE, like a record that does not exist.
A path that is not an API route, or a method a route does not define, is answered 404 or 405 by the web server, outside this envelope.
Retrying
| Answer | Retry? |
|---|---|
429 RATE_LIMITED | Yes, after Retry-After seconds |
503 SERVICE_UNAVAILABLE | Yes, after Retry-After seconds (5) |
409 IDEMPOTENCY_KEY_IN_PROGRESS | Yes, after Retry-After seconds (2), with the same key and the same body: you then receive the stored response of the first request |
500 WRITE_FAILED, 500 INTERNAL_ERROR, network errors, timeouts | Yes, with exponential backoff and jitter. For a create, retry safely: see below. |
409 DUPLICATE_RECORD | Not as is: on a create, update existing_id with PATCH, or write with PUT (upsert by identity); on an added email, phone or profile, the value belongs to existing_id: merge the two records in Kasar, or leave the value where it is |
409 AMBIGUOUS_IDENTITY | Not as is: merge the duplicates in Kasar, or PATCH the right record by id |
404 RECORD_NOT_FOUND on a retried DELETE | No: the first attempt deleted the record |
Any other 4xx | No: the request itself is wrong. Fix it first. |
Retrying a write safely:
POSTandPUTaccept anIdempotency-Keyheader (orX-Idempotency-Key). If the first request succeeded and you lost its response, a retry with the same key and body replays the stored response, withX-Idempotency-Replayed: true, and writes nothing. An error response is not stored: a retry with the same key runs the request again.PUTrecognizes an existing record by its identity, so a retried upsert updates the record the first attempt created.PATCHsets the same values again.
Details: Upsert and idempotency.
Webhooks
Outbound webhooks. Subscribe a destination to record and business events, verify Standard Webhooks signatures, handle retries, and manage destinations through the API.
Rate Limits
The ceilings that apply to the API and to inbound webhooks, how they are counted, the headers that describe them, and how to back off.