> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chatsailer.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sincronização

> Mantenha outro sistema em dia com um workspace da Sailer: mudanças, exclusões, seus próprios ids e escritas em massa.

Um sync de mão dupla precisa de quatro coisas de uma API: ler só o que mudou,
saber o que foi excluído, uma chave que você controla e um jeito de escrever
muitos registros de uma vez. A Sailer tem as quatro para contatos, organizações
e negócios.

## Leia o que mudou

Liste com `updated_since` no momento em que sua execução anterior **começou**,
e `sort=updated_at`:

```bash theme={null}
curl "https://api.chatsailer.com/v1/contacts?updated_since=2026-09-30T00:00:00Z&sort=updated_at&limit=200" \
  -H "Authorization: Bearer $SAILER_API_TOKEN"
```

`updated_at` muda sempre que algo no registro muda — um campo padrão, um campo
personalizado ou as tags. O horário precisa ter fuso; `2026-09-30T00:00:00`
sem fuso devolve `400`.

Siga o cursor até o fim:

```python theme={null}
params = {"updated_since": last_run_started_at, "sort": "updated_at", "limit": 200}
while True:
    page = httpx.get(f"{BASE}/v1/contacts", params=params, headers=headers).json()
    for contact in page["data"]:
        upsert_locally(contact)
    if page["links"]["next"] is None:
        break
    params["cursor"] = page["links"]["next"]
```

O cursor fica ancorado no último registro que você recebeu, então um registro
criado ou editado durante a paginação nunca aparece duas vezes nem é pulado. Um
registro editado no meio do percurso simplesmente reaparece mais adiante no
mesmo percurso, com o `updated_at` novo.

Filtre uma listagem com parâmetros de query — repita um para fazer OU entre os
valores, combine diferentes para fazer E:

```bash theme={null}
# Contatos ganhos ou perdidos de uma organização
curl "https://api.chatsailer.com/v1/contacts?status=won&status=lost&organization_id=org_..."
```

Para condições aninhadas, intervalos ou filtros entre relacionamentos, use
`POST /v1/contacts/search`, que devolve os mesmos objetos.

## Leia o que foi excluído

Exclusões têm um feed próprio, das mais antigas para as mais novas:

```bash theme={null}
curl "https://api.chatsailer.com/v1/deleted-records?resource=contact&since=2026-09-30T00:00:00Z" \
  -H "Authorization: Bearer $SAILER_API_TOKEN"
```

```json theme={null}
{
  "data": [
    {
      "object": "deleted_record",
      "resource": "contact",
      "id": "con_9f2ac41d8b7e4a51b0d3e6f7a1c2d3e4",
      "external_id": "crm-48213",
      "deleted_at": "2026-09-30T14:02:11Z"
    }
  ],
  "meta": { "limit": 50, "has_more": false },
  "links": { "next": null }
}
```

Ele lista registros excluídos por esta API, pelo app da Sailer ou por um CRM
conectado. Exige o escopo de leitura do recurso, por exemplo `contacts:read`.

`DELETE /v1/contacts/{id}` (e o mesmo em organizações e negócios) tira um
registro de todas as listagens e buscas **sem apagar nada**: conversas e
histórico ficam guardados. Fazer upsert do mesmo contato, ou uma nova mensagem
dele, o restaura, e ele volta a aparecer na sua próxima leitura com
`updated_since`.

## Use seus próprios ids

Defina `external_id` com o id do seu sistema ao criar ou atualizar um registro.
Ele é único por workspace e por recurso, volta em toda leitura e pode ser usado
como filtro:

```bash theme={null}
curl "https://api.chatsailer.com/v1/contacts?external_id=crm-48213"
```

`POST /v1/contacts/upsert` casa primeiro pelo `external_id`, depois pelo
`phone`:

| O que você envia | O que acontece |
| - | - |
| Um `external_id` que nenhum contato tem, um telefone novo | Um contato é criado com o seu `external_id` (`201`) |
| Um `external_id` que um contato tem, o mesmo telefone | Esse contato é atualizado (`200`) |
| Um telefone de um contato que ainda não tem `external_id` | Esse contato é atualizado e recebe o seu `external_id` (`200`) |
| Um `external_id` de um contato e um telefone de outro | `409 external_id_conflict`: as duas chaves discordam |
| Um `external_id` de um contato com outro telefone | `409 phone_mismatch`: o upsert nunca muda um telefone |

## Escreva em massa

`POST /v1/contacts/batch` faz upsert de até 100 contatos numa chamada. Cada item
é gravado exatamente como em `POST /v1/contacts/upsert`, em ordem, e recebe o
próprio resultado — um item com falha nunca desfaz os outros:

```json theme={null}
{
  "object": "batch_result",
  "data": [
    { "index": 0, "status": "created", "contact": { "object": "contact", "id": "con_..." } },
    { "index": 1, "status": "updated", "contact": { "object": "contact", "id": "con_..." } },
    {
      "index": 2,
      "status": "failed",
      "error": { "type": "invalid_request_error", "code": "validation_failed", "message": "..." }
    }
  ],
  "created": 1,
  "updated": 1,
  "failed": 1
}
```

A chamada responde `200` sempre que o lote em si é válido; leia o `status` de
cada item. Envie um header `Idempotency-Key` para que um lote reenviado não seja
aplicado duas vezes.

## Juntando tudo

1. Anote o horário e leia as mudanças com `updated_since` no início da execução
   anterior.
2. Leia `GET /v1/deleted-records` com `since` no mesmo horário e remova esses
   registros do seu lado.
3. Envie suas mudanças com `POST /v1/contacts/batch`, pela chave `external_id`,
   com um `Idempotency-Key`.
4. Guarde o horário do passo 1 como o início desta execução.

Acompanhe `X-RateLimit-Remaining` em toda resposta e desacelere antes de chegar
a zero.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.