Pular para o conteúdo principal

Para IAs & LLMs

Página machine-first: escrita para ser colada no contexto de um agente que vai escrever código de integração com a API V2 de Antecipação de Recebíveis da Zemo Capital.

Tudo aqui é derivado das páginas canônicas — Autenticação, Valores do Recebível, Idempotência, Códigos de Erro, Webhooks, Getting Started e Integração em 6 curls — e do openapi.json, que é a fonte da verdade do contrato. Onde esta página e o contrato divergirem, o contrato vence.

Como usar

O llms.txt é o índice (a ordem de leitura das páginas). Esta página é o system-prompt sugerido — o bloco abaixo é autossuficiente para os casos comuns. Use os dois juntos: o bloco no system-prompt, o llms.txt como mapa para aprofundar.

1. TL;DR (bloco único, copiável)​

SISTEMA: integracao com a API Zemo Capital V2 (antecipacao de recebiveis).
CONTRATO (fonte da verdade): https://docs.zemocapital.com/openapi.json — release 1.3.0, prefixo /v1

BASE URL
Sandbox: https://receivables-api-sandbox.zemocapital.com
Producao: https://receivables-api.zemocapital.com
O ambiente e o da credencial: zk_sbx_* (ou zk_test_*, legado) = Sandbox,
zk_live_* = Producao.

AUTH — Client Credentials (OAuth2), em 2 linhas
1) POST /v1/auth/token Content-Type: application/json
body {"client_id":"zk_test_...","client_secret":"sk_test_..."}
-> {"access_token":"eyJ...","token_type":"bearer","expires_in":900,"scopes":[...]}
2) Todas as demais chamadas: header Authorization: Bearer <access_token> (TTL 15 min — renove)

GOLDEN PATH — 3 endpoints (registro de estoque -> operacao)
1. POST /v1/auth/token -> access_token
2. POST /v1/stock -> registra o recebivel. EXIGE Idempotency-Key
3. POST /v1/stock/request-anticipation -> cria a operacao. EXIGE Idempotency-Key
Depois: GET /v1/operations/{id} (estado autoritativo em lifecycle_status)
POST /v1/webhooks (eventos, em vez de polling)
Atalho quando NAO ha estoque a registrar: POST /v1/operations/direct (tudo numa chamada).

VOCABULARIO CANONICO — use SO estes nomes de valor
gross_future_value · gross_future_value_deductions · net_future_value ·
requested_net_future_value · requested_net_future_value_percent ·
present_value_discount · net_present_liquid_value ·
opr_gross_future_value · opr_net_future_value · opr_present_value_discount ·
opr_net_present_liquid_value
Nunca invente um campo que nao esteja no contrato.

REGRAS DURAS
- Idempotency-Key em TODO POST de dinheiro. Retry = MESMA chave. Nunca gere uma chave nova no retry.
- Dinheiro: BRL, string decimal com 2 casas ("10000.00"). IDs em UUIDv7.
- Usar gross_future_value OBRIGA enviar gross_future_value_deductions no mesmo payload (0 se nao houver).
- Classifique erro por detail.code (ou pelo prefixo ate o ":"), NUNCA pelo texto da mensagem.
- Webhook: valide o HMAC-SHA256 do body CRU no header X-Zemo-Signature; deduplique por event_id.
- Nunca escreva client_id/client_secret no codigo gerado — leia de variavel de ambiente/cofre.

2. Regras de ouro da integração​

R1 — Autenticação de API: Client Credentials (OAuth2)​

Um único fluxo servidor-a-servidor: troque client_id + client_secret por um JWT RS256 de curta duração em POST /v1/auth/token, e use esse token como Authorization: Bearer.

curl -sS -X POST "$BASE/v1/auth/token" \
-H "Content-Type: application/json" \
-d '{"client_id": "zk_test_...", "client_secret": "sk_test_..."}'
# -> {"access_token": "eyJ...", "token_type": "bearer", "expires_in": 900, "scopes": [...]}
RegraDetalhe
Nunca em código geradoclient_id / client_secret saem de variável de ambiente ou cofre de segredos. O client_secret é exibido uma única vez na emissão. Nunca em log, nunca em repositório.
TTL15 minutos (expires_in: 900). Renove antes de expirar; os SDKs fazem cache e refresh sozinhos.
Escopo mínimoO token carrega scopes granulares. Peça só os que a integração usa — faltando, a rota responde 403 scope_insufficient.
RotaçãoNão existe endpoint de rotação. Crie uma credencial nova, migre o cliente, e só então revogue a antiga (teto de 10 credenciais ativas por originador). Recomendação: trocar o secret a cada 90 dias.
Revogação é imediataRevogar derruba os access_token já emitidos: a credencial é relida a cada request e responde 401 invalid_client_credentials na hora.
O nome é "Client Credentials", mas o wire não é o token endpoint form-encoded do OAuth2

POST /v1/auth/token recebe um corpo JSON com client_id e client_secret. Não há grant_type, não há application/x-www-form-urlencoded e não há HTTP Basic. Não use o formulário genérico "Authorize" de ferramentas OAuth — monte a chamada como está acima.

Detalhes, tipos de chave e ciclo de vida da credencial em Autenticação.

R2 — Idempotência​

RegraDetalhe
ObrigatóriaIdempotency-Key é exigida em POST /v1/stock, POST /v1/stock/request-anticipation e POST /v1/operations/direct. Sem o header: 422 idempotency_key_required, antes de qualquer efeito.
Formato16 a 80 caracteres, validado na entrada (422 idempotency_key_invalid). Use UUID (uuidgen, uuid.uuid4(), crypto.randomUUID()). Nunca contador, hash truncado ou string curta.
Retry seguro = MESMA chaveReenviar a mesma chave com o mesmo corpo devolve a resposta guardada, com Idempotency-Replayed: true, sem executar de novo. TTL de 24 h.
Nunca re-gerar no retryGerar uma chave nova num retry duplica a operação — é exatamente o acidente que o header existe para impedir. Uma chave por operação de negócio, persistida antes do primeiro envio.
Mesma chave, corpo diferente409 idempotency_key_reused_with_different_body. Corrija o corpo e use uma chave nova: é outra operação.
Chave em voo409 idempotency_key_in_flight não é falha: a primeira chamada ainda está rodando. Re-tente com a mesma chave.
Opcional, mas honradaQualquer POST/PUT/PATCH/DELETE sob /v1/* participa do cache se o header vier. Ignorada em POST /v1/auth/login e POST /v1/auth/token.
Segredo não volta no replayValores de uso único (hmac_secret, access_token, signer_token) só vêm na primeira resposta; no replay a chave existe com valor null.

Regras completas, incluindo 503 idempotency_unavailable e o dedup por external_id, em Idempotência.

R3 — Vocabulário canônico​

Estes são os nomes de valor da API. Gere código só com eles:

NomeNívelO que é
gross_future_valuerecebívelValor de face bruto do lastro, antes dos descontos aplicados a ele.
gross_future_value_deductionsrecebívelOs descontos já aplicados ao lastro, declarados por você.
net_future_valuerecebívelNFV (Net Future Value) — o face futuro líquido. É o teto do antecipável e a base do deságio no fluxo de estoque.
requested_net_future_valuerecebívelQuanto do NFV está sendo antecipado.
requested_net_future_value_percentrecebívelO mesmo, em percentual do NFV (0 < p <= 100). Só em POST /v1/simulate e POST /v1/operations/direct.
present_value_discountrecebívelDeságio do recebível.
net_present_liquid_valuerecebívelValor presente líquido do recebível.
opr_gross_future_valueoperaçãoSoma dos gross_future_value. Não é base de cálculo em nenhuma porta.
opr_net_future_valueoperaçãoSoma dos net_future_value — o lastro agregado, não o valor antecipado.
opr_present_value_discountoperaçãoDeságio total.
opr_net_present_liquid_valueoperaçãoValor efetivamente pago por PIX ao cedente. É este que concilia pagamento.
opr_net_liquid_valueoperaçãoProjeção informativa de opr_net_present_liquid_value − other_debt_discounts. Não é persistida e não concilia nada.
other_debt_discountsoperaçãoSaldo devedor dos títulos vencidos do mesmo cedente. Informativo — não é abatido do pagamento.
  • Nunca invente campo. Se o nome não está no openapi.json, ele não existe — a API não o infere.
  • Enviar o mesmo valor duas vezes com números diferentes é 422 VOCABULARY_CONFLICT. Nada é escolhido em silêncio.
  • Semântica do vocabulário: *_future_value é o que vale no vencimento; *_present_value / net_present_* é o que vale hoje. O prefixo opr_ marca o nível operação (agregado); o nome nu é o nível recebível/título.
Nomes de campo legados serão removidos

A API ainda aceita e ainda devolve um conjunto de nomes de campo antigos, herdados da série anterior — inclusive dentro do detail de alguns erros e no loc de um validation_error. Eles serão removidos: não os use em código novo e normalize tudo o que chegar para os nomes desta página. O inventário histórico, para quem precisa migrar código legado, está em Valores do Recebível.

R4 — Dinheiro​

RegraDetalhe
FormatoBRL, string decimal com 2 casas nas respostas ("9160.00"). IDs em UUIDv7.
Bruto exige deduçõesUsar gross_future_value obriga gross_future_value_deductions no mesmo payload — envie 0 se não houver. Sem ele: 422 DEDUCTIONS_REQUIRED_WITH_GROSS.
A decomposição tem que fecharnet_future_value == gross_future_value − gross_future_value_deductions, sem tolerância de centavo — senão 422 VALUE_DECOMPOSITION_MISMATCH.
Percentual ou valor, nunca os doisrequested_net_future_value_percent é mutuamente exclusivo com o valor absoluto: os dois juntos são 422 REQUESTED_VALUE_AMBIGUOUS.
O NFV é tetorequested_net_future_value não pode exceder o net_future_value do recebível — 422.
O estoque antecipa sempre 100%Em POST /v1/stock/request-anticipation e POST /v1/stock/simulate-anticipation, requested_net_future_value tem que ser exatamente a soma dos net_future_value dos itens; qualquer outro valor é 422 STOCK_REQUESTED_VALUE_MISMATCH. Antecipação parcial existe só em POST /v1/operations/direct.
422 = corrigir, não repetirTodo 422 é determinístico: o mesmo payload dá o mesmo erro para sempre. Corrija o corpo e mande uma requisição nova — com Idempotency-Key nova, porque é outra operação.

A cadeia de valor completa, e por que o deságio incide sobre o NFV, em Valores do Recebível.

R5 — Erros: classifique por code, nunca por mensagem​

O envelope é sempre detail, em três formas:

FormaQuandoComo identificar
Objeto com code: "validation_error" + errors[]Falha de schemadetail.code === "validation_error"
Objeto com code + message, sem errorsRegra de negócio estruturadadetail.code presente e detail.errors ausente
StringRegra de negócio legadatypeof detail === "string"

Códigos de regra podem trazer um sufixo : <detalhe> variável (ex.: stock_item_<id>_not_available): classifique pelo prefixo até o :, nunca por igualdade da string inteira — e nunca pelo texto de message, que não é contrato e muda sem aviso.

Retry seguro (com a MESMA Idempotency-Key)

StatuscodeO que o agente faz
429rate_limit_exceededEspera o Retry-After (segundos) e re-tenta. detail.scope diz qual bucket negou (token ou ip).
409idempotency_key_in_flightA primeira chamada ainda está rodando. Re-tenta em instantes, mesma chave.
503idempotency_unavailableA requisição não foi executada. Re-tenta com backoff, mesma chave.
503(dependência externa)Re-tenta com backoff, mesma chave.
401token_expiredRenova o token em POST /v1/auth/token e repete a chamada.

Ação humana (re-tentar não resolve)

StatuscodeCausa
401invalid_client_credentialsCredencial inválida, revogada ou expirada — inclusive fora do token exchange. Emita uma credencial nova.
403scope_insufficient · scope_not_allowedFalta scope no token / scope pedido fora do permitido. detail traz required e granted (ou allowed_scopes).
403LIMIT_MAX_OPERATION_VALUE · LIMIT_MIN_OPERATION_VALUE · LIMIT_AGGREGATE_EXPOSURELimite de policy. detail.source indica o recorte que travou; em LIMIT_AGGREGATE_EXPOSURE, detail.limit_origin indica de onde veio o teto (ver linha abaixo).
403LIMIT_AGGREGATE_EXPOSURE com limit_brl: "0"Ente TRAVADO, não "sem limite". Quando a policy ancora o risco num lado (cedente ou sacado), a ausência de teto naquele lado significa bloqueio: o ente entra com limite 0 e nada passa por ele. detail.limit_origin distingue anchored_side_closed (ninguém definiu o default do lado) de side_default_zero (o default foi configurado como 0, bloqueio deliberado). No lado não ancorado a ausência libera, mas o que estiver configurado lá vale. É assim que se escreve uma allowlist: default do lado ancorado 0/ausente + limite próprio só nos entes que devem operar.
403LIMIT_CONFIG_MISSING · LIMIT_ENTITY_CONFIG_MISSINGConfiguração de limites incompleta na policy — não é excesso de valor. LIMIT_CONFIG_MISSING: nenhum limite agregado configurado. LIMIT_ENTITY_CONFIG_MISSING: falta teto por ente valendo para esta operação — o teto da tese inteira e o teto por operação controlam o agregado e não substituem o limite do ente. Basta o teto do cedente ou de um sacado. detail.configured_levels diz quais recortes chegaram; detail.entity_levels_required, quais satisfazem. Peça ao Backoffice o limite individual do cedente ou do sacado (ou o default por cedente da policy).
422validation_errorFalha de schema. detail.errors[] traz loc, msg e type por campo.
422VOCABULARY_CONFLICT · DEDUCTIONS_REQUIRED_WITH_GROSS · VALUE_DECOMPOSITION_MISMATCH · REQUESTED_VALUE_AMBIGUOUSVocabulário ou decomposição de valores. Corrija o payload.
422STOCK_REQUESTED_VALUE_MISMATCH · STOCK_ITEM_NFV_NOT_POSITIVE · STOCK_ITEM_NFV_ABOVE_GROSS · RECEIVABLE_DISCOUNT_EXCEEDS_BASE · CET_OUT_OF_BOUNDSRegra de negócio de valor/taxa.
422idempotency_key_required · idempotency_key_invalidHeader ausente ou fora de 16–80 caracteres.
409idempotency_key_reused_with_different_body · idempotency_key_reused_on_different_routeChave reaproveitada indevidamente.
409stock_item_<id>_not_pre_authorized · stock_item_not_in_stockItem travado (pre_authorized: false) ou fora de IN_STOCK. Libere com PATCH /v1/stock/{item_id}.
404operation_not_found · stock_item_not_found · title_not_foundRecurso inexistente ou de outro originador (a API é multi-tenant).
423user_lockedSenha correta e conta bloqueada por 30 min após 5 falhas de login. Senha errada em conta bloqueada devolve 401 invalid_credentials (a API não é oráculo de existência de e-mail). Passados os 30 min o desbloqueio é automático e o contador de falhas zera — uma nova trava exige 5 novas falhas.

Não existe 400 nem 502 em rota pública: erro de corpo sai como 422, falha de dependência externa sai como 503. Catálogo completo em Códigos de Erro — e a lista de códigos por operação está na própria descrição de cada resposta no openapi.json.

R6 — Webhooks​

RegraDetalhe
Valide a assinaturaX-Zemo-Signature é o HMAC-SHA256 do body cru (os bytes exatos recebidos, nunca o JSON re-serializado), em hexadecimal, com o hmac_secret como chave. Compare em tempo constante.
Guarde o hmac_secretEle aparece apenas na resposta de POST /v1/webhooks, e não volta num replay de idempotência.
Idempotência por event_idA entrega é at-least-once: o mesmo event_id pode chegar mais de uma vez. Deduplique por ele (também disponível no header X-Zemo-Event-Id).
Consuma o par completoHoje os valores monetários do data ainda viajam com dois nomes lado a lado, com o mesmo número. Leia o canônico — e, num evento antigo reentregue na janela de re-tentativa (~128 min), aceite o nome antigo como fallback, porque o payload entregue é o que foi gravado na emissão. Ver Eventos.
Responda 2xx rápidoO timeout de entrega é de 10 s; responda abaixo disso (idealmente em menos de 5 s) e processe de forma assíncrona.
HTTPS obrigatórioA URL passa por guarda anti-SSRF: HTTPS e host público resolvível, senão 422 com código da família url_* / ip_*.

Envelope comum a todo evento — event_type, event_id, occurred_at, data, os quatro sempre presentes:

{
"event_type": "operation.approved",
"event_id": "01994c1f-1111-7000-9000-0000000000d4",
"occurred_at": "2026-07-29T14:31:00Z",
"data": {
"operation_id": "01994c1e-9b20-7000-9000-0000000000b2",
"display_number": "OP-A1B2C3D4-E5F6G7H8",
"lifecycle_status": "APPROVED_DIRECT",
"approved_at": "2026-07-29T14:31:00Z"
}
}

Catálogo de eventos em Eventos; headers e verificação em Segurança de Webhooks.

3. Golden path executável​

O caminho principal é o de estoque: registrar o recebível e, depois, criar a operação a partir dele. Versão narrada em Getting Started.

Sandbox

Credenciais zk_sbx_* (ou zk_test_*, legado), sem transações reais. Use zk_live_* só depois do go-live.

Os valores dos exemplos fecham entre si: NFV 10.000,00 · 3,5% a.m. · 70 dias + 2 de floating = 72 dias ⇒ deságio 840,00, líquido 9.160,00. Os id que você receber serão outros.

Preparação​

export BASE="https://receivables-api-sandbox.zemocapital.com"
export CLIENT_ID="zk_test_..."
read -rsp "Client secret de Sandbox: " CLIENT_SECRET; echo
export CLIENT_SECRET

1. Autenticar​

TOKEN=$(jq -n '{client_id: env.CLIENT_ID, client_secret: env.CLIENT_SECRET}' \
| curl -sS -X POST "$BASE/v1/auth/token" \
-H "Content-Type: application/json" --data-binary @- \
| jq -r .access_token)
unset CLIENT_SECRET
export TOKEN
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.<payload>.<assinatura>",
"token_type": "bearer",
"expires_in": 900,
"scopes": ["stock:write", "simulation:create", "operation:create", "operation:read", "webhook:write"]
}

Credencial errada devolve 401 com detail string: { "detail": "invalid_client_credentials" }. Este fluxo também cadastra sacado e cedente, então a credencial precisa ainda de payer:write e assignor:write (tabela de scopes).

2. Cadastrar sacado e cedente​

O dedup por documento torna estes dois comandos seguros para repetir.

PAYER_ID=$(curl -sS -X POST "$BASE/v1/payers" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"cnpj":"98765432000198","legal_name":"Sacado Sandbox SA"}' | jq -r .id)

ASSIGNOR_ID=$(curl -sS -X POST "$BASE/v1/assignors" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"document":"12345678000195","legal_name":"Cedente Sandbox SA","type":"J"}' | jq -r .id)

export PAYER_ID ASSIGNOR_ID

3. Registrar o recebível no estoque​

Idempotency-Key é obrigatória aqui.

STOCK_ITEM_ID=$(curl -sS -X POST "$BASE/v1/stock" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d "{
\"assignor_id\": \"$ASSIGNOR_ID\",
\"payer_id\": \"$PAYER_ID\",
\"external_id\": \"NF-2026-001\",
\"backing_type\": \"NFE\",
\"gross_future_value\": \"10000.00\",
\"gross_future_value_deductions\": \"0.00\",
\"net_future_value\": \"10000.00\",
\"due_date\": \"2026-10-07\",
\"pre_authorized\": true
}" | jq -r .id)

export STOCK_ITEM_ID

Resposta (201) — quatro campos, só isso:

{
"id": "01994c1e-c000-7000-9000-0000000000a7",
"external_id": "NF-2026-001",
"status": "IN_STOCK",
"created_at": "2026-07-29T14:30:00Z"
}

gross_future_value e net_future_value precisam ser maiores que zero, e usar o bruto obriga declarar gross_future_value_deductions (aqui 0.00, porque bruto e NFV coincidem). pre_authorized: true libera o item para antecipação — item com false é recusado com 409 tanto na simulação quanto na solicitação. Para reler o item inteiro, use GET /v1/stock/{item_id}.

4. Simular (não cria nada)​

curl -sS -X POST "$BASE/v1/stock/simulate-anticipation" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"stock_item_ids\": [\"$STOCK_ITEM_ID\"],
\"requested_net_future_value\": 10000.00,
\"fees\": { \"monthly_rate_pct\": 3.5, \"floating_days\": 2 }
}"
{
"status": "SIMULATED",
"opr_gross_future_value": "10000.00",
"opr_net_future_value": "10000.00",
"opr_present_value_discount": "840.00",
"opr_net_present_liquid_value": "9160.00",
"average_days_in_advance": 70,
"floating_days": 2,
"receivables": [
{
"stock_item_id": "01994c1e-c000-7000-9000-0000000000a7",
"external_id": "NF-2026-001",
"gross_future_value": "10000.00",
"net_future_value": "10000.00",
"requested_net_future_value": "10000.00",
"due_date": "2026-10-07",
"days_advanced": 72,
"present_value_discount": "840.00",
"net_present_liquid_value": "9160.00",
"monthly_rate_pct": "3.5",
"fee_source": "operation"
}
]
}

Omitir fees faz a API aplicar a hierarquia de taxas configurada para o seu originador. requested_net_future_value tem que ser exatamente a soma dos net_future_value dos itens — o estoque antecipa sempre 100%.

5. Solicitar a antecipação​

Idempotency-Key é obrigatória aqui.

OPERATION_ID=$(curl -sS -X POST "$BASE/v1/stock/request-anticipation" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d "{
\"stock_item_ids\": [\"$STOCK_ITEM_ID\"],
\"requested_net_future_value\": 10000.00,
\"bank\": { \"use_document_pix\": true },
\"fees\": { \"monthly_rate_pct\": 3.5, \"floating_days\": 2 }
}" | jq -r .id)

export OPERATION_ID
{
"id": "01994c1e-9b20-7000-9000-0000000000b2",
"display_number": "OP-A1B2C3D4-E5F6G7H8",
"lifecycle_status": "WAITING_APPROVAL",
"opr_gross_future_value": "10000.00",
"opr_net_future_value": "10000.00",
"opr_present_value_discount": "840.00",
"opr_net_present_liquid_value": "9160.00",
"other_debt_discounts": "0.00",
"opr_net_liquid_value": "9160.00"
}
opr_net_present_liquid_value e opr_net_liquid_value são campos diferentes

opr_net_present_liquid_value (9160.00) é o PIX ao cedente — é por ele que se concilia. opr_net_liquid_value é uma projeção informativa (opr_net_present_liquid_value menos other_debt_discounts), não é persistida e não concilia nada. Aqui os dois coincidem porque other_debt_discounts é 0.00.

6. Consultar o estado​

curl -sS "$BASE/v1/operations/$OPERATION_ID" \
-H "Authorization: Bearer $TOKEN"
{
"id": "01994c1e-9b20-7000-9000-0000000000b2",
"display_number": "OP-A1B2C3D4-E5F6G7H8",
"lifecycle_status": "WAITING_APPROVAL",
"opr_gross_future_value": "10000.00",
"opr_present_value_discount": "840.00",
"opr_net_present_liquid_value": "9160.00",
"created_at": "2026-07-29T14:30:00Z",
"updated_at": "2026-07-29T14:30:00Z"
}

lifecycle_status é o campo autoritativo. Polling serve para depurar; o caminho de produção é o webhook do passo 7. Regra completa em Lifecycle.

7. Registrar o webhook​

curl -sS -X POST "$BASE/v1/webhooks" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"url": "https://api.seu-dominio.com.br/zemo/webhooks",
"events": ["operation.approved", "operation.paid", "title.paid"],
"description": "Integracao principal"
}'
{
"id": "01994c1e-3c00-7000-9000-000000000003",
"url": "https://api.seu-dominio.com.br/zemo/webhooks",
"event_types": ["operation.approved", "operation.paid", "title.paid"],
"hmac_secret": "whsec_<guarde-este-valor-ele-nao-e-exibido-de-novo>",
"created_at": "2026-07-29T14:30:00Z"
}
Guarde o hmac_secret agora

Ele só aparece nesta resposta e é o segredo que valida toda entrega. Grave-o no seu cofre de segredos antes de seguir.

8. Receber: verificar o HMAC​

import hashlib
import hmac

def verify(raw_body: bytes, secret: str, signature: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)

# FastAPI
@app.post("/zemo/webhooks")
async def receive(request: Request):
raw = await request.body() # bytes CRUS, antes de qualquer parse
if not verify(raw, WEBHOOK_SECRET, request.headers.get("X-Zemo-Signature", "")):
return Response(status_code=401)
event = json.loads(raw)
enqueue(event) # processe fora do request
return Response(status_code=200) # responda em menos de 5 s
const crypto = require('crypto');

function verify(rawBody, secret, signature) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(expected, 'utf8');
const b = Buffer.from(signature || '', 'utf8');
// timingSafeEqual exige o mesmo tamanho — compare o comprimento antes.
return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// Express: express.raw() preserva os bytes crus.
app.post('/zemo/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
if (!verify(req.body, WEBHOOK_SECRET, req.get('X-Zemo-Signature'))) {
return res.sendStatus(401);
}
enqueue(JSON.parse(req.body.toString('utf8')));
res.sendStatus(200);
});

Caminho secundário: operação direta​

Atalho para quando não há estoque a registrar: POST /v1/operations/direct envia cedente, recebíveis e dados bancários numa única chamada, sem passar pelo estoque. Mesmas regras de Idempotency-Key e de vocabulário.

Duas diferenças que mudam código:

  • É a única rota que faz antecipação parcial do NFV (requested_net_future_value menor que net_future_value, ou requested_net_future_value_percent); o restante volta ao estoque como um item novo, que nasce travado (pre_authorized: false) e precisa ser liberado com PATCH /v1/stock/{item_id}.
  • A resposta traz o alias legado status e deixa lifecycle_status em null — o oposto do fluxo de estoque. Cliente que atende os dois lê lifecycle_status ?? status.

O simulate desta porta é POST /v1/simulate (recebe receivables[], não stock_item_ids). Payloads completos em Integração em 6 curls e Operação direta.

4. Recursos machine-readable​

RecursoURLO que é
Índice para agentes/llms.txt · /.well-known/llms.txtO índice: a ordem de leitura das páginas, pronto para colar no contexto.
Especificação viva/openapi.jsonFonte da verdade do contrato (release 1.3.0): endpoints, schemas, códigos de erro por operação e exemplos.
Especificação congelada/openapi-v1.1.0.jsonSnapshot imutável de 1.1.0 — o piso de compatibilidade da série 1.x. Fixe-o em geradores de SDK, mocks e testes de contrato quando quiser um alvo que não muda.
Collection Postman/postman_collection.jsonGerada da especificação; importa direto no Postman/Insomnia.
Referência interativa/referenceA mesma especificação renderizada (Scalar), com exemplos de código por endpoint.
ChangelogChangelogHistórico de mudanças que afetam a integração, em ordem de versão.
llms.txt é o índice; esta página é o system-prompt sugerido

O llms.txt diz o que ler e em que ordem. Esta página diz como escrever a integração sem errar. Um agente bem calibrado carrega o bloco da seção 1 no system-prompt e resolve o resto consultando o openapi.json.

5. Anti-padrões​

Os cinco erros que mais custam

1. Gerar código com nomes de campo legados. A API ainda os aceita hoje e vai removê-los. Código novo nasce com os nomes da seção R3 — e normaliza para eles tudo o que chegar.

2. Pular a idempotência, ou gerar chave nova no retry. As duas falhas produzem o mesmo desfecho: operação duplicada, dinheiro pago duas vezes. Uma chave por operação de negócio, persistida antes do primeiro envio, reutilizada em todos os retries.

3. Parsear a mensagem de erro. detail.message não é contrato: o texto muda sem aviso. Classifique por detail.code — e, nos códigos com sufixo variável, pelo prefixo até o :.

4. Confundir os dois "liquid value" da operação. Colisão de homônimos real: opr_net_present_liquid_value é o PIX ao cedente e concilia pagamento; opr_net_liquid_value é uma projeção informativa que desconta débitos vencidos e não concilia nada. Os nomes são parecidos e o significado não é — a nota completa está em Valores do Recebível. No mesmo espírito, opr_net_future_value agrega lastro, não valor antecipado: somá-lo entre operações conta o item-resto duas vezes.

5. Tratar 422 como falha transitória. 422 é determinístico: re-tentar o mesmo payload devolve o mesmo erro para sempre. Corrija o corpo. Transitório é 429 (respeite Retry-After) e 503 (backoff, mesma Idempotency-Key).

Outros dois que aparecem com frequência: assumir que o simulate espelha o create (fora de três validações compartilhadas, as portas divergem por design — ver Códigos de Erro) e inventar campo que "deveria existir" — a API não infere nada que você não declarou.

6. Versão, política e suporte​

ItemValor
Release vigente1.3.0 — toda resposta traz o header X-Zemo-API-Version.
Versão na URL/v1. Só muda em quebra de compatibilidade, e aí a anterior entra em descontinuação anunciada.
Retrocompatível (pode chegar sem aviso)Endpoint novo, campo opcional novo em request, campo novo em response, valor de enum novo, código de erro novo, evento de webhook novo. Programe defensivamente: ignore campo desconhecido e trate enum aberto com default.
BreakingRemover/renomear campo ou endpoint, tornar opcional em obrigatório, mudar tipo/semântica, remover valor de enum. Só por versão nova de URL, anunciada no Changelog.
Nomes de campo legadosContinuam aceitos na série 1.x e são removidos na 2.0. Não os use — a garantia é de versão, não de calendário.
Antes de produzirChecklist de Go-Live.
SuporteGuarde o header X-Request-Id de cada resposta e informe-o ao seu contato técnico na Zemo — é com ele que a requisição é localizada. Disponibilidade em status.zemocapital.com.

Política completa em Versionamento.