Contatos

Contatos representam clientes (customer) e fornecedores (supplier) da sua conta. Para cobranças individuais (billing_issue_type=individual), o contact_id do cliente é obrigatório.

Veja conventions.md para paginação, autenticação, erros, rate limit e regras de isolamento entre contas.

Modelo

CampoTipoObservações
idinteger
contact_typestring"customer" ou "supplier". O tipo dependent existe no Sacador mas não é acessível pela API
codestring | nullIdentificador livre, até 20 chars
activebooleanDefault true
emailstring | nullFormato de email válido, até 160 chars
legal_entitybooleantrue = pessoa jurídica (CNPJ); false = pessoa física (CPF)
full_namestringPessoa física: obrigatório, até 255 chars, aceita apenas letras e espaços (sem números, sem pontuação). Pessoa jurídica: opcional
company_namestringObrigatório se legal_entity=true, até 255 chars. Aceita letras, números, espaço e / & , . : -
trademarkstringNome fantasia. Obrigatório se legal_entity=true, até 255 chars, mesmos caracteres de company_name
birth_datedate | nullOpcional. Se enviada, a pessoa precisa ter ≥ 18 anos
cpfstringPessoa física: obrigatório, exatamente 11 dígitos, CPF válido. Único por (conta, contact_type)
cnpjstringPessoa jurídica: obrigatório, 14 caracteres (números ou letras A-Z; CNPJ alfanumérico válido a partir de jul/2026). Único por (conta, contact_type)
business_phone_area, business_phone_numberstringDDD 2 dígitos; número 8 ou 9 dígitos
cell_phone_area, cell_phone_numberstringIdem
address_postal_codestringCEP com 9 caracteres (formato 00000-000)
address_streetstring5..255 chars
address_numberstring1..10 chars
address_complementstringaté 255 chars
address_districtstring3..255 chars
address_citystring2..255 chars
address_statestringSigla de UF (SP, RJ, …). Normalizada para maiúsculas no servidor
observationstring | nullTexto livre
external_idstring | nullIdentificador do contato no seu sistema, até 60 chars. Único por conta. Espaços nas pontas são removidos; enviar em branco apaga o valor. É por ele que créditos e a baixa de fiado por repasse localizam o contato. Ver Fiado e créditos do PDV
on_account_limitdecimal | nullLimite de fiado do cliente no PDV, em reais. null tem significado diferente conforme o contato tenha ou não external_id, ver Limite de fiado
store_credit_balancedecimalSomente leitura. Saldo atual de créditos do cliente. Ignorado se enviado no POST/PATCH
on_account_balancedecimalSomente leitura. Fiado em aberto: soma do due_amount das vendas concluídas do contato que ainda têm saldo a pagar. "0.0" quando não há
on_account_allowedbooleanSomente leitura. Se o contato pode comprar fiado no PDV conforme as tags. false só quando a conta restringe o fiado por tag e o contato não tem nenhuma dessas tags. Ver Fiado restrito por tag
descriptionstringDerivado: company_name se PJ, full_name se PF. Não enviar — calculado pelo Sacador
documentstringDerivado: cnpj se PJ, cpf se PF
tagsarrayTags aplicadas ao contato, cada uma {id, name, color}. Definidas via tag_ids — ver tags
custom_fieldsarrayCampos personalizados preenchidos, cada um {key, label, value}. Somente leitura — ver Campos personalizados
created_at, updated_atISO-8601

Regras de PF vs PJ

O campo legal_entity controla quais validações são aplicadas:

  • legal_entity=false (PF): exige cpf, exige full_name no formato regex; cnpj e company_name são opcionais/ignorados na validação cruzada.
  • legal_entity=true (PJ): exige cnpj, company_name e trademark.

Em ambos os casos, endereço completo é obrigatório (CEP, rua, número, bairro, cidade, UF) e o telefone (qualquer um dos pares) é opcional. A exceção é o contato com external_id, criado por integração: ele pode vir sem nenhum campo de endereço. Se vier com parte do endereço, precisa vir completo, e um endereço já gravado não pode ser apagado, porque os boletos emitidos para o contato copiam o endereço dele. Sem endereço, o contato compra no PDV, mas não recebe boleto até ter um endereço completo. email é opcional, mas sem ele o contato não recebe lembrete de cobrança por e-mail.

Fiado e créditos do PDV

Estes campos ligam o contato ao PDV (ponto de venda) do Sacador:

CampoQuem gravaPara que serve
external_idVocêVincula o contato ao cadastro do seu sistema
on_account_limitVocê (ou a loja, pela tela)Teto do fiado do cliente
store_credit_balanceO SacadorSaldo de créditos, movimentado por /v1/store_credits e pelas vendas
on_account_balanceO SacadorFiado em aberto, movimentado pelas vendas, pelos recebimentos e pela baixa por repasse
tag_idsVocê (ou a loja, pela tela)Libera o fiado quando a conta o restringe por tag, ver Fiado restrito por tag
on_account_allowedO SacadorResultado da regra de tags para o contato

Para vincular um cliente que a loja já tinha cadastrado, localize-o pelo documento com GET/v1/contacts?document= e grave o external_id com um PATCH. Depois disso, use GET/v1/contacts?external_id= para encontrá-lo.

Limite de fiado

No PDV, uma venda só pode deixar no fiado o que couber no limite: o fiado em aberto do cliente (on_account_balance) somado ao fiado da nova venda não pode passar de on_account_limit. Se passar, o PDV recusa a finalização e mostra quanto ainda está disponível.

on_account_limitContato sem external_idContato com external_id
Valor (ex.: "300.0")Limite de R$ 300,00Limite de R$ 300,00
"0.0"Não compra fiadoNão compra fiado
nullSem limiteVale como zero: não compra fiado

A diferença no null é proposital: um contato vinculado ao seu sistema só compra fiado depois que você envia um limite para ele.

Fiado restrito por tag

A loja pode marcar tags como "Permite fiado e uso de créditos" (campo allows_on_account da tag). Enquanto nenhuma tag da conta estiver marcada, nada muda: vale só o limite acima. Quando existe ao menos uma tag marcada, o fiado fica restrito:

  • Só o contato que tem ao menos uma tag marcada pode finalizar uma venda com valor em aberto. Para os demais, on_account_allowed é false e o PDV recusa a finalização.
  • A tag libera, o limite define o teto. Para quem tem a tag, a tabela de Limite de fiado continua valendo como está: um contato com external_id e sem limite enviado continua sem comprar fiado. Um contato sem external_id e sem limite, mas com a tag, compra fiado sem teto.
  • A regra barra só fiado novo. Fiado já em aberto, recebimentos, baixa por repasse e devoluções não mudam.
  • Créditos seguem a mesma regra. Sem uma tag marcada, o contato não paga com créditos no PDV nem recebe devolução em créditos. O saldo dele fica intocado e volta a valer quando a tag voltar. A inclusão de crédito pela API continua aceita.

Para liberar ou retirar o fiado e os créditos de um contato, envie as tags dele com tag_ids no POST/PATCH. Lembre que tag_ids substitui o conjunto inteiro de tags do contato.

Formato de envio. on_account_limit é lido em reais, nunca em centavos. Aceita número JSON (250, 99.9) ou string com ponto decimal ("250", "99.90"), com no máximo duas casas decimais e nunca negativo. null ou "" apagam o limite. Vírgula ("1.000,00") é recusada com 422:

{
  "status": 422,
  "errors": ["on_account_limit deve ser um número com até duas casas decimais, separadas por ponto (ex.: 5.00)."]
}

Campos personalizados

Cada conta pode definir campos próprios de cadastro de contato (matrícula, placa do veículo, número do apartamento, turma, o que fizer sentido para o negócio). Eles são criados na tela Configurações → Campos personalizados e aparecem em todas as respostas dos endpoints de contatos, no array custom_fields:

"custom_fields": [
  { "key": "matricula", "label": "Matrícula", "value": "2026-0117", "read_only": false },
  { "key": "turma", "label": "Turma", "value": "3º ano B", "read_only": false },
  { "key": "protocolo", "label": "Protocolo", "value": "2026-0042", "read_only": true }
]
CampoTipoObservações
keystringIdentificador estável do campo ([a-z][a-z0-9_]*, até 40 chars). Use este valor para casar com o seu sistema — o label pode ser renomeado a qualquer momento pela conta
labelstringNome exibido na tela, até 60 chars
valuedepende do campoJá convertido para o tipo do campo: string, text, select e sequence → string; integer → número inteiro; decimal → string decimal ("12.5", mesmo padrão dos valores monetários da API); date → string YYYY-MM-DD; boolean → true/false
read_onlybooleantrue quando o valor está gravado mas o campo não aceita mais escrita — ver Campos somente leitura

Regras de leitura:

  • Campos sem valor são omitidos. O array traz apenas os campos preenchidos daquele contato — não conte com uma posição fixa nem com o array ter sempre o mesmo tamanho.
  • A ordem não é garantida. Sempre localize o campo pelo key.
  • A lista varia por conta e por contact_type. Um campo definido só para customer não aparece em fornecedores.

Gravando campos personalizados

custom_fields é aceito em POST, PATCH e PUT, dentro do envelope contact. Dois formatos são aceitos:

{ "contact": { "custom_fields": { "matricula": "2026-0117", "turma": "3º ano B" } } }
{ "contact": { "custom_fields": [ { "key": "matricula", "value": "2026-0117" } ] } }

O segundo é o mesmo formato que o GET devolve, então você pode ler um contato e devolver o array inteiro num PATCH sem filtrar nada: as entradas read_only são ignoradas em silêncio.

SituaçãoResultado
key que não existe na sua conta422 — a requisição inteira falha e nada é gravado
key de um campo desativado, ou que não se aplica a este contact_typeIgnorado em silêncio. O valor gravado continua como está
Valor em branco ("" ou null)Apaga o valor do campo
Campo omitido do payloadMantém o valor atual
custom_fields omitidoNenhum campo personalizado é tocado
Valor que é lista ou objeto422
Mesma key duas vezes no formato de array422
Texto com caracteres de controle (byte nulo, etc.)422
Mais de 100 campos numa requisição422

Validação por tipo

Um valor que não corresponde ao tipo do campo é recusado com 422 — nunca convertido em silêncio:

TipoAceitaExemplos recusados
string, textQualquer texto. string até 255 chars, text até 16.000Texto acima do limite
selectExatamente uma das opções configuradasQualquer valor fora da lista
dateString ISO YYYY-MM-DD15/03/2026, 2026-02-30
booleantrue/false, ou "1"/"0", "true"/"false", "sim"/"nao", "yes"/"no", "on"/"off""talvez"
integerInteiro não-negativo, como número ou string de dígitos"12abc", "-5"
decimalNúmero, ou string em 1234.56 ou 1.234,56. Máximo 11 dígitos inteiros e 4 decimais"12abc", 1000000000000

Campos marcados como únicos recusam com 422 um valor já usado por outro contato da mesma conta (a unicidade é por conta — o mesmo valor pode existir em outra conta).

Campos marcados como obrigatórios precisam vir preenchidos no POST; omiti-los ou enviá-los em branco retorna 422. No PATCH só são cobrados quando você os envia explicitamente em branco.

As mensagens de 422 são prefixadas pelo label do campo, para você saber qual falhou:

{ "status": 422, "errors": ["Turno não é válido", "Matrícula já está em uso"] }

Campos somente leitura

Um valor volta com read_only: true — e é ignorado se você enviá-lo de volta — em três situações:

  • o campo foi desativado na tela depois que o valor já estava gravado;
  • o campo deixou de valer para este contact_type (por exemplo, passou a valer só para fornecedores, ou o contato mudou de tipo);
  • o campo é do tipo sequência, cujo valor o Sacador gera. Sequências configuradas como manuais aceitam um valor enquanto estiverem vazias; depois de preenchidas ficam congeladas, igual à tela.

Um valor manual de sequência empurra o contador da conta para a frente, para que o próximo número gerado não colida com ele. Por isso a API recusa com 422 um valor cujo número esteja muito além da numeração atual (mais de 1.000.000 à frente): seria um salto irreversível na numeração de toda a conta.

Endpoints

GET/v1/contacts

Lista os contatos da conta autenticada, ordenados por description ascendente.

Query params:

ParamTipoComportamento
contact_typecustomer | supplierFiltra por tipo
activetrue | falseFiltra por status
filterstringDefine o campo da busca: contact (default, busca por description), document (busca por CPF/CNPJ — só dígitos), email (busca por email começando com s), code (igualdade exata)
sstringTermo da busca, interpretado conforme filter
tag_idintRetorna apenas os contatos que têm a tag informada. Ver tags
external_idstringIgualdade exata com o external_id do contato. Espaços nas pontas são ignorados
documentstringIgualdade exata com o CPF ou CNPJ. A máscara é ignorada (529.982.247-25 e 52998224725 encontram o mesmo contato). Um valor sem nenhum dígito ou letra válida não encontra nada
page, per_pageintPadrão de paginação

document e filter=document são coisas diferentes: filter=document&s=529982 é uma busca por prefixo (encontra todo documento que começa com 529982), enquanto document=529982 só encontra um contato cujo documento seja exatamente 529982, ou seja, nenhum. Para localizar um cliente específico pelo CPF/CNPJ, use document com o número completo.

Os filtros se combinam entre si (e com s/filter) como condições que precisam valer ao mesmo tempo.

Exemplo cURL (cliente pelo identificador do seu sistema):

curl -X GET 'https://api.sacador.com.br/v1/contacts?external_id=assoc-1234' \
  -H 'Authorization: Bearer sct_seu_token_aqui'

Exemplo cURL:

curl -X GET 'https://api.sacador.com.br/v1/contacts?contact_type=customer&active=true&page=1' \
  -H 'Authorization: Bearer sct_seu_token_aqui'

Resposta 200 OK:

{
  "contacts": [
    {
      "id": 101,
      "contact_type": "customer",
      "code": "C-0042",
      "active": true,
      "email": "[email protected]",
      "legal_entity": false,
      "full_name": "Maria Silva",
      "company_name": null,
      "trademark": null,
      "birth_date": "1990-03-15",
      "cpf": "12345678901",
      "cnpj": null,
      "business_phone_area": "11",
      "business_phone_number": "30001234",
      "cell_phone_area": "11",
      "cell_phone_number": "999998888",
      "address_postal_code": "01310-100",
      "address_street": "Avenida Paulista",
      "address_number": "1000",
      "address_complement": "Apto 42",
      "address_district": "Bela Vista",
      "address_city": "São Paulo",
      "address_state": "SP",
      "observation": null,
      "external_id": "assoc-1234",
      "on_account_limit": "300.0",
      "store_credit_balance": "17.5",
      "description": "Maria Silva",
      "document": "12345678901",
      "on_account_balance": "45.0",
      "on_account_allowed": true,
      "tags": [
        { "id": 12, "name": "Mensalistas", "color": "success" }
      ],
      "custom_fields": [
        { "key": "matricula", "label": "Matrícula", "value": "2026-0117", "read_only": false },
        { "key": "turma", "label": "Turma", "value": "3º ano B", "read_only": false }
      ],
      "created_at": "2026-04-01T10:00:00.000Z",
      "updated_at": "2026-04-01T10:00:00.000Z"
    }
  ]
}

GET/v1/contacts/:id

Exemplo cURL:

curl -X GET 'https://api.sacador.com.br/v1/contacts/101' \
  -H 'Authorization: Bearer sct_seu_token_aqui'

Resposta 200 OK: objeto idêntico ao item da listagem.

Erros: 404 se inexistente ou de outra conta.


POST/v1/contacts

Cria um contato.

Body (envelopado em contact):

{
  "contact": {
    "contact_type": "customer",
    "legal_entity": false,
    "full_name": "Maria Silva",
    "cpf": "123.456.789-01",
    "email": "[email protected]",
    "address_postal_code": "01310-100",
    "address_street": "Avenida Paulista",
    "address_number": "1000",
    "address_district": "Bela Vista",
    "address_city": "São Paulo",
    "address_state": "sp"
  }
}

Campos aceitos (qualquer outro campo enviado é ignorado, incluindo account_id):

contact_type, code, active, email, legal_entity, full_name, birth_date, cpf, cnpj, company_name, trademark, business_phone_area, business_phone_number, cell_phone_area, cell_phone_number, address_postal_code, address_street, address_number, address_complement, address_district, address_city, address_state, observation, external_id, on_account_limit, tag_ids, custom_fields.

store_credit_balance, on_account_balance e on_account_allowed são somente leitura: enviados, são ignorados. O saldo de créditos muda por POST/v1/store_credits.

on_account_limit segue o formato descrito em Limite de fiado: reais com ponto decimal, até duas casas.

tag_ids é um array de ids de tags da conta e substitui o conjunto atual de tags do contato. Detalhes e casos de borda em tags.

custom_fields grava os campos personalizados da conta. Formatos aceitos, validações e casos de borda em Campos personalizados.

Normalização aplicada pelo servidor

  • cpf: removidos todos os caracteres não-dígito.
  • cnpj: removidos caracteres fora de [A-Z0-9] e convertido para maiúsculas.
  • email: minúsculo e sem espaços.
  • address_state: maiúsculo.
  • full_name, company_name, trademark, address_*: espaços em excesso são colapsados (squishize).

Exemplo cURL (pessoa física):

curl -X POST 'https://api.sacador.com.br/v1/contacts' \
  -H 'Authorization: Bearer sct_seu_token_aqui' \
  -H 'Content-Type: application/json' \
  -d '{
    "contact": {
      "contact_type": "customer",
      "legal_entity": false,
      "full_name": "Maria Silva",
      "cpf": "123.456.789-01",
      "email": "[email protected]",
      "cell_phone_area": "11",
      "cell_phone_number": "999998888",
      "address_postal_code": "01310-100",
      "address_street": "Avenida Paulista",
      "address_number": "1000",
      "address_district": "Bela Vista",
      "address_city": "São Paulo",
      "address_state": "SP"
    }
  }'

Exemplo cURL (pessoa jurídica):

curl -X POST 'https://api.sacador.com.br/v1/contacts' \
  -H 'Authorization: Bearer sct_seu_token_aqui' \
  -H 'Content-Type: application/json' \
  -d '{
    "contact": {
      "contact_type": "customer",
      "legal_entity": true,
      "company_name": "Acme Comércio Ltda",
      "trademark": "Acme",
      "cnpj": "12.345.678/0001-90",
      "email": "[email protected]",
      "address_postal_code": "04538-132",
      "address_street": "Rua Funchal",
      "address_number": "263",
      "address_district": "Vila Olímpia",
      "address_city": "São Paulo",
      "address_state": "SP"
    }
  }'

Resposta 201 Created: corpo idêntico a um item de GET/v1/contacts (todos os campos do Modelo).

Erros 422: - CPF/CNPJ inválido ou já cadastrado (mesma conta + mesmo contact_type) - full_name com número ou pontuação (PF) - company_name ou trademark ausente ou com caractere não aceito (PJ) - Endereço incompleto - Email mal formado - external_id já usado por outro contato da conta - on_account_limit negativo, com vírgula ou com mais de duas casas decimais


PATCH/v1/contacts/:id

Atualiza um contato. Mesmos campos do POST. Campos omitidos preservam o valor atual.

Exemplo cURL:

curl -X PATCH 'https://api.sacador.com.br/v1/contacts/101' \
  -H 'Authorization: Bearer sct_seu_token_aqui' \
  -H 'Content-Type: application/json' \
  -d '{
    "contact": {
      "email": "[email protected]",
      "cell_phone_number": "988887777"
    }
  }'

Resposta 200 OK: corpo idêntico a um item de GET/v1/contacts.

Erros: 404 (conta errada), 422 (validação).


DELETE/v1/contacts/:id

Exemplo cURL:

curl -X DELETE 'https://api.sacador.com.br/v1/contacts/101' \
  -H 'Authorization: Bearer sct_seu_token_aqui'

Resposta 204 No Content.

Erros: - 404 — contato inexistente ou de outra conta - 422 — contato possui cobranças (billings), contas a pagar/receber (bills) ou lançamentos de caixa (cashiers) associados. A exclusão é bloqueada (restrict_with_error)