Crédito consignado CLT, ponta a ponta: obter a autorização do trabalhador, consultar a margem,
simular, submeter o KYC e contratar.Todos os endpoints deste grupo exigem o scope proposals:write na sua api-key — inclusive os GET
de acompanhamento, porque quem origina precisa acompanhar o que originou. Sem ele a resposta é
403, mesmo com um token válido. proposals:write é distinto de proposals:read, que dá acesso à
listagem de propostas já contratadas em /api/v1/proposals.No consignado o menu de ofertas não existe de saída. Há três portões em ordem, e cada um é uma
espera diferente:O primeiro portão é o que mais surpreende: ele não depende de você nem da plataforma. Enquanto
o trabalhador não autorizar, não há margem — e sem margem não há o que simular.O fluxo em sete passos#
1. POST /applications → 202, status AWAITING_AUTHORIZATION
2. GET /applications/{id}/status ↻ até authorization_status virar AUTHORIZED
(POST /authorization força uma verificação quando você souber que assinou)
3. GET /applications/{id}/margin → vínculos e quanto há em cada um
4. POST /applications/{id}/simulations → 202, cotação em curso
5. GET /applications/{id}/status ↻ até offers_count > 0 (ou NO_OFFERS)
6. POST /applications/{id}/kyc → cadastro completo + conta ou chave Pix
7. POST /applications/{id}/offers/{offer_id}/accept → 202, contratação iniciada
Depois do aceite, siga no GET /status até DISBURSED — ou até um terminal de recusa.Os estados e para onde eles vão#
O caminho central é o de sucesso; as saídas laterais são os desfechos que você precisa tratar.Quatro leituras que o desenho torna óbvias:Os dois terminais do início são diferentes entre si. AUTHORIZATION_UNAVAILABLE é o CPF sem
autorização de consulta no Dataprev. NO_MARGIN é margem zerada hoje, que pode mudar no mês
seguinte.
NO_OFFERS não é fim de linha. Como o valor é você quem pede, uma nova simulação com outro
valor — ou com a outra base — pode render menu.
CONTRACT_REJECTED também volta. Aceitando outra oferta do mesmo menu.
ENDORSED é a fronteira sem volta. Antes dela, cancelar e tentar outro caminho é viável;
depois, o CPF está averbado naquele banco.
Trate estado desconhecido como "em andamento": a lista cresce, e um valor novo não deve quebrar seu
parser.A criação pede cpf, phone e email. name, birth_date e gender são opcionais.Isso não é descuido do contrato: cada bancarizadora exige um conjunto diferente para começar, e
obrigar você a preencher o superconjunto puniria todo mundo pela mais exigente. Quem não tem o que
precisa fica de fora da cotação, com o motivo em quotes[].reason, e a solicitação segue com as
demais.O bloco customer na resposta diz o que falta:"customer": {
"provided": ["CPF", "PHONE", "EMAIL"],
"missing_for_more_offers": ["NAME", "BIRTH_DATE", "GENDER"]
}
Consulte esse bloco antes de pedir qualquer coisa ao cliente final. Boa parte desses campos vem
da própria Dataprev junto com a margem, e a lista encolhe sozinha — pedir antes seria retrabalho
para o seu usuário. O que sobrar você completa com PATCH /applications/{id}/customer.A lista nunca diz qual bancarizadora exige o campo: o provider é detalhe interno.Informe também o user_uuid do operador que conduz a venda, para que a proposta e a comissão fiquem
com ele. O campo passa a ser obrigatório em 05/10/2026 — ver
Vincular a operação ao seu operador.2. Esperar a autorização. É o portão que não depende de você.#
authorization.status tem quatro valores, e dois deles se parecem sem ser:| Status | O que significa | O que fazer |
|---|
AUTHORIZED | Consentimento vigente | Seguir para a margem |
NOT_AUTHORIZED | O trabalhador ainda não assinou o termo | Aguardar — transitório |
NOT_AVAILABLE | O CPF ainda não autorizou a consulta no Dataprev | Nada a fazer por aqui — terminal |
PENDING | Registro em processamento | Aguardar |
A plataforma reconsulta sozinha. O POST /applications/{id}/authorization existe para quando você
souber que o cliente acabou de assinar — mas é limitado por CPF: estourado o limite, responde
429 com Retry-After. Respeite-o em vez de tentar em laço.Onde o trabalhador assina#
O consentimento é um só: o termo de autorização da plataforma. authorization.consent_url traz
o endereço da página de aceite — encaminhe o trabalhador para lá.A página é servida pela Unno e exibe o termo que ele assina. Um único aceite cobre todas as
bancarizadoras aderentes ao termo universal, então não há um link por bancarizadora, nem jornada
diferente por oferta.Assinado o termo, a solicitação segue sozinha para MARGIN_QUERY — você não precisa avisar a
plataforma. O POST /authorization só antecipa a verificação.O campo pode vir ausente se o endereço ainda não tiver sido gerado; nesse caso reconsulte com
POST /applications/{id}/authorization.3. Consultar a margem. Um CPF pode ter mais de uma.#
GET /applications/{id}/margin devolve employments[]: um item por vínculo empregatício, cada um
com empregador, margem disponível e data de admissão. A simulação referencia um employment_id —
é você quem escolhe em qual vínculo operar.Esconder isso seria decidir pelo cliente: dois empregos são duas margens, e a maior nem sempre é a
que ele quer comprometer.MARGIN_QUERY que não sai do lugar#
A consulta depende da bancarizadora, e ela pode falhar. Quando falha, a solicitação não morre: a
plataforma retenta, e só encerra em FAILED por prazo (30 min) ou por teto de tentativas (8).Nesse meio-tempo o GET /status traz margin_failure_reason. O campo só existe quando há
falha — em consulta normal a chave não vem, e é por isso que dá para distinguir as duas situações
que antes eram o mesmo MARGIN_QUERY:/status em MARGIN_QUERY | Leitura |
|---|
sem margin_failure_reason | consulta em curso, espere |
com margin_failure_reason | uma tentativa falhou, a plataforma está retentando |
O código é sempre nosso e genérico (ARRANQUE_DA_CONSULTA_FALHOU, COLETA_DA_CONSULTA_FALHOU,
NENHUMA_BANCARIZADORA_ELEGIVEL). O que a bancarizadora disse vai em failure_detail do GET
/margin — é ali que aparece o texto dela, e é o que separa "parceiro em janela de manutenção" de
"credencial vencida" sem abrir um chamado.4. Simular. Aqui você diz quanto quer.#
basis decide qual valor você informa:INSTALLMENT — installment_amount, quanto ele quer pagar por mês;
NET_AMOUNT — net_amount, quanto o cliente quer receber.
Use a base em que o cliente pensa#
Hoje todas as bancarizadoras do CLT atendem as duas bases. Se o cliente chegou dizendo quanto
quer receber, peça em NET_AMOUNT; se ele chegou dizendo quanto pode pagar por mês, peça em
INSTALLMENT. Traduzir de uma para a outra do lado de cá só embute uma conta que a bancarizadora
faz melhor.Isso é o estado do catálogo, não uma garantia do contrato. Cada bancarizadora declara as bases que
atende, e uma que atenda só uma delas continua plugável: ela apareceria em quotes[] com
UNSUPPORTED_QUOTE_BASIS e não contribuiria com oferta. Não sobrando nenhuma elegível, a simulação
falha com SIMULATION_BASIS_UNSUPPORTED — e aí não há menu nenhum.O teto do INSTALLMENT#
Pedindo por parcela, o teto é a margem disponível do vínculo escolhido — a mesma que
GET /applications/{id}/margin devolve em employments[].available_margin. Acima dela a simulação
falha com INSTALLMENT_EXCEEDS_MARGIN (422) e nenhuma rodada é aberta: nenhuma bancarizadora
aprovaria a operação, e disparar o fan-out só gastaria chamada em todas para voltar sem oferta.A recusa vale só para essa base. Em NET_AMOUNT quem calcula a parcela é a bancarizadora, já
ciente da margem que ela mesma consultou.O teto do NET_AMOUNT#
INSTALLMENT compromete a margem cheia; a inversão que uma bancarizadora faz para atender
NET_AMOUNT pode partir de menos que isso. O efeito prático é que o líquido máximo pedindo por
valor costuma ser menor do que o de uma parcela na margem toda — a mesma pessoa, o mesmo vínculo.Pedir acima desse teto não é erro: a bancarizadora responde com as condições do máximo que ela
alcança e a quote traz a ressalva em reason. Confira o net_amount das ofertas antes de
apresentá-las — ele pode ser menor do que o pedido, e o cliente precisa saber disso pela sua tela,
não pelo extrato.O prazo não é pedido. Quem o define é o motor de crédito da bancarizadora, dentro dos valores
que ele aceita para aquele cliente e aquela tabela — pedir um prazo não o faria valer. A plataforma
oferta uma condição por prazo viável em cada tabela elegível, e é no menu que o cliente escolhe
entre parcela menor e prazo mais curto.Repetir o POST /simulations é a re-cotação, e invalida o menu anterior. Não há endpoint de
requote no CLT: descarte os offer_id antigos e trabalhe só com o menu da rodada nova.offers[] vem ordenado por net_amount decrescente, com as bancarizadoras misturadas: o topo é
sempre a condição que mais entrega ao cliente, venha de quem vier. Empate no líquido é desempatado
pela menor installment_amount — mesmo dinheiro na mão, menor comprometimento de folha primeiro.
Ofertas sem condição (as recusadas, que trazem rejection_reason) vão para o fim.Não agrupamos por bancarizadora de propósito. Agrupar daria vantagem de posição a quem respondesse
primeiro ao fan-out, que é uma característica nossa e não do produto que o cliente compra.A tabela vem identificada, não só nomeada#
Toda oferta traz a tabela comercial que a originou em dois campos: table_name, o rótulo que o
cliente lê na tela, e table_id, o uuid dela no cadastro da plataforma — o mesmo identificador que
aparece no table_identifier da proposta depois do aceite.Use o table_id para qualquer casamento programático (agrupar o menu, aplicar a sua própria
política por tabela, conciliar comissão): o nome é rótulo comercial e muda quando a bancarizadora
renomeia a tabela ou publica uma "2.0", enquanto o uuid atravessa a renomeação.table_id vem ausente quando a bancarizadora ofertou uma condição que não casa com nenhuma
tabela do catálogo. Nesse caso o table_name é o rótulo do próprio provedor — bom para exibir, sem
serventia para conciliar. Trate a ausência como "tabela não identificada", nunca como erro.O seguro prestamista é dimensão da tabela#
A bancarizadora publica tabelas irmãs — "BÚZIOS - COM SEGURO" e "BÚZIOS - SEM SEGURO" —, cada uma
com sua própria condição. insurance_included diz de qual delas a oferta veio, e insurance_amount
traz o prêmio já embutido na parcela; nas ofertas sem seguro ele vem zerado.Agrupar por insurance_included monta as duas listas. O que não existe é a garantia de que
toda condição apareça nas duas: cada tabela tem seus próprios prazos e sua própria política de
crédito, e um prazo pode ser ofertado com seguro e não sem — ou o contrário. Um menu real:| Tabela | insurance_included | Prazos ofertados |
|---|
| BÚZIOS - COM SEGURO | true | 15x, 19x, 25x |
| BÚZIOS - SEM SEGURO | false | 15x, 19x, 25x |
| JURERÊ - COM SEGURO | true | 24x |
| JURERÊ - SEM SEGURO | false | 24x, 30x, 36x |
Repare na JURERÊ: 30x e 36x só existem sem seguro. Se a sua tela parear por prazo, essas duas
ficam sem contrapartida — agrupe por tabela (pelo table_id, não pelo nome), não por prazo.5. Submeter o KYC. Antes do aceite, sempre.#
O KYC é obrigatório, e o bloco bank precisa de conta bancária ou chave Pix. Sem um dos dois o
aceite responde 422 DISBURSEMENT_DATA_REQUIRED — melhor descobrir aí do que depois de o cliente
já ter assinado o contrato.O GET /applications/{id}/kyc devolve um rascunho pré-preenchido com o que a plataforma já sabe,
incluindo o que veio da Dataprev. Use como ponto de partida do seu formulário.O POST /kyc é upsert e não exige Idempotency-Key — de propósito, para que corrigir um dado
errado seja só reenviar.6. Aceitar e acompanhar.#
Escolha um offer_id do menu e aceite. A oferta precisa estar AVAILABLE e dentro do expires_at;
passado o prazo, o aceite responde 409 OFFER_EXPIRED e o caminho é simular de novo.Resposta 202: emissão do contrato, assinatura e averbação seguem em background. Continue no GET
/status até DISBURSED ou um terminal de recusa.Duas coisas que costumam pegar quem integra#
A chave de acompanhamento é o application_id, não o proposal_uuid. A proposta é criada
apenas no aceite e muda a cada nova tentativa de contratação: se o banco recusar e você aceitar
outra oferta, nasce outra proposta. Só o application_id atravessa o fluxo inteiro.Recusa do banco não é erro HTTP. Se o banco recusar a contratação, o POST /accept já terá
respondido 202 — a recusa chega depois, com a solicitação em CONTRACT_REJECTED e o motivo em
offers[].rejection_reason. Trate como "tente a próxima", não como fim de linha.Vincular a operação ao seu operador (user_uuid)#
A partir de 05/10/2026 às 00:00 (horário de Brasília), o user_uuid é obrigatório. A
abertura da solicitação sem ele passa a responder 422 VALIDATION_FAILED. Detalhes e prazo no
documento Changelog e avisos de mudança.
Sem o campo, a solicitação — e a proposta que nascer dela — fica vinculada ao usuário padrão da
API, que aparece na plataforma como "Originação (Sistema)". Com isso nenhum operador do seu
time aparece como usuário digitador da proposta, e a comissão não é atribuída a ninguém.Para vincular a operação a quem de fato a conduziu, informe o user_uuid desse operador na
abertura da solicitação:O vínculo vale para a solicitação inteira: o operador fica como dono dela e passa a ser o usuário
digitador da proposta contratada a partir dela. Não é preciso repetir o campo no KYC nem no aceite.Como obter o user_uuid#
O user_uuid é o identificador do operador na nossa plataforma, não no seu sistema. Busque-o
na API de usuários, filtrando pelo CPF (ou e-mail) do operador:Use o campo uuid do item retornado. O valor não muda: guarde o mapeamento
"operador do seu sistema → uuid" no seu cadastro e consulte a API só quando entrar um operador
novo. Se o operador ainda não existe na plataforma, cadastre-o com POST /api/v1/users.Regras#
Obrigatório a partir de 05/10/2026 00:00 (horário de Brasília). Até lá, sem o campo a
operação segue com o usuário padrão da API e a resposta 202 traz os headers Deprecation,
Sunset (a data do prazo) e Link (a explicação). Depois do prazo, a omissão responde
422 VALIDATION_FAILED com details[].field = "user_uuid" e details[].code = "REQUIRED", e a
solicitação não é aberta.
Scope users:read. A validação do operador usa a mesma leitura da API de usuários, então a
sua api-key precisa desse scope (além de proposals:write). Sem ele a abertura responde 422.
O operador precisa ser seu. Tem de ser um usuário ativo do seu parceiro ou de um
sub-parceiro abaixo dele. Usuário de outro parceiro, inexistente ou inativo responde
422 VALIDATION_FAILED, com details[].field = "user_uuid", e a solicitação não é aberta.
Faz parte da idempotência. O user_uuid entra na comparação do corpo: reenviar a mesma
Idempotency-Key com outro user_uuid responde 409, como qualquer outra mudança de corpo.
Sem infra de webhook? Sem problema#
O webhook é gatilho de conveniência; o GET é a fonte da verdade. O fluxo inteiro funciona só com
POST + GET, e o GET /applications/{id}/status existe justamente para tornar o polling barato.
Quem usa webhook também deve confirmar pelo GET antes de agir — um evento pode se perder, e o
GET sempre reconcilia. Modificado em 2026-10-01 02:47:09