Margem consignável por API
Primeira chamada em menos de cinco minutos
- Crie uma conta de consignatária e peça credenciamento no município.
- No console, gere uma chave de sandbox.
- Rode o comando abaixo com um dos CPFs de teste.
- Quando estiver funcionando, gere a chave de produção. Nada mais muda.
export COCLE_API_KEY=cocle_test_...
curl -X POST https://api.cocle.com.br/v1/margens/consultas \
-H "Authorization: Bearer $COCLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"cpf":"11144477735","finalidade":"teste de integração"}'
Servidores de teste
Cada um exercita um caminho que a sua integração precisa tratar — e que, em produção, você descobriria com um cliente real na linha.
| CPF | Situação | Margem | O que exercita |
|---|---|---|---|
11144477735 | ativo | R$ 1.250,00 | Caminho feliz: margem ampla, averbação aprovada. |
22255588846 | ativo | R$ 42,75 | Margem quase esgotada — exercita recusa por margem insuficiente. |
33366699957 | ativo | — | Margem bloqueada pelo titular — responde como se não existisse. |
44477700083 | afastado | — | Servidor afastado: inelegível, e a parcela não é descontada no retorno. |
55588811194 | ativo | R$ 780,50 | Dois vínculos no mesmo município — a resposta traz margem por vínculo. |
Use 99988877714 para exercitar o 404.
Endpoints
Esta seção é gerada do mesmo contrato que gera o documento OpenAPI 3.1 e o cliente TypeScript, e uma verificação compara os caminhos declarados com os que a API de fato registrou. Documentação desatualizada aqui reprova o build.
POST /v1/margens/consultas
Consultar a margem disponível de um CPF
Responde 404 tanto para CPF que não existe quanto para titular que bloqueou a margem ou se opôs ao uso dos dados. A indistinção é deliberada: um erro distinguível permitiria montar a lista de quem recusou oferta.
curl -X POST https://api.cocle.com.br/v1/margens/consultas \
-H "Authorization: Bearer $COCLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"cpf": "11144477735",
"finalidade": "oferta de crédito consignado"
}'
Resposta 200 — Vínculos encontrados, com a margem de cada um.
{
"consulta_id": "cns_01J8Z",
"cpf_mascarado": "***.444.777-**",
"competencia_referencia": "2026-09",
"vinculos": [
{
"vinculo_id": "vnc_01J8Z",
"municipio": {
"codigo_ibge": "4204608",
"nome": "Município de Exemplo"
},
"orgao": "Secretaria Municipal de Educação",
"regime": "ESTATUTARIO",
"situacao": "ATIVO",
"elegivel": true,
"margens": [
{
"bucket": "EMPRESTIMO",
"disponivelEfetivo": 1250
}
]
}
],
"billing": {
"evento": "CONSULTA_MARGEM",
"faturavel": true,
"valor": 0.45
}
}
POST /v1/reservas
aceita Idempotency-Key
Reservar margem por tempo determinado
Segura a margem enquanto a proposta é analisada. Envie sempre Idempotency-Key: sem ela, uma retentativa de rede vira duas reservas e consome margem em dobro do mesmo servidor.
curl -X POST https://api.cocle.com.br/v1/reservas \
-H "Authorization: Bearer $COCLE_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"vinculo_id": "vnc_...",
"consignataria_id": "...",
"bucket": "EMPRESTIMO",
"valor_parcela": 250.00
}'
Resposta 201 — Reserva criada.
{
"reserva_id": "rsv_01J8Z",
"margem_restante": 1000,
"status": "VIGENTE"
}
Erros próprios deste endpoint:
MARGEM_INSUFICIENTE, RESERVAS_SIMULTANEAS_EXCEDIDAS
POST /v1/autorizacoes
Pedir a autorização do titular
Passo obrigatório entre reservar e averbar. As condições informadas aqui são as que o servidor vê no portal dele, e são conferidas por hash na averbação — divergência de taxa, prazo ou valor faz a averbação ser recusada.
curl -X POST https://api.cocle.com.br/v1/autorizacoes \
-H "Authorization: Bearer $COCLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reserva_id": "...",
"valor_financiado": 12000.00,
"valor_parcela": 250.00,
"qtd_parcelas": 60,
"taxa_am": 1.65,
"cet_aa": 21.70
}'
Resposta 201 — Pedido registrado. O titular decide no Portal do Servidor.
{
"autorizacao_id": "atz_01J8Z",
"status": "PENDENTE"
}
Erros próprios deste endpoint:
RESERVA_EXPIRADA, AUTORIZACAO_JA_EXISTE
POST /v1/averbacoes
aceita Idempotency-Key
Efetivar a averbação
Só passa com autorização APROVADA pelo titular e com as condições idênticas às autorizadas. A resposta traz a memória de cálculo do success fee, para conferir a cobrança no ato e não trinta dias depois.
curl -X POST https://api.cocle.com.br/v1/averbacoes \
-H "Authorization: Bearer $COCLE_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"reserva_id": "...",
"numero_contrato": "CT-2026-0001",
"valor_financiado": 12000.00,
"valor_parcela": 250.00,
"qtd_parcelas": 60,
"taxa_am": 1.65,
"cet_aa": 21.70
}'
Resposta 201 — Contrato averbado. O desconto entra na próxima folha.
{
"contrato_id": "ctr_01J8Z",
"status": "AVERBADA",
"parcelas": 60,
"billing": {
"evento": "AVERBACAO_EFETIVADA",
"base_calculo": "VALOR_FINANCIADO",
"valor_base": 12000,
"aliquota": 0.01,
"valor": 120
}
}
Erros próprios deste endpoint:
AUTORIZACAO_AUSENTE, CONDICOES_DIVERGEM_DA_AUTORIZACAO, RESERVA_EXPIRADA
POST /v1/folha/remessas
Abrir a remessa de uma competência
Primeiro passo do envio da folha. A remessa é área de espera: os lotes se acumulam nela e nada vira contracheque até o fechamento. Reabrir com a mesma referência devolve a remessa que já existe em vez de criar outra — repetir a abertura depois de um timeout é o primeiro comportamento de qualquer integrador, e duas remessas com metade dos lotes cada é exatamente a meia folha que o desenho evita.
curl -X POST https://api.cocle.com.br/v1/folha/remessas \
-H "Authorization: Bearer $COCLE_ERP_KEY" \
-H "Content-Type: application/json" \
-d '{ "competencia": "2026-03", "referencia": "folha-mensal-001" }'
Resposta 201 — Remessa aberta, pronta para receber lotes.
{
"remessa_id": "rmf_01J8Z",
"status": "ABERTA",
"competencia": "2026-03",
"max_linhas_por_lote": 1000,
"max_lotes": 200
}
Erros próprios deste endpoint:
COMPETENCIA_INVALIDA, REMESSA_JA_FECHADA
POST /v1/folha/remessas/:id/lotes
Enviar um lote de contracheques
O lote é numerado por você, a partir de 1. Reenviar o mesmo número com o mesmo conteúdo é aceito em silêncio e não soma duas vezes — é a retentativa normal de quem integra. O mesmo número com conteúdo diferente é recusado, porque aceitar calado deixaria a divergência para o fechamento, longe de quem poderia corrigi-la. Linha com erro de formato é isolada e devolvida em `problemas`; o restante do lote entra. Uma matrícula com CPF digitado errado no cadastro não pode segurar a folha dos outros 3.999.
curl -X POST https://api.cocle.com.br/v1/folha/remessas/$REMESSA/lotes \
-H "Authorization: Bearer $COCLE_ERP_KEY" \
-H "Content-Type: application/json" \
-d '{ "lote": 1, "vinculos": [ ... ] }'
Resposta 202 — Lote recebido na área de espera. Nada virou contracheque ainda.
{
"aceitas": 1,
"recusadas": 0,
"problemas": [],
"remessa": {
"vinculos": 1,
"lotes_recebidos": 1,
"total_proventos": 4200,
"total_descontos": 462
}
}
Erros próprios deste endpoint:
LOTE_DIVERGENTE, LOTE_GRANDE, LOTE_VAZIO, LOTE_INVALIDO, REMESSA_JA_FECHADA
POST /v1/folha/remessas/:id/fechamento
Fechar a remessa e aplicar a folha
Avalia a remessa inteira contra a competência anterior e, se passar, aplica numa transação só. Antes de aplicar há um freio de mudança em massa. Queda de mais de 10% nos vínculos, de mais de 15% nos proventos, crescimento de mais de 50% ou mais de 5% das linhas recusadas devolvem `status: BLOQUEADA` e a folha **não** é aplicada — a remessa fica esperando uma pessoa do município liberar, com justificativa. Não é conservadorismo: uma exportação quebrada que traz 40 servidores em vez de 4.000 zera a margem de quase toda a prefeitura numa chamada HTTP. Fechar de novo uma remessa já aplicada devolve o mesmo resultado sem reaplicar.
curl -X POST https://api.cocle.com.br/v1/folha/remessas/$REMESSA/fechamento \
-H "Authorization: Bearer $COCLE_ERP_KEY"
Resposta 200 — Remessa avaliada. Veja `status`: APLICADA ou BLOQUEADA.
{
"status": "APLICADA",
"avaliacao": {
"vinculos_agora": 612,
"vinculos_antes": 610,
"competencia_comparada": "2026-02",
"variacao_vinculos": 0.0033,
"variacao_proventos": 0.0089,
"proporcao_problemas": 0,
"alertas": []
}
}
Erros próprios deste endpoint:
REMESSA_VAZIA, REMESSA_JA_FECHADA
GET /v1/folha/remessas/:id
Consultar a situação da remessa
Traz status, totais, a lista de problemas linha a linha e a avaliação do freio. É o endereço para descobrir por que a folha não entrou às duas da manhã, sem abrir chamado.
curl https://api.cocle.com.br/v1/folha/remessas/$REMESSA \
-H "Authorization: Bearer $COCLE_ERP_KEY"
Resposta 200 — Situação da remessa.
{
"remessa_id": "rmf_01J8Z",
"status": "APLICADA",
"competencia": "2026-03",
"referencia": "folha-mensal-001",
"vinculos": 612,
"itens": 4284,
"lotes_recebidos": 1,
"total_proventos": 2741320.55,
"total_descontos": 611004.12,
"problemas": [],
"avaliacao": null,
"motivo": null
}
Erros
| HTTP | Código | Quando acontece |
|---|---|---|
401 | UNAUTHENTICATED | Chave ausente, inválida ou revogada. |
403 | FORBIDDEN | A chave não alcança este município ou esta operação. |
404 | NOT_FOUND | CPF sem vínculo, margem bloqueada pelo titular, ou oposição registrada. Não distinga os casos: a resposta é a mesma de propósito. |
409 | MARGEM_INSUFICIENTE | A parcela pedida não cabe na margem disponível. |
409 | AUTORIZACAO_AUSENTE | Não há autorização aprovada pelo titular para esta reserva. |
409 | CONDICOES_DIVERGEM_DA_AUTORIZACAO | Valor, prazo, taxa ou CET diferem do que o titular autorizou. |
409 | RESERVA_EXPIRADA | A reserva venceu. Consulte a margem de novo. |
422 | VALIDATION_ERROR | Campo ausente ou inválido. O campo vem em `erro.campo`. |
403 | CREDENCIAL_SUSPENSA | A credencial foi suspensa por padrão compatível com varredura de CPF. Gerar outra chave não contorna — a reativação é decisão humana. |
429 | RATE_LIMITED | Acima do limite por minuto da chave. A resposta traz em quantos segundos tentar de novo. |
Webhooks
Registre um destino no console. Cada entrega vai assinada com
HMAC-SHA256 no cabeçalho Cocle-Signature, no formato
t=<epoch>,v1=<hex> — e o instante entra no que
é assinado, para que uma entrega capturada não valha para sempre.
| Tópico | Quando dispara |
|---|---|
consentimento.revogado | O titular se opôs ao uso dos dados, revogou o consentimento ou pediu eliminação. Interrompa o tratamento: novas consultas respondem 404. |
consentimento.restaurado | O titular retirou a oposição. As consultas voltam a responder. |
margem.bloqueada | O titular bloqueou a própria margem. Nenhuma reserva ou averbação nova é aceita para o vínculo. |
margem.desbloqueada | |
averbacao.efetivada | Um contrato seu foi averbado e entra na próxima folha. |
contrato.liquidado | A última parcela foi descontada e a margem correspondente voltou. |
consentimento.revogado antes de qualquer
outro. Ele chega em menos de 60 segundos da decisão do titular,
e continuar tratando o dado depois disso é infração sua — a consulta
seguinte responderia 404 de qualquer forma, mas a sua esteira já teria
ligado para alguém que pediu para ser deixado em paz.import { createHmac, timingSafeEqual } from "node:crypto";
// O corpo precisa ser o texto cru. Reserializar o JSON depois de parseá-lo
// muda espaços e ordem, e a assinatura deixa de bater por um motivo que
// não tem nada a ver com autenticidade.
export function conferir(segredo: string, corpo: string, cabecalho: string) {
const partes = new Map(cabecalho.split(",").map((p) => {
const i = p.indexOf("=");
return [p.slice(0, i), p.slice(i + 1)] as const;
}));
const t = Number(partes.get("t"));
const recebida = partes.get("v1") ?? "";
const chave = Buffer.from(segredo.replace("whsec_", ""), "base64url");
const esperada = createHmac("sha256", chave).update(t + "." + corpo).digest("hex");
const a = Buffer.from(esperada), b = Buffer.from(recebida);
if (a.length !== b.length || !timingSafeEqual(a, b)) return false;
// Rejeite o que for velho demais: sem isto, uma entrega capturada pode ser
// reenviada meses depois e o seu servidor não tem como distinguir.
return Math.abs(Date.now() / 1000 - t) < 300;
}
Quatro coisas que quebram integração
- Não enviar
Idempotency-Keyem escrita. Uma retentativa de rede vira contrato duplicado — e desconto em duplicidade no contracheque de uma pessoa. - Deduplicar webhook pelo conteúdo em vez do
Cocle-Event-Id. A entrega é at-least-once. É a causa nº 1 de bug em integração aqui. - Ignorar
consentimento.revogado. Continuar tratando o dado depois da revogação é infração sua, não nossa. - Varrer CPFs. Acima de 70% de consultas sem resultado, com pelo menos 50 chamadas na janela, a credencial é suspensa automaticamente — e gerar outra não contorna. O limiar é publicado de propósito: é folgado para base fria legítima, e a regra que você conhece é a que consegue respeitar. Consulte quem procurou você.
Cliente em TypeScript
O SDK trata por padrão as três coisas que a integração erra com mais
frequência: manda Idempotency-Key em toda escrita, só
repete em erro transitório — nunca num 409, que é decisão de negócio —
e devolve o erro como objeto com codigo, para o seu
if não depender do texto da mensagem.
npm install @cocle/sdk@2026.8.0
import { Cocle, ErroCocle } from "@cocle/sdk";
const cocle = new Cocle({ chave: process.env.COCLE_API_KEY! });
try {
const r = await cocle.consultarMargem({
cpf: "11144477735",
finalidade: "oferta de crédito consignado",
});
const vinculo = r.vinculos.find((v) => v.elegivel);
if (!vinculo) return;
const reserva = await cocle.criarReserva({
vinculoId: vinculo.vinculo_id,
consignatariaId: process.env.COCLE_CONSIGNATARIA_ID!,
bucket: "EMPRESTIMO",
valorParcela: 250,
});
// O titular precisa aprovar antes de averbar. Nenhum desconto é lançado
// sem isso, e as condições daqui são conferidas por hash na averbação.
await cocle.pedirAutorizacao({
reservaId: reserva.reserva_id,
valorFinanciado: 12000, valorParcela: 250, qtdParcelas: 60,
taxaAm: 1.65, cetAa: 21.7,
});
} catch (e) {
if (e instanceof ErroCocle && e.codigo === "MARGEM_INSUFICIENTE") {
// Ofereça um valor de parcela menor.
} else if (e instanceof ErroCocle) {
console.error(e.codigo, e.requestId);
}
}