KasarKasar Docs
API Reference

Records

List, read, create, update and delete the records of any object, and poll the records deleted since a date.

Every route below takes an {object} path segment: the name of an object from GET /objects, always the plural (contacts, companies, opportunities, or one of your custom objects).

Endpoints and rights

Each route needs a right on the object in your integration's grant (Settings, API):

MethodPathRightSuccess
GET/objects/{object}/records{object}:read200
GET/objects/{object}/records/{id}{object}:read200
POST/objects/{object}/records{object}:write201
PUT/objects/{object}/records{object}:write201 created, 200 updated (see Upsert & idempotency)
PATCH/objects/{object}/records/{id}{object}:write200
DELETE/objects/{object}/records/{id}{object}:delete200
GET/objects/{object}/deleted?since={object}:read200
GET/objects/{object}/records/{id}/{field}{object}:read200
POST/objects/{object}/records/{id}/{field}{object}:write201 added, 200 already there
PATCH/objects/{object}/records/{id}/{field}/{entry_id}{object}:write200
DELETE/objects/{object}/records/{id}/{field}/{entry_id}{object}:write200

The last four manage a record's emails, phones and LinkedIn profile, {field} being the name of that field (emails, phones, linkedin).

A missing right is 403 FORBIDDEN_GRANT, and the message names it (This integration is not granted 'contacts:write'.). Grants are default-deny, so an object name your integration was never granted, a misspelled one included, answers the same 403. A granted object that the API does not serve answers 404 INVALID_OBJECT.

Response envelope

Every response that carries a record wraps it in data: GET, POST, PATCH and PUT alike.

{ "data": { "id": "8f14e45f-ceea-467f-a34e-3c5d5b9a6c01", "source": "referral" } }

A list adds total (only when it is computed, see Pagination) and next_cursor (only when another page exists):

{ "data": [ { "id": "8f14e45f-ceea-467f-a34e-3c5d5b9a6c01" } ], "next_cursor": "k1.WyIyMDI2LTEwLTEzVDA5OjMw" }

Errors use { "error": { "code", "message", "suggestion"? } } and the status is derived from the code: see Errors. Every response carries an X-Request-Id header to quote to support.

The record shape

What you read is the same document, key for key, whether it comes from GET, from the response of a write, or from the record of an outbound webhook. A contact, abridged:

{
  "data": {
    "id": "8f14e45f-ceea-467f-a34e-3c5d5b9a6c01",
    "name": { "first": "Jean", "last": "Dupont", "full": "Jean Dupont" },
    "emails": [
      { "id": "0b7e3c1a-5d2f-4e8b-9a6c-1f3d5b7e9a20", "email_address": "jean.dupont@acme.fr", "label": "work", "is_primary": true },
      { "id": "6d1f9b3e-2c4a-4f7d-8e5b-3a9c1e7f5d42", "email_address": "jdupont@gmail.com", "label": "home", "is_primary": false }
    ],
    "phones": [{ "id": "9e5a2c7f-1b3d-4a6e-8f0c-5d7b9a1e3c64", "phone_number": "+33612345678", "label": "mobile", "is_primary": true }],
    "linkedin": [],
    "companies": [
      {
        "id": "3b9d2f61-7a4e-4c1b-9e2d-5f8a0c6b1d47",
        "position": "Head of Sales",
        "period_start": "2024-01-15",
        "period_end": null
      }
    ],
    "source": "referral",
    "owner_id": "c2e7a9d4-1f3b-4e8a-b6c5-9d0e2f4a7b13",
    "address": {
      "address1": "12 rue de la Paix",
      "address2": null,
      "city": "Paris",
      "region": null,
      "postal_code": "75002",
      "country": "FR"
    },
    "connection_strength": 62,
    "last_interaction_date": "2026-10-10T14:05:12.000Z",
    "created_at": "2026-09-02T08:41:27.513Z",
    "updated_at": "2026-10-10T14:05:12.904Z"
  }
}

The rules behind it:

  • id is always present.
  • Only what your grant opens. By default an integration reads every field of the object, fields created later included. When the grant lists fields for an object, only those keys come out, plus id.
  • No confidential field. A field the workspace marked confidential, and any field computed from it, never comes out, whatever the grant.
  • created_at and updated_at come from the database clock. They are equal on a record just created. updated_at moves on every change of the record, including an email, a phone or a LinkedIn profile added or removed; a many-to-many link added or removed does not move it.
  • Relations carry the id of the record they point to, under the key the relation is stored at: owner_id, main_contact_id, client_company_id. No display label travels with it.
  • The inverse side of a relation is absent. A company has no opportunities key: to read a company's opportunities, list opportunities with a filter on client_company_id.
  • Many-to-many links are an array of { "id", ...link columns }, every column the link declares present (null when empty): a contact's companies above. On a relation that can point to several objects, each link also carries object, the target object's name.
  • Emails, phones, LinkedIn are arrays: [{ "id", "email_address", "label", "is_primary" }], [{ "id", "phone_number", "label", "is_primary" }], [{ "id", "profile_id", "public_identifier", "is_primary" }]. id identifies the entry on this record, for the routes that manage it. label is the nature of the entry (work, home, mobile...), null when unset. A record has one LinkedIn profile, so it is the primary one and has no label. Phones are stored in E.164 (+33612345678), emails in lower case. No value is [].
  • Composite fields are objects: a name is { first, last, full } (full is computed), an address is { address1, address2, city, region, postal_code, country }, a currency is { amount, currency_code }.
  • Pipelines expose the pipeline and the current step as ids (pipeline_id, pipeline_step_id on opportunities), and a computed status: open, won or lost, read from the current step. The key is status when the object has one pipeline and no field named status; otherwise it is <pipeline field>_status. It is read-only: write the step to change it.
  • Numbers are JSON numbers: number, integer, decimal, percent and rating fields, computed fields with a numeric result, and the amount of a currency. Never strings.
  • No empty strings. An empty value is null; an empty list is [].
  • Dates: a date-time is ISO-8601 in UTC (2026-10-10T14:05:12.000Z); a date-only field is YYYY-MM-DD.
  • External lookup fields also carry <field>_display: the value read from the external source.

GET /objects/{object} describes this shape key by key for one object: type, read-only or not, whether the value is a number, and an example of the value to write.

List records

curl "https://platform.kasar.app/api/v1/objects/contacts/records?limit=2" \
  -H "Authorization: Bearer ksr_svc_..."
{
  "data": [
    { "id": "8f14e45f-ceea-467f-a34e-3c5d5b9a6c01", "name": { "first": "Jean", "last": "Dupont", "full": "Jean Dupont" }, "source": "referral" },
    { "id": "2d6f8a0c-4e1b-4a3c-9d5e-7f9a1b3c5d70", "name": { "first": "Claire", "last": "Martin", "full": "Claire Martin" }, "source": null }
  ],
  "next_cursor": "k1.WyIyMDI2LTEwLTEzVDA5OjMw"
}

Query parameters

ParameterTypeDescription
limitintegerPage size, 1 to 100, default 20. Outside that range: 400 INVALID_PARAMETER, never a silently truncated page.
cursorstringThe next_cursor of the previous page.
include_totaltrue | falseAsk for total on a list sorted by creation date. Any other value: 400 INVALID_PARAMETER.
filtersJSONA filter group, see Filters.
sortJSONAn array of { "field", "direction" }, see Sort.
sort_by / sort_dirstringA single sort; sort_dir is asc or desc (anything else: 400 INVALID_PARAMETER). sort wins when both are sent.
searchstringText search over the object's searchable fields.

Pagination

Follow next_cursor until it is absent from the response. The cursor is opaque: pass it back as received. Which pagination you get depends on the query.

Sorted by creation date (the default: no search, and no sort other than created_at). Pages are cut by key: no record is skipped or repeated when records are created or deleted between two pages, and there is no depth limit, so this is the way to read a whole object. Newest first by default; sort_dir=asc reads oldest first. total is returned only with include_total=true, since it counts the whole list. The cursor is bound to its object, its filters and its direction: sent with different filters or another direction, it is 400 INVALID_CURSOR.

Custom sort, or search. Pages are cut by position, and total is always returned. Paging past 10 000 rows is 400 DEEP_PAGINATION_LIMIT: narrow the query with filters instead. The cursor only carries a position, so keep the same parameters from one page to the next.

A cursor from one mode sent to a query of the other mode is 400 INVALID_CURSOR, as is a cursor that is not one of ours.

Filters

filters is a JSON group: { "type": "AND" | "OR", "conditions": [...] }. A condition is { "field", "operator", "value" }, and a condition can itself be a group. Contacts from a referral or an event, with at least one recorded interaction:

curl -G "https://platform.kasar.app/api/v1/objects/contacts/records" \
  -H "Authorization: Bearer ksr_svc_..." \
  --data-urlencode 'filters={"type":"AND","conditions":[{"type":"OR","conditions":[{"field":"source","operator":"equals","value":"referral"},{"field":"source","operator":"equals","value":"event"}]},{"field":"last_interaction_date","operator":"is_not_null","value":null}]}'

field is a field name of the object, or the key a relation is stored at (owner_id, client_company_id).

FamilyOperators
Textequals, not_equals, contains, starts_with, ends_with, ilike
Numbergreater_than, less_than, greater_equal, less_equal, between
Datedate_equals, date_before, date_after, date_between
Relative datedate_today, date_yesterday, date_this_week, date_last_week, date_this_month, date_last_month, date_this_quarter, date_last_quarter, date_this_year, date_last_year, date_last_n_days, date_next_n_days
Selectin, not_in, is_selected, is_not_selected
To-many relationcount_eq, count_gt, count_gte, count_lt, count_lte, count_between, contains_all
Booleanis_true, is_false
Emptyis_null, is_not_null, is_empty, is_not_empty

A negative operator (not_equals, not_in, is_not_selected) also returns the records whose field is empty: status not_equals customer includes the records without a status. On an email or phone field, not_equals returns the records where no address equals the value.

A relation to several records (a contact's companies, a company's deals) is a list of links. A condition on its links is { "relation", "match", "where" }: where is a group on the fields of the linked record, and every condition in it is judged on the same link. match is some (at least one link satisfies where) or none (no link does). On a relation that carries data of its own (a position, a period), link.<column> reads that data: link.position, link.period_start, link.period_end.

Contacts who were CEO of a company in the software industry:

curl -G "https://platform.kasar.app/api/v1/objects/contacts/records" \
  -H "Authorization: Bearer ksr_svc_..." \
  --data-urlencode 'filters={"type":"AND","conditions":[{"relation":"companies","match":"some","where":{"type":"AND","conditions":[{"field":"link.position","operator":"equals","value":"CEO"},{"field":"industry","operator":"equals","value":"software"}]}}]}'

A flat key through such a relation is a shorthand for a one-condition group: companies.industry equals software reads as some link whose company is in software, and a negative operator reads as none: companies.industry not_equals software returns the contacts without any company in software. Two flat keys may be satisfied by two different links; put them in one where to judge them on the same link.

A linked record your integration cannot read never satisfies a condition.

is_empty works on every field type: no value, an empty text, or an empty list.

A filter that cannot be applied is refused, never dropped:

  • filters that is not valid JSON, or not a group of that shape: 400 INVALID_PARAMETER
  • a field the object does not have, that your integration cannot read (outside its field list, or confidential), or that cannot be filtered: 400 INVALID_QUERY_FIELD, naming the field. The first two answer the same: an error never tells that a field exists.
  • an unknown operator, or one the field's type cannot carry (a text operator on a number, a date or a boolean; an ordering operator on a multi-select or a boolean): 400 INVALID_FILTER_OPERATOR, naming the field
  • a value that does not fit its field (text against an id, an unreadable date): 400 INVALID_FILTER_VALUE

Polling changes

To keep a copy up to date without webhooks, list the records changed since your last poll with a filter on updated_at (date_after), sorted by updated_at. updated_at is the time the writing transaction started, not the time it became visible: a change can appear a few seconds after a later one. Overlap each window with the previous one by a minute, and skip a record whose id and updated_at you already applied. Deletions are listed by /deleted.

Sort

curl -G "https://platform.kasar.app/api/v1/objects/contacts/records" \
  -H "Authorization: Bearer ksr_svc_..." \
  --data-urlencode 'sort=[{"field":"last_interaction_date","direction":"desc"}]'

sort takes several entries, applied in order; direction is asc or desc, and desc when omitted. Records with equal sort values are ordered by id, so pages are stable. A sort that is not an array of { "field" } is 400 INVALID_PARAMETER; a field that does not exist, that your integration cannot read, or that cannot be sorted is 400 INVALID_QUERY_FIELD.

A sort on created_at alone keeps the pagination by key; any other sort switches to pagination by position.

curl "https://platform.kasar.app/api/v1/objects/contacts/records?search=dupont" \
  -H "Authorization: Bearer ksr_svc_..."

search matches the object's searchable fields (a contact's full name, for instance), within the fields your integration can read. It combines with filters, and it always uses pagination by position.

Get a record

curl https://platform.kasar.app/api/v1/objects/contacts/records/8f14e45f-ceea-467f-a34e-3c5d5b9a6c01 \
  -H "Authorization: Bearer ksr_svc_..."

Returns { "data": <record> } in the record shape. An id that does not exist, or that is not a UUID, is 404 RECORD_NOT_FOUND.

Create a record

curl -X POST https://platform.kasar.app/api/v1/objects/contacts/records \
  -H "Authorization: Bearer ksr_svc_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": { "first": "Jean", "last": "Dupont" },
    "emails": ["jean.dupont@acme.fr"],
    "companies": [{ "id": "3b9d2f61-7a4e-4c1b-9e2d-5f8a0c6b1d47", "position": "Head of Sales" }],
    "source": "referral"
  }'

Returns 201 and { "data": <record> }: the record read back after the write, emails, links and computed fields included, exactly as a GET would return it. When your grant can write the object but not read it, the record is created and data is null.

POST accepts an Idempotency-Key header for safe retries: see Upsert & idempotency. A create whose identity is already taken (a contact with the email of an existing contact) is refused with 409 DUPLICATE_RECORD; to create or update in one call, use PUT.

An API write goes through the same write path as the app: field validation, automations triggered by a create or an update, and outbound webhooks all run as if the record had been saved on screen. A write that changes nothing (the same values sent back) triggers no automation and no webhook.

Writing values

The body is a JSON object of values, keyed as the record shape names them.

ValueWhat to send
RelationThe id under its key: "main_contact_id": "8f14e45f-ceea-467f-a34e-3c5d5b9a6c01". null clears it.
Many-to-many[{ "id": "<uuid>" }], with link columns when you want to set them: [{ "id": "<uuid>", "position": "CTO" }]. On a relation to several objects, each link needs object. On a relation to a single object, an array of ids (["<uuid>"]) is accepted too.
Emails, phonesOne value ("jean@acme.fr"), an array of values (["jean@acme.fr"]), or the shape you read ([{ "email_address": "jean@acme.fr", "label": "work", "is_primary": true }]; id is ignored). Same for phones with phone_number. A value that already identifies another record is refused.
LinkedInThe profile URL, the public identifier or the profile id: "linkedin": "https://www.linkedin.com/in/jean-dupont". A record has one profile: writing one replaces it.
CompositeThe object: "name": { "first": "Jean", "last": "Dupont" }.
Pipeline stepWrite the step id (pipeline_step_id) to move the record, with pipeline_id to move it to another pipeline. The step must belong to the pipeline; a record created without a step starts at the first step of its pipeline. status cannot be written.

What you read can be sent back for links and contact points. The read-only keys must be left out of a write, or it is refused with READONLY_FIELD: id, created_at, updated_at, created_by_id, the pipeline status, and computed fields.

What is refused

Nothing is written when a body is refused.

SituationResponse
Empty body, invalid JSON, or a body that is not a JSON object400 INVALID_BODY
Body over 256 KB413 PAYLOAD_TOO_LARGE
A key the object does not have, or one your integration cannot read (outside its field list, or confidential)422 UNKNOWN_FIELD, naming it: the two answer the same
A read-only key (system field, computed field, pipeline status)422 READONLY_FIELD
A select value outside the field's options422 INVALID_ENUM_VALUE, with available_values
A wrong type or format, an invalid email or phone number, a required field left empty, or a value in a shape the field does not accept ("emails": [{ "email": "..." }])422 VALIDATION_ERROR, with field_errors
A relation id, a link, a pipeline or a pipeline step that points to no record (or, for a many-to-many link, to a record your integration cannot read)422 INVALID_REFERENCE
A pipeline step that does not belong to the pipeline of the record422 VALIDATION_ERROR
A move to a pipeline step whose required fields are empty422 MISSING_REQUIRED_FIELDS, with field_errors
A field your integration reads but cannot write (a pipeline whose status alone was chosen)403 PERMISSION_DENIED
An identity, or a unique value, already used by another record: on a create, or an email, a phone or a LinkedIn profile added to a record that identifies another one409 DUPLICATE_RECORD, with existing_id when your integration can read that record
The workspace's plan limit for the object is reached403 PLAN_LIMIT_REACHED
{
  "error": {
    "code": "DUPLICATE_RECORD",
    "message": "A record already exists with the same value for: emails",
    "existing_id": "8f14e45f-ceea-467f-a34e-3c5d5b9a6c01"
  }
}

Update a record

curl -X PATCH https://platform.kasar.app/api/v1/objects/opportunities/records/5b0e7c2a-9f1d-4c3e-8a6b-2d4f6e8a1c3b \
  -H "Authorization: Bearer ksr_svc_..." \
  -H "Content-Type: application/json" \
  -d '{ "pipeline_step_id": "7c3d5e9f-2a4b-4c6d-8e0f-1a2b3c4d5e6f", "main_contact_id": null }'
{
  "data": {
    "id": "5b0e7c2a-9f1d-4c3e-8a6b-2d4f6e8a1c3b",
    "name": "Annual licence renewal",
    "client_company_id": "3b9d2f61-7a4e-4c1b-9e2d-5f8a0c6b1d47",
    "main_contact_id": null,
    "pipeline_id": "e4a1c3b5-6d7f-4a2b-8c9d-0e1f2a3b4c5d",
    "pipeline_step_id": "7c3d5e9f-2a4b-4c6d-8e0f-1a2b3c4d5e6f",
    "status": "won",
    "expected_close_date": "2026-11-30",
    "updated_at": "2026-10-13T09:30:00.412Z"
  }
}

PATCH is partial and returns 200 with the whole record read back (abridged above), like POST. The same values are accepted and the same refusals apply, plus:

  • a field absent from the body is left untouched; a field set to null is cleared;
  • many-to-many: the list you send replaces the record's links. [] or null removes them. A link sent as { "id" } alone keeps its link columns; sending columns rewrites them. Links to records your integration cannot read are kept;
  • emails and phones are added, never removed: values you send that the record does not have are added, and a shorter list, [] or null removes nothing. "is_primary": true on a value makes it the primary one; a label is written with a value added, not on one already there. To remove an entry or change its label, see Emails, phones and LinkedIn;
  • an id in the path that does not exist, or that is not a UUID, is 404 RECORD_NOT_FOUND.

PATCH and DELETE do not read an idempotency key.

Emails, phones and LinkedIn

A record's emails, phones and LinkedIn profile each have a route, {field} being the field's name. Writing the record adds values without removing any; these routes list the entries, add one, make one the primary, change its label, and remove one. The id of an entry is the one the record carries.

The field must be one your integration reads: a field outside its field list, or confidential, answers 404 FIELD_NOT_FOUND, like a field that does not exist. Reading needs {object}:read, every other gesture {object}:write. Each gesture that changes something emits an updated webhook for the record, with the field in changed_fields.

List the entries

curl https://platform.kasar.app/api/v1/objects/contacts/records/8f14e45f-ceea-467f-a34e-3c5d5b9a6c01/emails \
  -H "Authorization: Bearer ksr_svc_..."
{
  "data": [
    { "id": "0b7e3c1a-5d2f-4e8b-9a6c-1f3d5b7e9a20", "email_address": "jean.dupont@acme.fr", "label": "work", "is_primary": true },
    { "id": "6d1f9b3e-2c4a-4f7d-8e5b-3a9c1e7f5d42", "email_address": "jdupont@gmail.com", "label": "home", "is_primary": false }
  ]
}

The primary entry comes first, then the others from the oldest.

Add an entry

curl -X POST https://platform.kasar.app/api/v1/objects/contacts/records/8f14e45f-ceea-467f-a34e-3c5d5b9a6c01/emails \
  -H "Authorization: Bearer ksr_svc_..." \
  -H "Content-Type: application/json" \
  -d '{ "email_address": "Jean.Dupont@Acme-Group.com", "label": "work", "is_primary": true }'
{ "data": { "id": "3c8a6e2f-9d1b-4f5a-8e7c-2b4d6f8a0e19", "email_address": "jean.dupont@acme-group.com", "label": "work", "is_primary": true } }

The body is the entry in the shape you read, without id: email_address, phone_number or profile_id (a profile URL, its public identifier or its id), plus, on emails and phones, an optional label and is_primary.

  • 201 and the entry when it is added. The first email (or phone) of a record becomes its primary one; "is_primary": true makes the new one primary.
  • 200 and the existing entry when the record already has this value: nothing is written, so a retried request does no harm. Its label and primary flag are left as they are; change them with PATCH.
  • LinkedIn: a record has one profile, and adding one replaces it.
  • POST accepts an Idempotency-Key header, scoped to the record and the field.

Change an entry

curl -X PATCH https://platform.kasar.app/api/v1/objects/contacts/records/8f14e45f-ceea-467f-a34e-3c5d5b9a6c01/emails/6d1f9b3e-2c4a-4f7d-8e5b-3a9c1e7f5d42 \
  -H "Authorization: Bearer ksr_svc_..." \
  -H "Content-Type: application/json" \
  -d '{ "is_primary": true, "label": "work" }'

Returns 200 and the entry. Send "is_primary": true, a label (null clears it), or both. A record always has a primary entry, so "is_primary": false is refused: make another entry the primary instead. The value itself does not change here: add the right one and remove the wrong one. A LinkedIn profile has neither a label nor a primary flag to change.

Remove an entry

curl -X DELETE https://platform.kasar.app/api/v1/objects/contacts/records/8f14e45f-ceea-467f-a34e-3c5d5b9a6c01/emails/0b7e3c1a-5d2f-4e8b-9a6c-1f3d5b7e9a20 \
  -H "Authorization: Bearer ksr_svc_..."
{ "data": { "id": "0b7e3c1a-5d2f-4e8b-9a6c-1f3d5b7e9a20", "deleted": true, "promoted_id": "6d1f9b3e-2c4a-4f7d-8e5b-3a9c1e7f5d42" } }

Removing the primary entry makes the oldest remaining one primary, and promoted_id names it; it is null when nothing was promoted.

What is refused

SituationResponse
{field} is not an email, phone or LinkedIn field of the object, or your integration cannot read it404 FIELD_NOT_FOUND
The record does not exist, or its id is not a UUID404 RECORD_NOT_FOUND
The entry does not exist, or belongs to another record404 NOT_FOUND
The value is missing or invalid, a label outside the field's labels (the message lists them), "is_primary": false, a label or a primary flag on LinkedIn, or another key in the body422 VALIDATION_ERROR, with field_errors
The value already identifies another record (a contact's email, phone or profile)409 DUPLICATE_RECORD, with existing_id when your integration can read that record
The field is one your integration reads but cannot write403 PERMISSION_DENIED

Delete a record

curl -X DELETE https://platform.kasar.app/api/v1/objects/contacts/records/8f14e45f-ceea-467f-a34e-3c5d5b9a6c01 \
  -H "Authorization: Bearer ksr_svc_..."
{ "data": { "id": "8f14e45f-ceea-467f-a34e-3c5d5b9a6c01", "deleted": true } }

A delete is permanent: there is no trash to restore from. The deleted record then appears in GET /objects/{object}/deleted.

  • An id that does not exist, or that is not a UUID: 404 RECORD_NOT_FOUND.
  • Other records point to this one through a required relation: 409 DELETE_BLOCKED. The message counts them (Blocked by required references: 2 Opportunities (Client company)); reassign or delete them first, then retry.

List deleted records

A system that keeps a copy of your records has no other way to learn that one disappeared. GET /objects/{object}/deleted returns the records of the object deleted since a date, oldest deletion first:

curl -G "https://platform.kasar.app/api/v1/objects/contacts/deleted" \
  -H "Authorization: Bearer ksr_svc_..." \
  --data-urlencode "since=2026-10-01T00:00:00Z"
{
  "data": [
    { "id": "2d6f8a0c-4e1b-4a3c-9d5e-7f9a1b3c5d70", "deleted_at": "2026-10-02T08:14:03.512Z", "merged_into": null },
    { "id": "9a1c3e5f-7b2d-4f6a-8c0e-2b4d6f8a0c1e", "deleted_at": "2026-10-06T16:47:55.081Z", "merged_into": "8f14e45f-ceea-467f-a34e-3c5d5b9a6c01" }
  ],
  "next_cursor": "k1.WyIyMDI2LTEwLTA2IDE2OjQ3"
}
KeyDescription
idThe deleted record's id.
deleted_atWhen it was deleted, ISO-8601 UTC.
merged_intoThe id of the record that absorbed it when the deletion was a duplicate merge, null otherwise.
ParameterDescription
sinceRequired. An ISO-8601 date-time, within the last 30 days.
limit1 to 100, default 100.
cursorThe next_cursor of the previous page. It is bound to the object and to since.

Deletions are kept 30 days. Poll at least that often; if you missed the window, read the object again in full with GET /records, then resume polling from that point.

SituationResponse
since missing or not a date400 INVALID_PARAMETER
since older than 30 days400 INVALID_PARAMETER, with the resynchronisation advice in suggestion
limit outside 1 to 100400 INVALID_PARAMETER
A cursor from another object or another since, or not one of ours400 INVALID_CURSOR

On this page