Visão geral
A API do Triple P Bank é uma API REST que fala JSON. Ela cobre o banco digital (clientes, conta corrente, depósito, saque, transferência, extrato) e um módulo de investimentos simulado: perfil do investidor, carteira sugerida, aplicação e resgate em títulos do Tesouro, no CDB e na LCI do banco e no fundo FGV Quant Capital, posição e rentabilidade. A operação do banco tem rotas próprias, em /internal, para bloquear contas, fechar o dia e ver a receita.
Três garantias valem para toda a API:
- Nenhum centavo é criado ou some. Toda movimentação grava o pedido e as linhas do extrato num único
commit, com as contas travadas. A tarifa sai do cliente e entra na conta do banco. - Um pedido repetido não move o dinheiro de novo (ver Idempotência).
- Um cliente nunca vê o recurso de outro. O recurso de outro cliente responde
404, igual a um que não existe.
Como começar
- Suba o ambiente:
docker compose up -d (API, banco e o simulador das fontes de preço). - Cadastre um cliente:
POST /customer. - Abra a conta:
POST /customer/{customer_key}/account. - Deposite:
POST .../transaction com type = DEPOSIT e uma Idempotency-Key. - Para investir: responda o perfil (
POST .../suitability), veja o catálogo (GET /products) e aplique (POST .../investment_order).
Ambiente e host
A API roda localmente com Docker Compose. Não há ambiente público.
| Serviço | Host | Observação |
|---|
| API | http://localhost:3000 | A porta vem da variável API_PORT do .env (padrão 3000). |
| Simulador das fontes de preço (MockServer) | http://localhost:1080 | Imita o Tesouro, a administradora do fundo e o Banco Central (MOCK_PORT). |
| PostgreSQL | localhost:5432 | DB_PORT. Schema e dados iniciais em database/database.sql. |
Mudou o database.sql? Ele só roda quando o banco nasce: docker compose down -v e depois docker compose up -d --build.
Autenticação
Toda rota exige o cabeçalho INTERNAL-TOKEN, menos GET / e GET /health_check. O valor é o da variável INTERNAL_TOKEN do ambiente. Sem ele, ou com valor errado: 403 (PPP000002).
As rotas em /internal são da operação do banco e exigem também o cabeçalho OPERATOR-TOKEN (variável OPERATOR_TOKEN). Sem ele: 403 (PPP000007). O cliente da API só tem o primeiro token; é isso que impede, por exemplo, um cliente bloqueado de se desbloquear.
| Cabeçalho | Onde | Valor |
|---|
INTERNAL-TOKEN | Todas as rotas, menos / e /health_check | Variável INTERNAL_TOKEN |
OPERATOR-TOKEN | Rotas /internal/... | Variável OPERATOR_TOKEN |
Idempotency-Key | POST .../transaction e POST .../investment_order | Um UUID novo por pedido |
Content-Type | Rotas com corpo | application/json |
Idempotência
As rotas que movem dinheiro (POST .../transaction e POST .../investment_order) exigem o cabeçalho Idempotency-Key: um UUID gerado por quem chama a API, um por pedido. Se a resposta se perder (a internet caiu, deu timeout), reenvie o mesmo pedido com a mesma chave: o banco devolve a resposta original e não move o dinheiro de novo.
A chave é única no banco inteiro. Quando ela chega de novo, o banco compara o pedido com o que já gravou:
| Situação (mesma chave) | Resposta |
|---|
| Reenvio do mesmo pedido, pela mesma conta | 200 com a resposta original. Nada se move. |
| Mesma conta, pedido diferente (outro valor, tipo, destino ou produto) | 409 (PPP000004) |
| Chave já usada por outra conta | 409 (PPP000004), sem mostrar nada do outro pedido |
| Chave de um depósito mandada numa aplicação (ou o contrário) | 409 (PPP000004) |
| Sem o cabeçalho, ou fora do formato UUID | 400 (PPP000003) |
Um pedido recusado (saldo insuficiente, por exemplo) não grava nada, então a mesma chave pode ser usada de novo depois.
Dinheiro, datas e decimais
| O quê | Formato | Exemplo |
|---|
| Dinheiro | Inteiro em centavos. Campos terminam em _amount (e balance). | R$ 10,50 = 1050 |
| Percentual | Inteiro em pontos-base. Campos terminam em _bps. | 10000 = 100%, 150 = 1,5% |
| Cotas e preço unitário | Texto com 8 casas decimais, para não virar float. | "1.25000000" |
| Data | AAAA-MM-DD | "2026-10-09" |
| Instante | UTC, AAAA-MM-DDTHH:MM:SSZ | "2026-10-09T14:12:00Z" |
| Identificador | A ..._key (UUID). O id interno nunca sai. | "9c2f..." |
Data de movimento. O "hoje" do banco (GET /internal/business_date) é separado do relógio. Ele só avança quando a operação fecha o dia (POST /internal/day_close), que também busca os preços dos produtos. Aplicações e resgates usam o preço gravado para a data de movimento. Idade mínima e validade do perfil usam o relógio real.
Paginação
Listas longas (extrato e histórico de ordens) aceitam page (a partir de 1, padrão 1) e limit (1 a 100, padrão 20) na query, e respondem:
{"items": [ ... ], "page": 1, "limit": 20, "total": 57}
total é a quantidade de itens que passam pelos filtros, somando todas as páginas.
Formato de erro
Todo erro responde com o mesmo corpo, com o status HTTP correspondente:
{
"title": "Insufficient Balance",
"description": "The account does not have enough balance for amount plus fee.",
"translation": "Saldo insuficiente para o valor mais a tarifa.",
"code": "PPP003003"
}
| Campo | O que é |
|---|
title | Nome curto do erro, em inglês. |
description | Explicação em inglês. |
translation | Explicação em português, pronta para mostrar ao usuário. |
code | Código estável do erro (PPP + 6 dígitos). Use ele para decidir o que fazer, não o texto. |
O corpo pode ganhar campos novos no futuro: não trate um campo a mais como erro. A lista completa está em Catálogo de erros.
Catálogo de erros
Todos os códigos que a API devolve. Os três primeiros dígitos depois de PPP dizem o módulo: 000 geral, 001 clientes, 002 contas, 003 movimentações, 004 perfil, 005 produtos, 006 carteira, 007 ordens.
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 400 | PPP000001 | Bad Request | (varia: diz o que está errado) | Payload Inválido |
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 400 | PPP000003 | Missing Idempotency Key | This route moves money and requires the Idempotency-Key header with a UUID. | Esta operação exige o cabeçalho Idempotency-Key com um UUID. |
| 409 | PPP000004 | Idempotency Key Reused | This Idempotency-Key was already used by another request. | Esta Idempotency-Key já foi usada em outro pedido. |
| 400 | PPP000005 | Invalid Parameter | (varia: diz o que está errado) | Parâmetros inválidos foram fornecidos na requisição. |
| 409 | PPP000006 | Business Date Mismatch | The close_date is not the current business date. | A data a fechar não é a data de movimento atual. |
| 403 | PPP000007 | Operator Only | This route belongs to the bank operation and requires the OPERATOR-TOKEN header. | Esta rota é da operação do banco e exige o token de operador. |
| 404 | PPP000404 | Resource not Found | The requested resource could not be found but may be available in the future. Subsequent requests by the client are permissible. | O resource solicitado não pode ser encontrado, mas pode estar disponível no futuro. Requests subsequentes do cliente são permitidos. |
| 405 | PPP000405 | Method not allowed | The requested method is forbidden for this resource. | O método desejado não foi encontrado para esse recurso. |
| 500 | PPP000500 | Internal Error | An internal error has occurred and its being investigated. | Um erro interno aconteceu e está sendo investigado. |
| 404 | PPP001001 | Customer Not Found | The customer_key does not exist. | Cliente não encontrado. |
| 409 | PPP001002 | Document Already Registered | There is already a customer with this document_number. | Já existe um cliente cadastrado com este CPF. |
| 422 | PPP001003 | Invalid Document Number | The document_number check digits are invalid. | CPF inválido. |
| 422 | PPP001004 | Underage Customer | The customer must be at least 18 years old and born in the past. | O cliente precisa ter pelo menos 18 anos. |
| 404 | PPP002001 | Account Not Found | The account_key does not exist for this customer. | Conta não encontrada. |
| 409 | PPP002002 | Account Already Exists | The customer already has an account that is not closed. | O cliente já tem uma conta não encerrada. |
| 409 | PPP002003 | Invalid Status Transition | The account cannot move from its current status to the requested one. | A conta não pode passar do status atual para o status pedido. |
| 409 | PPP002004 | Account Not Empty | The account must have zero balance and no investment position to be closed. | Só é possível encerrar uma conta sem saldo e sem investimentos. |
| 409 | PPP002005 | Account Not Active | The account is blocked or closed and cannot move money. | A conta está bloqueada ou encerrada e não pode movimentar dinheiro. |
| 422 | PPP002006 | Status Reason Required | Blocking or closing an account requires a reason. | Bloquear ou encerrar a conta exige um motivo. |
| 403 | PPP002007 | Status Change Reserved To Bank | Customers can only close their own account; blocking and unblocking are bank decisions. | O cliente só pode encerrar a própria conta; bloquear e desbloquear são decisões do banco. |
| 404 | PPP003001 | Transaction Not Found | The transaction_key does not exist or does not involve this account. | Transação não encontrada. |
| 404 | PPP003002 | Destination Account Not Found | The destination_account_key does not exist. | Conta de destino não encontrada. |
| 409 | PPP003003 | Insufficient Balance | The account does not have enough balance for amount plus fee. | Saldo insuficiente para o valor mais a tarifa. |
| 422 | PPP003004 | Invalid Amount | The amount must be an integer greater than zero, in cents. | O valor precisa ser maior que zero, em centavos. |
| 422 | PPP003005 | Amount Above Limit | The amount is above the limit for this type of operation. | O valor está acima do limite para este tipo de operação. |
| 422 | PPP003006 | Same Account Transfer | The destination account must be different from the origin account. | A conta de destino precisa ser diferente da conta de origem. |
| 409 | PPP003007 | Destination Account Not Active | The destination account is blocked or closed and cannot receive money. | A conta de destino está bloqueada ou encerrada e não pode receber dinheiro. |
| 404 | PPP004001 | Suitability Not Found | The customer has never answered the suitability questionnaire. | O cliente ainda não respondeu o questionário de perfil de investidor. |
| 422 | PPP004002 | Incomplete Answers | Every question must be answered exactly once, and only existing questions can be answered. | Cada pergunta do questionário precisa de exatamente uma resposta. |
| 422 | PPP004003 | Invalid Option | The option_code does not belong to the question. | A opção escolhida não pertence a esta pergunta. |
| 409 | PPP004004 | Suitability Expired | The investor profile is more than 24 months old and must be answered again. | O perfil de investidor tem mais de 24 meses e precisa ser refeito. |
| 409 | PPP004005 | Suitability Required | The customer needs an investor profile before this operation. | O cliente precisa responder o questionário de perfil de investidor antes desta operação. |
| 404 | PPP005001 | Product Not Found | The product_key does not exist. | Produto não encontrado. |
| 502 | PPP005006 | Price Source Unavailable | An external price or rate source is down, timed out or answered an error. | Uma fonte externa de preço ou taxa está fora do ar ou respondeu erro. Nada foi gravado. |
| 502 | PPP005007 | Invalid Price Source Response | An external price or rate source answered something that makes no sense. | Uma fonte externa de preço ou taxa respondeu algo sem sentido. Nada foi gravado. |
| 422 | PPP006001 | Suggestion Amount Too Low | The amount for a portfolio suggestion must be at least 100000 cents (R$ 1,000.00). | O valor mínimo para pedir uma sugestão de carteira é R$ 1.000,00. |
| 404 | PPP007001 | Order Not Found | The investment_order_key does not exist for this customer. | Ordem não encontrada. |
| 422 | PPP007002 | Below Minimum Application | The amount is below the product's minimum application. | O valor está abaixo da aplicação mínima do produto. |
| 422 | PPP007003 | Product Not Suitable | The product's risk level is above what the investor profile allows. | O risco do produto está acima do permitido para o seu perfil de investidor. |
| 409 | PPP007004 | Insufficient Position | The redemption is larger than the customer's position in this product. | O resgate é maior do que o que você tem aplicado neste produto. |
| 422 | PPP007005 | Redemption Not Allowed | This product can only be redeemed at maturity. | Este produto só pode ser resgatado no vencimento. |
Clientes
Cadastrar cliente
Cadastra um cliente pessoa física. O CPF pode vir com ou sem pontuação e é guardado só com os dígitos; o cliente precisa ter 18 anos ou mais. A resposta traz a customer_key, que identifica o cliente em todas as outras rotas, e o CPF mascarado.
Endpoint/customer
MétodoPOST
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Content-Type | sim | application/json |
Corpo da requisição
| Campo | Tipo | Descrição | Valores aceitos |
|---|
name* | string | Nome. No cadastro: nome completo do cliente, de 3 a 120 caracteres, sem espaço nas pontas. | mínimo 3 caracteres; até 120 caracteres; sem espaço nas pontas |
document_number* | string | CPF. Entra com ou sem pontuação (123.456.789-09 ou 12345678909) e sai mascarado (*.456.789-). | 000.000.000-00 ou 11 dígitos |
birth_date* | string | Data de nascimento (AAAA-MM-DD). O cliente precisa ter 18 anos ou mais. | data AAAA-MM-DD |
* obrigatório.
Exemplo de requisição
curl -X POST 'http://localhost:3000/customer' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"name": "Ana Souza", "document_number": "529.982.247-25", "birth_date": "1992-04-17"}'
Respostas
201 Cliente cadastrado.
STATUS 201Cliente cadastrado
{
"customer_key": "4f9a35d6-e269-4e84-a9f6-b50552c5ce56",
"name": "Ana Souza",
"document_number": "***.982.247-**",
"birth_date": "1992-04-17",
"created_at": "2026-10-11T19:43:59Z"
}
STATUS 409CPF já cadastrado
{
"title": "Document Already Registered",
"description": "There is already a customer with this document_number.",
"translation": "Já existe um cliente cadastrado com este CPF.",
"code": "PPP001002"
}
Campos da resposta
| Campo | Descrição |
|---|
customer_key | Identificador do cliente (UUID). É ele que vai no caminho das rotas /customer/.... |
name | Nome. No cadastro: nome completo do cliente, de 3 a 120 caracteres, sem espaço nas pontas. |
document_number | CPF. Entra com ou sem pontuação (123.456.789-09 ou 12345678909) e sai mascarado (*.456.789-). |
birth_date | Data de nascimento (AAAA-MM-DD). O cliente precisa ter 18 anos ou mais. |
created_at | Quando o registro foi criado, em UTC (AAAA-MM-DDTHH:MM:SSZ). |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 400 | PPP000001 | Bad Request | (varia: diz o que está errado) | Payload Inválido |
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 409 | PPP001002 | Document Already Registered | There is already a customer with this document_number. | Já existe um cliente cadastrado com este CPF. |
| 422 | PPP001003 | Invalid Document Number | The document_number check digits are invalid. | CPF inválido. |
| 422 | PPP001004 | Underage Customer | The customer must be at least 18 years old and born in the past. | O cliente precisa ter pelo menos 18 anos. |
Clientes
Consultar cliente
Devolve os dados do cliente. O CPF nunca sai completo: aparece como *.456.789-.
Endpoint/customer/{customer_key}
MétodoGET
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Parâmetros de caminho
| Campo | Descrição |
|---|
customer_key | Identificador do cliente (UUID). É ele que vai no caminho das rotas /customer/.... |
Exemplo de requisição
curl -X GET 'http://localhost:3000/customer/4f9a35d6-e269-4e84-a9f6-b50552c5ce56' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>'
Respostas
200 Dados do cliente.
STATUS 200Dados do cliente
{
"customer_key": "4f9a35d6-e269-4e84-a9f6-b50552c5ce56",
"name": "Ana Souza",
"document_number": "***.982.247-**",
"birth_date": "1992-04-17",
"created_at": "2026-10-11T19:43:59Z"
}
Campos da resposta
| Campo | Descrição |
|---|
customer_key | Identificador do cliente (UUID). É ele que vai no caminho das rotas /customer/.... |
name | Nome. No cadastro: nome completo do cliente, de 3 a 120 caracteres, sem espaço nas pontas. |
document_number | CPF. Entra com ou sem pontuação (123.456.789-09 ou 12345678909) e sai mascarado (*.456.789-). |
birth_date | Data de nascimento (AAAA-MM-DD). O cliente precisa ter 18 anos ou mais. |
created_at | Quando o registro foi criado, em UTC (AAAA-MM-DDTHH:MM:SSZ). |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 404 | PPP001001 | Customer Not Found | The customer_key does not exist. | Cliente não encontrado. |
Contas
Abrir conta
Abre a conta corrente do cliente, que nasce ACTIVE e com saldo zero. Cada cliente tem no máximo uma conta não encerrada; para abrir outra, a anterior precisa estar CLOSED.
Endpoint/customer/{customer_key}/account
MétodoPOST
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Parâmetros de caminho
| Campo | Descrição |
|---|
customer_key | Identificador do cliente (UUID). É ele que vai no caminho das rotas /customer/.... |
Exemplo de requisição
curl -X POST 'http://localhost:3000/customer/4f9a35d6-e269-4e84-a9f6-b50552c5ce56/account' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>'
Respostas
201 Conta aberta.
STATUS 201Conta aberta
{
"account_key": "b043541b-19bc-4c23-a079-b18cbff9354c",
"status": "ACTIVE",
"balance": 0,
"investments_amount": 0,
"net_worth_amount": 0,
"created_at": "2026-10-11T19:43:59Z",
"updated_at": "2026-10-11T19:43:59Z"
}
Campos da resposta
| Campo | Descrição |
|---|
account_key | Identificador da conta (UUID). |
status | Status da conta: ACTIVE (movimenta), BLOCKED (não movimenta, só consulta) ou CLOSED (encerrada). |
balance | Saldo da conta (só o dinheiro parado nela, sem os investimentos), em centavos. Na resposta de uma movimentação, é o saldo logo depois dela. |
investments_amount | Valor de todas as posições ao preço do dia, em centavos. Fica fora do balance: o que foi aplicado saiu do saldo. |
net_worth_amount | Capital total do cliente no banco: caixa + investimentos ao preço do dia, em centavos. |
created_at | Quando o registro foi criado, em UTC (AAAA-MM-DDTHH:MM:SSZ). |
updated_at | Quando o registro mudou pela última vez, em UTC. |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 404 | PPP001001 | Customer Not Found | The customer_key does not exist. | Cliente não encontrado. |
| 409 | PPP002002 | Account Already Exists | The customer already has an account that is not closed. | O cliente já tem uma conta não encerrada. |
Contas
Consultar conta
Devolve o status, o saldo (dinheiro na conta), o valor investido ao preço do dia e o capital total (saldo + investimentos), em centavos. Os dois últimos são calculados na hora e batem com o total de posições. Conta de outro cliente responde 404, igual a uma inexistente.
Endpoint/customer/{customer_key}/account/{account_key}
MétodoGET
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Parâmetros de caminho
| Campo | Descrição |
|---|
customer_key | Identificador do cliente (UUID). É ele que vai no caminho das rotas /customer/.... |
account_key | Identificador da conta (UUID). |
Exemplo de requisição
curl -X GET 'http://localhost:3000/customer/4f9a35d6-e269-4e84-a9f6-b50552c5ce56/account/b043541b-19bc-4c23-a079-b18cbff9354c' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>'
Respostas
200 Dados da conta.
STATUS 200Dados da conta
{
"account_key": "b043541b-19bc-4c23-a079-b18cbff9354c",
"status": "ACTIVE",
"balance": 0,
"investments_amount": 0,
"net_worth_amount": 0,
"created_at": "2026-10-11T19:43:59Z",
"updated_at": "2026-10-11T19:43:59Z"
}
STATUS 200Conta com investimentos
{
"account_key": "b043541b-19bc-4c23-a079-b18cbff9354c",
"status": "ACTIVE",
"balance": 99800,
"investments_amount": 79998,
"net_worth_amount": 179798,
"created_at": "2026-10-11T19:43:59Z",
"updated_at": "2026-10-11T19:43:59Z"
}
Campos da resposta
| Campo | Descrição |
|---|
account_key | Identificador da conta (UUID). |
status | Status da conta: ACTIVE (movimenta), BLOCKED (não movimenta, só consulta) ou CLOSED (encerrada). |
balance | Saldo da conta (só o dinheiro parado nela, sem os investimentos), em centavos. Na resposta de uma movimentação, é o saldo logo depois dela. |
investments_amount | Valor de todas as posições ao preço do dia, em centavos. Fica fora do balance: o que foi aplicado saiu do saldo. |
net_worth_amount | Capital total do cliente no banco: caixa + investimentos ao preço do dia, em centavos. |
created_at | Quando o registro foi criado, em UTC (AAAA-MM-DDTHH:MM:SSZ). |
updated_at | Quando o registro mudou pela última vez, em UTC. |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 404 | PPP001001 | Customer Not Found | The customer_key does not exist. | Cliente não encontrado. |
| 404 | PPP002001 | Account Not Found | The account_key does not exist for this customer. | Conta não encontrada. |
Contas
Encerrar conta (cliente)
O cliente encerra a própria conta, informando o motivo. Só encerra com saldo zero e sem investimentos. Bloquear e desbloquear são exclusivos do banco (rota interna); pedir isso aqui responde 403.
Endpoint/customer/{customer_key}/account/{account_key}
MétodoPATCH
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Content-Type | sim | application/json |
Parâmetros de caminho
| Campo | Descrição |
|---|
customer_key | Identificador do cliente (UUID). É ele que vai no caminho das rotas /customer/.... |
account_key | Identificador da conta (UUID). |
Corpo da requisição
| Campo | Tipo | Descrição | Valores aceitos |
|---|
status* | string | Novo status. O cliente só pode pedir CLOSED; BLOCKED e ACTIVE são do banco (rota interna) e respondem 403. | ACTIVE · BLOCKED · CLOSED |
reason | string | Motivo da mudança de status, até 255 caracteres. Obrigatório para bloquear e encerrar. | até 255 caracteres |
* obrigatório.
Exemplo de requisição
curl -X PATCH 'http://localhost:3000/customer/117b4144-e22a-456f-8975-6760acfc0481/account/829a2669-8375-43b1-bbf8-19e21609e92d' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"status": "CLOSED", "reason": "Não vou mais usar"}'
Respostas
200 Conta encerrada; um evento de status é gravado.
STATUS 200Cliente encerra a conta vazia
{
"account_key": "829a2669-8375-43b1-bbf8-19e21609e92d",
"status": "CLOSED",
"balance": 0,
"investments_amount": 0,
"net_worth_amount": 0,
"created_at": "2026-10-11T19:44:00Z",
"updated_at": "2026-10-11T19:44:00Z"
}
STATUS 403Cliente tenta bloquear
{
"title": "Status Change Reserved To Bank",
"description": "Customers can only close their own account; blocking and unblocking are bank decisions.",
"translation": "O cliente só pode encerrar a própria conta; bloquear e desbloquear são decisões do banco.",
"code": "PPP002007"
}
Campos da resposta
| Campo | Descrição |
|---|
account_key | Identificador da conta (UUID). |
status | Status da conta: ACTIVE (movimenta), BLOCKED (não movimenta, só consulta) ou CLOSED (encerrada). |
balance | Saldo da conta (só o dinheiro parado nela, sem os investimentos), em centavos. Na resposta de uma movimentação, é o saldo logo depois dela. |
investments_amount | Valor de todas as posições ao preço do dia, em centavos. Fica fora do balance: o que foi aplicado saiu do saldo. |
net_worth_amount | Capital total do cliente no banco: caixa + investimentos ao preço do dia, em centavos. |
created_at | Quando o registro foi criado, em UTC (AAAA-MM-DDTHH:MM:SSZ). |
updated_at | Quando o registro mudou pela última vez, em UTC. |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 400 | PPP000001 | Bad Request | (varia: diz o que está errado) | Payload Inválido |
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 403 | PPP002007 | Status Change Reserved To Bank | Customers can only close their own account; blocking and unblocking are bank decisions. | O cliente só pode encerrar a própria conta; bloquear e desbloquear são decisões do banco. |
| 404 | PPP001001 | Customer Not Found | The customer_key does not exist. | Cliente não encontrado. |
| 404 | PPP002001 | Account Not Found | The account_key does not exist for this customer. | Conta não encontrada. |
| 409 | PPP002003 | Invalid Status Transition | The account cannot move from its current status to the requested one. | A conta não pode passar do status atual para o status pedido. |
| 409 | PPP002004 | Account Not Empty | The account must have zero balance and no investment position to be closed. | Só é possível encerrar uma conta sem saldo e sem investimentos. |
| 422 | PPP002006 | Status Reason Required | Blocking or closing an account requires a reason. | Bloquear ou encerrar a conta exige um motivo. |
Contas
Histórico de status da conta
Toda mudança de status da conta, da abertura em diante, com o motivo e quem fez (CUSTOMER ou BANK). Nada é apagado.
Endpoint/customer/{customer_key}/account/{account_key}/status_events
MétodoGET
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Parâmetros de caminho
| Campo | Descrição |
|---|
customer_key | Identificador do cliente (UUID). É ele que vai no caminho das rotas /customer/.... |
account_key | Identificador da conta (UUID). |
Exemplo de requisição
curl -X GET 'http://localhost:3000/customer/117b4144-e22a-456f-8975-6760acfc0481/account/829a2669-8375-43b1-bbf8-19e21609e92d/status_events' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>'
Respostas
200 Eventos, do mais antigo para o mais novo.
STATUS 200Abertura e encerramento
{
"items": [
{
"from_status": null,
"to_status": "ACTIVE",
"reason": null,
"actor": "CUSTOMER",
"created_at": "2026-10-11T19:44:00Z"
},
{
"from_status": "ACTIVE",
"to_status": "CLOSED",
"reason": "Não vou mais usar",
"actor": "CUSTOMER",
"created_at": "2026-10-11T19:44:00Z"
}
]
}
Campos da resposta
| Campo | Descrição |
|---|
items | A lista de resultados. |
items[].from_status | Status anterior. Nulo no evento de abertura da conta. |
items[].to_status | Status depois da mudança. |
items[].reason | Motivo da mudança de status, até 255 caracteres. Obrigatório para bloquear e encerrar. |
items[].actor | Quem fez a mudança: CUSTOMER (rota do cliente) ou BANK (rota interna). |
items[].created_at | Quando o registro foi criado, em UTC (AAAA-MM-DDTHH:MM:SSZ). |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 404 | PPP001001 | Customer Not Found | The customer_key does not exist. | Cliente não encontrado. |
| 404 | PPP002001 | Account Not Found | The account_key does not exist for this customer. | Conta não encontrada. |
Movimentações e extrato
Depositar, sacar ou transferir
Cria uma movimentação de dinheiro na conta. Na transferência, a tarifa de R$ 2,00 sai da conta de origem e entra na conta do banco; o destino recebe o valor inteiro. Limites por operação: depósito até R$ 1.000.000,00, saque até R$ 5.000,00, transferência até R$ 50.000,00. A resposta traz o saldo da conta logo depois da operação.
Endpoint/customer/{customer_key}/account/{account_key}/transaction
MétodoPOST
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Idempotency-Key | sim | UUID novo por pedido; reenviar com a mesma chave não repete o efeito. Ver Idempotência. |
Content-Type | sim | application/json |
Parâmetros de caminho
| Campo | Descrição |
|---|
customer_key | Identificador do cliente (UUID). É ele que vai no caminho das rotas /customer/.... |
account_key | Identificador da conta (UUID). |
Corpo da requisição
| Campo | Tipo | Descrição | Valores aceitos |
|---|
type* | string | Tipo da movimentação: DEPOSIT (depósito), WITHDRAWAL (saque) ou TRANSFER (transferência). Aplicação e resgate vão pela rota de ordens. | DEPOSIT · WITHDRAWAL · TRANSFER |
amount* | integer | Valor em centavos, inteiro e maior que zero (R$ 10,50 = 1050). | |
destination_account_key | string | Conta que recebe a transferência. Obrigatório só no TRANSFER; pode ser de outro cliente, mas não a própria conta. | até 36 caracteres |
description | string | Texto livre que aparece no extrato e no comprovante, até 140 caracteres. | até 140 caracteres |
* obrigatório.
Exemplo de requisição
curl -X POST 'http://localhost:3000/customer/4f9a35d6-e269-4e84-a9f6-b50552c5ce56/account/b043541b-19bc-4c23-a079-b18cbff9354c/transaction' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>' \
-H 'Idempotency-Key: cbb910f5-6214-48fb-bdba-3d2aa80ffe86' \
-H 'Content-Type: application/json' \
-d '{"type": "DEPOSIT", "amount": 200000, "description": "Salário"}'
Respostas
201 Movimentação feita.
200 Repetição do mesmo pedido (mesma chave, mesma conta, mesmo corpo): devolve a resposta original e não move o dinheiro de novo.
STATUS 201Depósito de R$ 2.000,00
{
"transaction_key": "97d971c5-10dc-4350-bdb1-ff8dad5de744",
"type": "DEPOSIT",
"amount": 200000,
"fee_amount": 0,
"description": "Salário",
"balance": 200000,
"created_at": "2026-10-11T19:43:59Z"
}
STATUS 201Transferência de R$ 200,00 (tarifa de R$ 2,00)
{
"transaction_key": "42ff2a44-cb70-41d9-9d9e-8c25bc377dd6",
"type": "TRANSFER",
"amount": 20000,
"fee_amount": 200,
"description": "Aluguel",
"balance": 179800,
"created_at": "2026-10-11T19:43:59Z"
}
STATUS 200Reenvio do mesmo depósito (mesma chave)
{
"transaction_key": "97d971c5-10dc-4350-bdb1-ff8dad5de744",
"type": "DEPOSIT",
"amount": 200000,
"fee_amount": 0,
"description": "Salário",
"balance": 200000,
"created_at": "2026-10-11T19:43:59Z"
}
STATUS 409Mesma chave com outro valor
{
"title": "Idempotency Key Reused",
"description": "This Idempotency-Key was already used by another request.",
"translation": "Esta Idempotency-Key já foi usada em outro pedido.",
"code": "PPP000004"
}
STATUS 409Saldo insuficiente
{
"title": "Insufficient Balance",
"description": "The account does not have enough balance for amount plus fee.",
"translation": "Saldo insuficiente para o valor mais a tarifa.",
"code": "PPP003003"
}
Campos da resposta
| Campo | Descrição |
|---|
transaction_key | Identificador da movimentação (UUID). |
type | Tipo da movimentação: DEPOSIT, WITHDRAWAL, TRANSFER, INVESTMENT_APPLY ou INVESTMENT_REDEEM. |
amount | Valor em centavos, inteiro e maior que zero (R$ 10,50 = 1050). |
fee_amount | Tarifa cobrada, em centavos. R$ 2,00 (200) na transferência; zero nas outras. |
description | Texto livre que aparece no extrato e no comprovante, até 140 caracteres. |
balance | Saldo da conta (só o dinheiro parado nela, sem os investimentos), em centavos. Na resposta de uma movimentação, é o saldo logo depois dela. |
created_at | Quando o registro foi criado, em UTC (AAAA-MM-DDTHH:MM:SSZ). |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 400 | PPP000001 | Bad Request | (varia: diz o que está errado) | Payload Inválido |
| 400 | PPP000003 | Missing Idempotency Key | This route moves money and requires the Idempotency-Key header with a UUID. | Esta operação exige o cabeçalho Idempotency-Key com um UUID. |
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 404 | PPP001001 | Customer Not Found | The customer_key does not exist. | Cliente não encontrado. |
| 404 | PPP002001 | Account Not Found | The account_key does not exist for this customer. | Conta não encontrada. |
| 404 | PPP003002 | Destination Account Not Found | The destination_account_key does not exist. | Conta de destino não encontrada. |
| 409 | PPP000004 | Idempotency Key Reused | This Idempotency-Key was already used by another request. | Esta Idempotency-Key já foi usada em outro pedido. |
| 409 | PPP002005 | Account Not Active | The account is blocked or closed and cannot move money. | A conta está bloqueada ou encerrada e não pode movimentar dinheiro. |
| 409 | PPP003003 | Insufficient Balance | The account does not have enough balance for amount plus fee. | Saldo insuficiente para o valor mais a tarifa. |
| 409 | PPP003007 | Destination Account Not Active | The destination account is blocked or closed and cannot receive money. | A conta de destino está bloqueada ou encerrada e não pode receber dinheiro. |
| 422 | PPP003004 | Invalid Amount | The amount must be an integer greater than zero, in cents. | O valor precisa ser maior que zero, em centavos. |
| 422 | PPP003005 | Amount Above Limit | The amount is above the limit for this type of operation. | O valor está acima do limite para este tipo de operação. |
| 422 | PPP003006 | Same Account Transfer | The destination account must be different from the origin account. | A conta de destino precisa ser diferente da conta de origem. |
Movimentações e extrato
Comprovante
O comprovante de uma movimentação, para a conta de origem ou de destino. Não mostra saldo: o destino não pode ver o saldo da origem. Mostra o dia de movimento da transação (business_date). Transação que não envolve a conta responde 404.
Endpoint/customer/{customer_key}/account/{account_key}/transaction/{transaction_key}
MétodoGET
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Parâmetros de caminho
| Campo | Descrição |
|---|
customer_key | Identificador do cliente (UUID). É ele que vai no caminho das rotas /customer/.... |
account_key | Identificador da conta (UUID). |
transaction_key | Identificador da movimentação (UUID). |
Exemplo de requisição
curl -X GET 'http://localhost:3000/customer/4f9a35d6-e269-4e84-a9f6-b50552c5ce56/account/b043541b-19bc-4c23-a079-b18cbff9354c/transaction/42ff2a44-cb70-41d9-9d9e-8c25bc377dd6' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>'
Respostas
200 Comprovante.
STATUS 200Comprovante da transferência
{
"transaction_key": "42ff2a44-cb70-41d9-9d9e-8c25bc377dd6",
"type": "TRANSFER",
"amount": 20000,
"fee_amount": 200,
"description": "Aluguel",
"origin_account_key": "b043541b-19bc-4c23-a079-b18cbff9354c",
"destination_account_key": "4ec7839a-1587-4d71-99de-7b2576e03be5",
"business_date": "2026-11-03",
"created_at": "2026-10-11T19:43:59Z"
}
Campos da resposta
| Campo | Descrição |
|---|
transaction_key | Identificador da movimentação (UUID). |
type | Tipo da movimentação: DEPOSIT, WITHDRAWAL, TRANSFER, INVESTMENT_APPLY ou INVESTMENT_REDEEM. |
amount | Valor em centavos, inteiro e maior que zero (R$ 10,50 = 1050). |
fee_amount | Tarifa cobrada, em centavos. R$ 2,00 (200) na transferência; zero nas outras. |
description | Texto livre que aparece no extrato e no comprovante, até 140 caracteres. |
origin_account_key | Conta de onde o dinheiro saiu. Nulo no depósito e no resgate (o dinheiro vem de fora da conta). |
destination_account_key | Conta que recebe a transferência. Obrigatório só no TRANSFER; pode ser de outro cliente, mas não a própria conta. |
business_date | Data de movimento: o "hoje" do banco, a data real simulada. Num lançamento ou comprovante, o dia de movimento em que a transação aconteceu. |
created_at | Quando o registro foi criado, em UTC (AAAA-MM-DDTHH:MM:SSZ). |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 404 | PPP001001 | Customer Not Found | The customer_key does not exist. | Cliente não encontrado. |
| 404 | PPP002001 | Account Not Found | The account_key does not exist for this customer. | Conta não encontrada. |
| 404 | PPP003001 | Transaction Not Found | The transaction_key does not exist or does not involve this account. | Transação não encontrada. |
Perfil do investidor
Perguntas do questionário
As 7 perguntas do questionário de perfil (suitability), na ordem (Q1 a Q7), cada uma com 4 opções. O código da opção (1 a 4) é também a pontuação dela.
Endpoint/suitability_questions
MétodoGET
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Exemplo de requisição
curl -X GET 'http://localhost:3000/suitability_questions' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>'
Respostas
200 Perguntas e opções.
STATUS 200Questionário
{
"items": [
{
"code": "Q1",
"text": "Por quanto tempo pretende deixar o dinheiro investido?",
"dimension": "objetivo",
"options": [
{
"code": 1,
"text": "Menos de 1 ano"
},
{
"code": 2,
"text": "1 a 3 anos"
},
{
"code": 3,
"text": "3 a 5 anos"
},
{
"code": 4,
"text": "Mais de 5 anos"
}
]
},
{
"code": "Q2",
"text": "Qual o principal objetivo?",
"dimension": "objetivo",
"options": [
{
"code": 1,
"text": "Preservar o dinheiro"
},
{
"code": 2,
"text": "Reserva com algum ganho"
},
{
"code": 3,
"text": "Crescer o patrimônio"
},
{
"code": 4,
"text": "Maximizar o retorno aceitando oscilações"
}
]
},
{
"code": "Q3",
"text": "Se seus investimentos caíssem 10% em um mês, você...",
"dimension": "tolerância",
"options": [
{
"code": 1,
"text": "Resgataria tudo"
},
{
"code": 2,
"text": "Resgataria uma parte"
},
{
"code": 3,
"text": "Manteria"
},
{
"code": 4,
"text": "Aplicaria mais"
}
]
},
{
"code": "Q4",
"text": "Quanto da sua renda mensal sobra para investir?",
"dimension": "situação financeira",
"options": [
{
"code": 1,
"text": "Quase nada"
},
{
"code": 2,
"text": "Até 10%"
},
{
"code": 3,
"text": "10% a 30%"
},
{
"code": 4,
"text": "Mais de 30%"
}
]
},
{
"code": "Q5",
"text": "Você pode precisar deste dinheiro de repente?",
"dimension": "liquidez",
"options": [
{
"code": 1,
"text": "Sim, a qualquer momento"
},
{
"code": 2,
"text": "Provavelmente em até 1 ano"
},
{
"code": 3,
"text": "Dificilmente"
},
{
"code": 4,
"text": "Não"
}
]
},
{
"code": "Q6",
"text": "Em quais produtos você já investiu?",
"dimension": "experiência",
"options": [
{
"code": 1,
"text": "Nenhum ou só poupança"
},
{
"code": 2,
"text": "Tesouro/CDB"
},
{
"code": 3,
"text": "Fundos multimercado ou de ações"
},
{
"code": 4,
"text": "Ações, derivativos ou cripto"
}
]
},
{
"code": "Q7",
"text": "Como você descreve seu conhecimento financeiro?",
"dimension": "conhecimento",
"options": [
{
"code": 1,
"text": "Nenhum"
},
{
"code": 2,
"text": "Básico"
},
{
"code": 3,
"text": "Intermediário (entendo risco e retorno)"
},
{
"code": 4,
"text": "Avançado (formação ou atuação na área)"
}
]
}
]
}
Campos da resposta
| Campo | Descrição |
|---|
items | A lista de resultados. |
items[].code | Código. Na pergunta: Q1 a Q7. Na opção: 1 a 4, que é também a pontuação. Na taxa: CDI ou SELIC. |
items[].text | Texto da pergunta ou da opção. |
items[].dimension | O que a pergunta avalia (objetivo, tolerância, situação financeira, liquidez, experiência, conhecimento). |
items[].options | As 4 opções da pergunta. |
items[].options[].code | Código. Na pergunta: Q1 a Q7. Na opção: 1 a 4, que é também a pontuação. Na taxa: CDI ou SELIC. |
items[].options[].text | Texto da pergunta ou da opção. |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
Perfil do investidor
Responder o questionário
Recebe uma resposta para cada uma das 7 perguntas e devolve o perfil: Conservador (7 a 13 pontos), Moderado (14 a 20) ou Arrojado (21 a 28). Quem responde a opção 1 na Q3 ou na Q5 é Conservador qualquer que seja a soma (forced_by_rule = true quando isso muda o resultado). O perfil vale 24 meses; responder de novo cria uma avaliação nova e guarda a anterior.
Endpoint/customer/{customer_key}/suitability
MétodoPOST
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Content-Type | sim | application/json |
Parâmetros de caminho
| Campo | Descrição |
|---|
customer_key | Identificador do cliente (UUID). É ele que vai no caminho das rotas /customer/.... |
Corpo da requisição
| Campo | Tipo | Descrição | Valores aceitos |
|---|
answers* | array de objetos | Uma resposta para cada uma das 7 perguntas. | |
answers[].question_code* | string | Código da pergunta (Q1 a Q7). | |
answers[].option_code* | integer | Código da opção escolhida (1 a 4). | |
* obrigatório.
Exemplo de requisição
curl -X POST 'http://localhost:3000/customer/4f9a35d6-e269-4e84-a9f6-b50552c5ce56/suitability' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"answers": [{"question_code": "Q1", "option_code": 2}, {"question_code": "Q2", "option_code": 2}, {"question_code": "Q3", "option_code": 2}, {"question_code": "Q4", "option_code": 2}, {"question_code": "Q5", "option_code": 2}, {"question_code": "Q6", "option_code": 2}, {"question_code": "Q7", "option_code": 2}]}'
Respostas
201 Avaliação criada.
STATUS 201Perfil Moderado (14 pontos)
{
"suitability_assessment_key": "845f79aa-afd2-4dcf-8336-2f3bc900876a",
"profile": "MODERATE",
"maximum_risk_level": 3,
"score": 14,
"forced_by_rule": false,
"valid_until": "2028-10-11T19:43:59Z",
"created_at": "2026-10-11T19:43:59Z",
"answers": [
{
"question_code": "Q1",
"option_code": 2
},
{
"question_code": "Q2",
"option_code": 2
},
{
"question_code": "Q3",
"option_code": 2
},
{
"question_code": "Q4",
"option_code": 2
},
{
"question_code": "Q5",
"option_code": 2
},
{
"question_code": "Q6",
"option_code": 2
},
{
"question_code": "Q7",
"option_code": 2
}
]
}
STATUS 422Resposta faltando
{
"title": "Incomplete Answers",
"description": "Every question must be answered exactly once, and only existing questions can be answered.",
"translation": "Cada pergunta do questionário precisa de exatamente uma resposta.",
"code": "PPP004002"
}
Campos da resposta
| Campo | Descrição |
|---|
suitability_assessment_key | Identificador da avaliação de perfil (UUID). |
profile | Perfil do investidor: CONSERVATIVE, MODERATE ou AGGRESSIVE. |
maximum_risk_level | Risco máximo de produto. No perfil: até que risco ele aceita (Conservador 2, Moderado 3, Arrojado 5). No filtro do catálogo: só produtos com risco até este (1 a 5). |
score | Pontuação do questionário (soma dos códigos das opções, de 7 a 28). |
forced_by_rule | true quando a trava da Q3/Q5 mudou o perfil para Conservador; false quando os pontos já davam esse perfil. |
valid_until | Até quando o perfil vale (24 meses depois da resposta), em UTC. |
created_at | Quando o registro foi criado, em UTC (AAAA-MM-DDTHH:MM:SSZ). |
answers | Uma resposta para cada uma das 7 perguntas. |
answers[].question_code | Código da pergunta (Q1 a Q7). |
answers[].option_code | Código da opção escolhida (1 a 4). |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 400 | PPP000001 | Bad Request | (varia: diz o que está errado) | Payload Inválido |
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 404 | PPP001001 | Customer Not Found | The customer_key does not exist. | Cliente não encontrado. |
| 422 | PPP004002 | Incomplete Answers | Every question must be answered exactly once, and only existing questions can be answered. | Cada pergunta do questionário precisa de exatamente uma resposta. |
| 422 | PPP004003 | Invalid Option | The option_code does not belong to the question. | A opção escolhida não pertence a esta pergunta. |
Perfil do investidor
Perfil atual
A avaliação mais recente do cliente, com as respostas que levaram ao perfil.
Endpoint/customer/{customer_key}/suitability
MétodoGET
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Parâmetros de caminho
| Campo | Descrição |
|---|
customer_key | Identificador do cliente (UUID). É ele que vai no caminho das rotas /customer/.... |
Exemplo de requisição
curl -X GET 'http://localhost:3000/customer/4f9a35d6-e269-4e84-a9f6-b50552c5ce56/suitability' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>'
Respostas
200 Avaliação vigente.
STATUS 200Perfil vigente
{
"suitability_assessment_key": "845f79aa-afd2-4dcf-8336-2f3bc900876a",
"profile": "MODERATE",
"maximum_risk_level": 3,
"score": 14,
"forced_by_rule": false,
"valid_until": "2028-10-11T19:43:59Z",
"created_at": "2026-10-11T19:43:59Z",
"answers": [
{
"question_code": "Q1",
"option_code": 2
},
{
"question_code": "Q2",
"option_code": 2
},
{
"question_code": "Q3",
"option_code": 2
},
{
"question_code": "Q4",
"option_code": 2
},
{
"question_code": "Q5",
"option_code": 2
},
{
"question_code": "Q6",
"option_code": 2
},
{
"question_code": "Q7",
"option_code": 2
}
]
}
Campos da resposta
| Campo | Descrição |
|---|
suitability_assessment_key | Identificador da avaliação de perfil (UUID). |
profile | Perfil do investidor: CONSERVATIVE, MODERATE ou AGGRESSIVE. |
maximum_risk_level | Risco máximo de produto. No perfil: até que risco ele aceita (Conservador 2, Moderado 3, Arrojado 5). No filtro do catálogo: só produtos com risco até este (1 a 5). |
score | Pontuação do questionário (soma dos códigos das opções, de 7 a 28). |
forced_by_rule | true quando a trava da Q3/Q5 mudou o perfil para Conservador; false quando os pontos já davam esse perfil. |
valid_until | Até quando o perfil vale (24 meses depois da resposta), em UTC. |
created_at | Quando o registro foi criado, em UTC (AAAA-MM-DDTHH:MM:SSZ). |
answers | Uma resposta para cada uma das 7 perguntas. |
answers[].question_code | Código da pergunta (Q1 a Q7). |
answers[].option_code | Código da opção escolhida (1 a 4). |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 404 | PPP001001 | Customer Not Found | The customer_key does not exist. | Cliente não encontrado. |
| 404 | PPP004001 | Suitability Not Found | The customer has never answered the suitability questionnaire. | O cliente ainda não respondeu o questionário de perfil de investidor. |
Perfil do investidor
Histórico de perfis
Todas as avaliações do cliente, da mais nova para a mais antiga.
Endpoint/customer/{customer_key}/suitabilities
MétodoGET
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Parâmetros de caminho
| Campo | Descrição |
|---|
customer_key | Identificador do cliente (UUID). É ele que vai no caminho das rotas /customer/.... |
Exemplo de requisição
curl -X GET 'http://localhost:3000/customer/4f9a35d6-e269-4e84-a9f6-b50552c5ce56/suitabilities' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>'
Respostas
200 Avaliações.
STATUS 200Avaliações
{
"items": [
{
"suitability_assessment_key": "845f79aa-afd2-4dcf-8336-2f3bc900876a",
"profile": "MODERATE",
"maximum_risk_level": 3,
"score": 14,
"forced_by_rule": false,
"valid_until": "2028-10-11T19:43:59Z",
"created_at": "2026-10-11T19:43:59Z",
"answers": [
{
"question_code": "Q1",
"option_code": 2
},
{
"question_code": "Q2",
"option_code": 2
},
{
"question_code": "Q3",
"option_code": 2
},
{
"question_code": "Q4",
"option_code": 2
},
{
"question_code": "Q5",
"option_code": 2
},
{
"question_code": "Q6",
"option_code": 2
},
{
"question_code": "Q7",
"option_code": 2
}
]
}
]
}
Campos da resposta
| Campo | Descrição |
|---|
items | A lista de resultados. |
items[].suitability_assessment_key | Identificador da avaliação de perfil (UUID). |
items[].profile | Perfil do investidor: CONSERVATIVE, MODERATE ou AGGRESSIVE. |
items[].maximum_risk_level | Risco máximo de produto. No perfil: até que risco ele aceita (Conservador 2, Moderado 3, Arrojado 5). No filtro do catálogo: só produtos com risco até este (1 a 5). |
items[].score | Pontuação do questionário (soma dos códigos das opções, de 7 a 28). |
items[].forced_by_rule | true quando a trava da Q3/Q5 mudou o perfil para Conservador; false quando os pontos já davam esse perfil. |
items[].valid_until | Até quando o perfil vale (24 meses depois da resposta), em UTC. |
items[].created_at | Quando o registro foi criado, em UTC (AAAA-MM-DDTHH:MM:SSZ). |
items[].answers | Uma resposta para cada uma das 7 perguntas. |
items[].answers[].question_code | Código da pergunta (Q1 a Q7). |
items[].answers[].option_code | Código da opção escolhida (1 a 4). |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 404 | PPP001001 | Customer Not Found | The customer_key does not exist. | Cliente não encontrado. |
Produtos e preços
Catálogo de produtos
Os produtos de investimento: títulos do Tesouro, CDB e LCI do banco e o fundo FGV Quant Capital. Dá para filtrar por tipo e por risco máximo, por exemplo para mostrar só o que cabe no perfil do cliente.
Endpoint/products
MétodoGET
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Parâmetros de query
| Campo | Tipo | Descrição | Valores aceitos |
|---|
type | string | Filtra o catálogo por tipo de produto. | PUBLIC_BOND · PRIVATE_BOND · FUND |
maximum_risk_level | string | Risco máximo de produto. No perfil: até que risco ele aceita (Conservador 2, Moderado 3, Arrojado 5). No filtro do catálogo: só produtos com risco até este (1 a 5). | de 1 a 5 |
* obrigatório.
Exemplo de requisição
curl -X GET 'http://localhost:3000/products' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>'
Respostas
200 Produtos.
STATUS 200Catálogo completo
{
"items": [
{
"product_key": "681c7fed-81ea-483f-8a5f-caef127c411c",
"name": "Tesouro Selic 2031",
"type": "PUBLIC_BOND",
"issuer": "Tesouro Nacional",
"indexer": "SELIC",
"rate_description": "100% da Selic",
"risk_level": 1,
"term_months": null,
"minimum_application_amount": 5000
},
{
"product_key": "6bcbbf83-3789-4d74-8f99-f944b569d36e",
"name": "CDB Triple P Bank Liquidez",
"type": "PRIVATE_BOND",
"issuer": "Triple P Bank",
"indexer": "CDI",
"rate_description": "95% do CDI",
"risk_level": 2,
"term_months": null,
"minimum_application_amount": 5000
},
{
"product_key": "cf85fbe3-a0b0-4e7b-b534-a8a602641c41",
"name": "LCI Triple P Bank 12 meses",
"type": "PRIVATE_BOND",
"issuer": "Triple P Bank",
"indexer": "CDI",
"rate_description": "93% do CDI, isento de IR",
"risk_level": 2,
"term_months": 12,
"minimum_application_amount": 10000
},
{
"product_key": "3e60dd05-c365-467b-90fa-dfebf7db22ef",
"name": "Tesouro Prefixado 2029",
"type": "PUBLIC_BOND",
"issuer": "Tesouro Nacional",
"indexer": "PREFIXED",
"rate_description": "13% a.a.",
"risk_level": 2,
"term_months": null,
"minimum_application_amount": 5000
},
{
"product_key": "eff42ff3-f013-440d-9ea2-760ecc9e3fa6",
"name": "Tesouro IPCA+ 2035",
"type": "PUBLIC_BOND",
"issuer": "Tesouro Nacional",
"indexer": "IPCA",
"rate_description": "IPCA + 6% a.a.",
"risk_level": 3,
"term_months": null,
"minimum_application_amount": 5000
},
{
"product_key": "cdf625c2-ce65-4211-890b-712c49cb8c8d",
"name": "FGV Quant Capital FIM",
"type": "FUND",
"issuer": "FGV Quant Capital",
"indexer": "QUOTA",
"rate_description": "Varia com a cota",
"risk_level": 3,
"term_months": null,
"minimum_application_amount": 10000
}
]
}
STATUS 200Só risco até 2
{
"items": [
{
"product_key": "681c7fed-81ea-483f-8a5f-caef127c411c",
"name": "Tesouro Selic 2031",
"type": "PUBLIC_BOND",
"issuer": "Tesouro Nacional",
"indexer": "SELIC",
"rate_description": "100% da Selic",
"risk_level": 1,
"term_months": null,
"minimum_application_amount": 5000
},
{
"product_key": "6bcbbf83-3789-4d74-8f99-f944b569d36e",
"name": "CDB Triple P Bank Liquidez",
"type": "PRIVATE_BOND",
"issuer": "Triple P Bank",
"indexer": "CDI",
"rate_description": "95% do CDI",
"risk_level": 2,
"term_months": null,
"minimum_application_amount": 5000
},
{
"product_key": "cf85fbe3-a0b0-4e7b-b534-a8a602641c41",
"name": "LCI Triple P Bank 12 meses",
"type": "PRIVATE_BOND",
"issuer": "Triple P Bank",
"indexer": "CDI",
"rate_description": "93% do CDI, isento de IR",
"risk_level": 2,
"term_months": 12,
"minimum_application_amount": 10000
},
{
"product_key": "3e60dd05-c365-467b-90fa-dfebf7db22ef",
"name": "Tesouro Prefixado 2029",
"type": "PUBLIC_BOND",
"issuer": "Tesouro Nacional",
"indexer": "PREFIXED",
"rate_description": "13% a.a.",
"risk_level": 2,
"term_months": null,
"minimum_application_amount": 5000
}
]
}
Campos da resposta
| Campo | Descrição |
|---|
items | A lista de resultados. |
items[].product_key | Identificador do produto (UUID). |
items[].name | Nome. No cadastro: nome completo do cliente, de 3 a 120 caracteres, sem espaço nas pontas. |
items[].type | Tipo da movimentação: DEPOSIT, WITHDRAWAL, TRANSFER, INVESTMENT_APPLY ou INVESTMENT_REDEEM. |
items[].issuer | Quem emitiu o produto. |
items[].indexer | Indexador: SELIC, CDI, PREFIXED, IPCA ou QUOTA (fundo). |
items[].rate_description | Rentabilidade em texto, para mostrar ao cliente ("95% do CDI"). |
items[].risk_level | Risco do produto, de 1 (menor) a 5. |
items[].term_months | Prazo de cada aplicação, em meses (a LCI: 12). Nulo: resgate a qualquer dia. Com prazo, só resgata no vencimento. |
items[].minimum_application_amount | Aplicação mínima, em centavos. |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 400 | PPP000001 | Bad Request | (varia: diz o que está errado) | Payload Inválido |
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
Produtos e preços
Consultar produto
O produto e o preço do dia (o último publicado até a data de movimento). O preço sai como texto com 8 casas decimais.
Endpoint/product/{product_key}
MétodoGET
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Parâmetros de caminho
| Campo | Descrição |
|---|
product_key | Identificador do produto (UUID). |
Exemplo de requisição
curl -X GET 'http://localhost:3000/product/cdf625c2-ce65-4211-890b-712c49cb8c8d' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>'
Respostas
200 Produto com o preço do dia.
STATUS 200Fundo com o preço do dia
{
"product_key": "cdf625c2-ce65-4211-890b-712c49cb8c8d",
"name": "FGV Quant Capital FIM",
"type": "FUND",
"issuer": "FGV Quant Capital",
"indexer": "QUOTA",
"rate_description": "Varia com a cota",
"risk_level": 3,
"term_months": null,
"minimum_application_amount": 10000,
"price": {
"price_date": "2026-11-03",
"unit_price": "102.00000000"
}
}
STATUS 404Produto inexistente
{
"title": "Product Not Found",
"description": "The product_key does not exist.",
"translation": "Produto não encontrado.",
"code": "PPP005001"
}
Campos da resposta
| Campo | Descrição |
|---|
product_key | Identificador do produto (UUID). |
name | Nome. No cadastro: nome completo do cliente, de 3 a 120 caracteres, sem espaço nas pontas. |
type | Tipo da movimentação: DEPOSIT, WITHDRAWAL, TRANSFER, INVESTMENT_APPLY ou INVESTMENT_REDEEM. |
issuer | Quem emitiu o produto. |
indexer | Indexador: SELIC, CDI, PREFIXED, IPCA ou QUOTA (fundo). |
rate_description | Rentabilidade em texto, para mostrar ao cliente ("95% do CDI"). |
risk_level | Risco do produto, de 1 (menor) a 5. |
term_months | Prazo de cada aplicação, em meses (a LCI: 12). Nulo: resgate a qualquer dia. Com prazo, só resgata no vencimento. |
minimum_application_amount | Aplicação mínima, em centavos. |
price | O preço do dia do produto (o último publicado até a data de movimento). |
price.price_date | Data de movimento a que o preço se refere. |
price.unit_price | Preço de uma cota, como texto com 8 casas decimais ("1.25000000"), para não ser lido como float. |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 404 | PPP005001 | Product Not Found | The product_key does not exist. | Produto não encontrado. |
Produtos e preços
Histórico de preços
Os preços publicados do produto, um por data de movimento, do mais antigo para o mais novo.
Endpoint/product/{product_key}/prices
MétodoGET
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Parâmetros de caminho
| Campo | Descrição |
|---|
product_key | Identificador do produto (UUID). |
Parâmetros de query
| Campo | Tipo | Descrição | Valores aceitos |
|---|
from | string | Data inicial do período (AAAA-MM-DD), inclusive, no calendário do banco (data de movimento). | data AAAA-MM-DD |
to | string | Data final do período (AAAA-MM-DD), inclusive, no calendário do banco (data de movimento). | data AAAA-MM-DD |
* obrigatório.
Exemplo de requisição
curl -X GET 'http://localhost:3000/product/cdf625c2-ce65-4211-890b-712c49cb8c8d/prices' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>'
Respostas
200 Preços.
STATUS 200Preços do fundo
{
"items": [
{
"price_date": "2026-10-11",
"unit_price": "105.33466498"
},
{
"price_date": "2026-10-12",
"unit_price": "102.00000000"
},
{
"price_date": "2026-10-13",
"unit_price": "102.00000000"
},
{
"price_date": "2026-10-14",
"unit_price": "102.00000000"
},
{
"price_date": "2026-10-15",
"unit_price": "102.00000000"
},
{
"price_date": "2026-10-16",
"unit_price": "102.00000000"
},
{
"price_date": "2026-10-19",
"unit_price": "102.00000000"
},
{
"price_date": "2026-10-20",
"unit_price": "102.00000000"
},
{
"price_date": "2026-10-21",
"unit_price": "102.00000000"
},
{
"price_date": "2026-10-22",
"unit_price": "102.00000000"
},
{
"price_date": "2026-10-23",
"unit_price": "102.00000000"
},
{
"price_date": "2026-10-26",
"unit_price": "102.00000000"
},
{
"price_date": "2026-10-27",
"unit_price": "102.00000000"
},
{
"price_date": "2026-10-28",
"unit_price": "102.00000000"
},
{
"price_date": "2026-10-29",
"unit_price": "102.00000000"
},
{
"price_date": "2026-10-30",
"unit_price": "102.00000000"
},
{
"price_date": "2026-11-02",
"unit_price": "102.00000000"
},
{
"price_date": "2026-11-03",
"unit_price": "102.00000000"
}
]
}
Campos da resposta
| Campo | Descrição |
|---|
items | A lista de resultados. |
items[].price_date | Data de movimento a que o preço se refere. |
items[].unit_price | Preço de uma cota, como texto com 8 casas decimais ("1.25000000"), para não ser lido como float. |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 400 | PPP000001 | Bad Request | (varia: diz o que está errado) | Payload Inválido |
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 404 | PPP005001 | Product Not Found | The product_key does not exist. | Produto não encontrado. |
Carteira sugerida
Sugestão de carteira
Divide um valor entre os produtos da carteira-modelo do perfil do cliente, com o peso de cada um. É só simulação: não olha saldo e não grava nada. Os valores somam exatamente o pedido; os centavos que sobram do arredondamento vão para o produto de maior peso.
Endpoint/customer/{customer_key}/portfolio_suggestion
MétodoGET
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Parâmetros de caminho
| Campo | Descrição |
|---|
customer_key | Identificador do cliente (UUID). É ele que vai no caminho das rotas /customer/.... |
Parâmetros de query
| Campo | Tipo | Descrição | Valores aceitos |
|---|
amount* | string | Valor a dividir pela carteira, em centavos. Mínimo R$ 1.000,00 (100000). | inteiro, só dígitos |
* obrigatório.
Exemplo de requisição
curl -X GET 'http://localhost:3000/customer/4f9a35d6-e269-4e84-a9f6-b50552c5ce56/portfolio_suggestion?amount=100000' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>'
Respostas
200 Sugestão.
STATUS 200R$ 1.000,00 na carteira Moderada
{
"profile": "MODERATE",
"amount": 100000,
"items": [
{
"product_key": "681c7fed-81ea-483f-8a5f-caef127c411c",
"name": "Tesouro Selic 2031",
"weight_bps": 2500,
"amount": 25000
},
{
"product_key": "6bcbbf83-3789-4d74-8f99-f944b569d36e",
"name": "CDB Triple P Bank Liquidez",
"weight_bps": 2000,
"amount": 20000
},
{
"product_key": "3e60dd05-c365-467b-90fa-dfebf7db22ef",
"name": "Tesouro Prefixado 2029",
"weight_bps": 2000,
"amount": 20000
},
{
"product_key": "eff42ff3-f013-440d-9ea2-760ecc9e3fa6",
"name": "Tesouro IPCA+ 2035",
"weight_bps": 2000,
"amount": 20000
},
{
"product_key": "cdf625c2-ce65-4211-890b-712c49cb8c8d",
"name": "FGV Quant Capital FIM",
"weight_bps": 1500,
"amount": 15000
}
]
}
STATUS 422Valor abaixo do mínimo
{
"title": "Suggestion Amount Too Low",
"description": "The amount for a portfolio suggestion must be at least 100000 cents (R$ 1,000.00).",
"translation": "O valor mínimo para pedir uma sugestão de carteira é R$ 1.000,00.",
"code": "PPP006001"
}
Campos da resposta
| Campo | Descrição |
|---|
profile | Perfil do investidor: CONSERVATIVE, MODERATE ou AGGRESSIVE. |
amount | Valor em centavos, inteiro e maior que zero (R$ 10,50 = 1050). |
items | A lista de resultados. |
items[].product_key | Identificador do produto (UUID). |
items[].name | Nome. No cadastro: nome completo do cliente, de 3 a 120 caracteres, sem espaço nas pontas. |
items[].weight_bps | Peso do produto na carteira, em pontos-base (10000 = 100%). |
items[].amount | Valor em centavos, inteiro e maior que zero (R$ 10,50 = 1050). |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 400 | PPP000001 | Bad Request | (varia: diz o que está errado) | Payload Inválido |
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 404 | PPP001001 | Customer Not Found | The customer_key does not exist. | Cliente não encontrado. |
| 409 | PPP004004 | Suitability Expired | The investor profile is more than 24 months old and must be answered again. | O perfil de investidor tem mais de 24 meses e precisa ser refeito. |
| 409 | PPP004005 | Suitability Required | The customer needs an investor profile before this operation. | O cliente precisa responder o questionário de perfil de investidor antes desta operação. |
| 422 | PPP006001 | Suggestion Amount Too Low | The amount for a portfolio suggestion must be at least 100000 cents (R$ 1,000.00). | O valor mínimo para pedir uma sugestão de carteira é R$ 1.000,00. |
Ordens e posição
Aplicar ou resgatar
A ordem é feita na conta do caminho, como as movimentações; conta de outro cliente responde 404. Aplicação (APPLY): debita a conta e compra cotas ao preço do dia (cotas = valor ÷ preço, truncadas em 8 casas). Exige perfil válido e produto dentro do risco do perfil. Resgate (REDEEM): vende cotas ao preço do dia e credita a conta; pode ser por valor ou total (redeem_all), e não depende do perfil. Produto com prazo (a LCI) só é resgatado no vencimento da aplicação. A resposta do resgate traz o custo das cotas vendidas, o resultado e a rentabilidade.
Endpoint/customer/{customer_key}/account/{account_key}/investment_order
MétodoPOST
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Idempotency-Key | sim | UUID novo por pedido; reenviar com a mesma chave não repete o efeito. Ver Idempotência. |
Content-Type | sim | application/json |
Parâmetros de caminho
| Campo | Descrição |
|---|
customer_key | Identificador do cliente (UUID). É ele que vai no caminho das rotas /customer/.... |
account_key | Identificador da conta (UUID). |
Corpo da requisição
| Campo | Tipo | Descrição | Valores aceitos |
|---|
product_key* | string | Identificador do produto (UUID). | até 36 caracteres |
side* | string | APPLY (aplicação) ou REDEEM (resgate). | APPLY · REDEEM |
amount | integer | Valor em centavos. Obrigatório na aplicação; no resgate, use amount (resgate por valor) ou redeem_all (resgate total). | |
redeem_all | boolean | Resgate total: vende todas as cotas. Use true no lugar de amount. | só true |
* obrigatório.
Exemplo de requisição
curl -X POST 'http://localhost:3000/customer/4f9a35d6-e269-4e84-a9f6-b50552c5ce56/account/b043541b-19bc-4c23-a079-b18cbff9354c/investment_order' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>' \
-H 'Idempotency-Key: ecad6b89-2c4a-4ce2-9762-d8728aeaacae' \
-H 'Content-Type: application/json' \
-d '{"product_key": "cdf625c2-ce65-4211-890b-712c49cb8c8d", "side": "APPLY", "amount": 100000}'
Respostas
201 Ordem executada.
200 Repetição do mesmo pedido: devolve a ordem original sem aplicar ou resgatar de novo.
STATUS 201Aplicação de R$ 1.000,00 no fundo
{
"investment_order_key": "a2b5b62b-fd40-421a-bf37-7c3b3808895a",
"product_key": "cdf625c2-ce65-4211-890b-712c49cb8c8d",
"side": "APPLY",
"source": "CUSTOMER",
"amount": 100000,
"unit_price": "102.00000000",
"quantity": "9.80392156",
"maturity_date": null,
"status": "EXECUTED",
"transaction_key": "5beee41e-48dd-400a-afb7-21a32b5d870f"
}
STATUS 201Aplicação de R$ 100,00 na LCI (com vencimento)
{
"investment_order_key": "be2b11b2-d793-4f30-84a4-caa70db8271c",
"product_key": "cf85fbe3-a0b0-4e7b-b534-a8a602641c41",
"side": "APPLY",
"source": "CUSTOMER",
"amount": 10000,
"unit_price": "111.82266423",
"quantity": "0.89427309",
"maturity_date": "2027-11-03",
"status": "EXECUTED",
"transaction_key": "0346f1fb-72e2-43fb-8b52-334658341ac8"
}
STATUS 201Resgate de R$ 300,00 do fundo
{
"investment_order_key": "880df567-a99c-4f5f-9b55-244c0aed464c",
"product_key": "cdf625c2-ce65-4211-890b-712c49cb8c8d",
"side": "REDEEM",
"source": "CUSTOMER",
"amount": 30000,
"unit_price": "102.00000000",
"quantity": "2.94117647",
"maturity_date": null,
"status": "EXECUTED",
"transaction_key": "4bd7a432-2b2c-49d0-a497-f0cd5b6755ec",
"acquisition_cost_amount": 30000,
"result_amount": 0,
"return_bps": 0
}
STATUS 422Abaixo da aplicação mínima
{
"title": "Below Minimum Application",
"description": "The amount is below the product's minimum application.",
"translation": "O valor está abaixo da aplicação mínima do produto.",
"code": "PPP007002"
}
STATUS 422Resgate da LCI antes do vencimento
{
"title": "Redemption Not Allowed",
"description": "This product can only be redeemed at maturity.",
"translation": "Este produto só pode ser resgatado no vencimento.",
"code": "PPP007005"
}
Campos da resposta
| Campo | Descrição |
|---|
investment_order_key | Identificador da ordem de investimento (UUID). |
product_key | Identificador do produto (UUID). |
side | APPLY (aplicação) ou REDEEM (resgate). |
source | Quem pediu a ordem: CUSTOMER ou MATURITY (o fechamento do dia, pagando uma aplicação vencida). |
amount | Valor em centavos, inteiro e maior que zero (R$ 10,50 = 1050). |
unit_price | Preço de uma cota, como texto com 8 casas decimais ("1.25000000"), para não ser lido como float. |
quantity | Quantidade de cotas, como texto com 8 casas decimais. |
maturity_date | Vencimento desta aplicação (só em produto com prazo). Nulo nos outros casos. |
status | Status da conta: ACTIVE (movimenta), BLOCKED (não movimenta, só consulta) ou CLOSED (encerrada). |
transaction_key | Identificador da movimentação (UUID). |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 400 | PPP000001 | Bad Request | (varia: diz o que está errado) | Payload Inválido |
| 400 | PPP000003 | Missing Idempotency Key | This route moves money and requires the Idempotency-Key header with a UUID. | Esta operação exige o cabeçalho Idempotency-Key com um UUID. |
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 404 | PPP001001 | Customer Not Found | The customer_key does not exist. | Cliente não encontrado. |
| 404 | PPP002001 | Account Not Found | The account_key does not exist for this customer. | Conta não encontrada. |
| 404 | PPP005001 | Product Not Found | The product_key does not exist. | Produto não encontrado. |
| 409 | PPP000004 | Idempotency Key Reused | This Idempotency-Key was already used by another request. | Esta Idempotency-Key já foi usada em outro pedido. |
| 409 | PPP002005 | Account Not Active | The account is blocked or closed and cannot move money. | A conta está bloqueada ou encerrada e não pode movimentar dinheiro. |
| 409 | PPP003003 | Insufficient Balance | The account does not have enough balance for amount plus fee. | Saldo insuficiente para o valor mais a tarifa. |
| 409 | PPP004004 | Suitability Expired | The investor profile is more than 24 months old and must be answered again. | O perfil de investidor tem mais de 24 meses e precisa ser refeito. |
| 409 | PPP004005 | Suitability Required | The customer needs an investor profile before this operation. | O cliente precisa responder o questionário de perfil de investidor antes desta operação. |
| 409 | PPP007004 | Insufficient Position | The redemption is larger than the customer's position in this product. | O resgate é maior do que o que você tem aplicado neste produto. |
| 422 | PPP003004 | Invalid Amount | The amount must be an integer greater than zero, in cents. | O valor precisa ser maior que zero, em centavos. |
| 422 | PPP007002 | Below Minimum Application | The amount is below the product's minimum application. | O valor está abaixo da aplicação mínima do produto. |
| 422 | PPP007003 | Product Not Suitable | The product's risk level is above what the investor profile allows. | O risco do produto está acima do permitido para o seu perfil de investidor. |
| 422 | PPP007005 | Redemption Not Allowed | This product can only be redeemed at maturity. | Este produto só pode ser resgatado no vencimento. |
Ordens e posição
Consultar ordem
A ordem, com o mesmo corpo da resposta de criação. Ordem de outra conta ou de outro cliente responde 404.
Endpoint/customer/{customer_key}/account/{account_key}/investment_order/{investment_order_key}
MétodoGET
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Parâmetros de caminho
| Campo | Descrição |
|---|
customer_key | Identificador do cliente (UUID). É ele que vai no caminho das rotas /customer/.... |
account_key | Identificador da conta (UUID). |
investment_order_key | Identificador da ordem de investimento (UUID). |
Exemplo de requisição
curl -X GET 'http://localhost:3000/customer/4f9a35d6-e269-4e84-a9f6-b50552c5ce56/account/b043541b-19bc-4c23-a079-b18cbff9354c/investment_order/a2b5b62b-fd40-421a-bf37-7c3b3808895a' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>'
Respostas
200 Ordem.
STATUS 200Ordem de aplicação
{
"investment_order_key": "a2b5b62b-fd40-421a-bf37-7c3b3808895a",
"product_key": "cdf625c2-ce65-4211-890b-712c49cb8c8d",
"side": "APPLY",
"source": "CUSTOMER",
"amount": 100000,
"unit_price": "102.00000000",
"quantity": "9.80392156",
"maturity_date": null,
"status": "EXECUTED",
"transaction_key": "5beee41e-48dd-400a-afb7-21a32b5d870f"
}
Campos da resposta
| Campo | Descrição |
|---|
investment_order_key | Identificador da ordem de investimento (UUID). |
product_key | Identificador do produto (UUID). |
side | APPLY (aplicação) ou REDEEM (resgate). |
source | Quem pediu a ordem: CUSTOMER ou MATURITY (o fechamento do dia, pagando uma aplicação vencida). |
amount | Valor em centavos, inteiro e maior que zero (R$ 10,50 = 1050). |
unit_price | Preço de uma cota, como texto com 8 casas decimais ("1.25000000"), para não ser lido como float. |
quantity | Quantidade de cotas, como texto com 8 casas decimais. |
maturity_date | Vencimento desta aplicação (só em produto com prazo). Nulo nos outros casos. |
status | Status da conta: ACTIVE (movimenta), BLOCKED (não movimenta, só consulta) ou CLOSED (encerrada). |
transaction_key | Identificador da movimentação (UUID). |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 404 | PPP001001 | Customer Not Found | The customer_key does not exist. | Cliente não encontrado. |
| 404 | PPP002001 | Account Not Found | The account_key does not exist for this customer. | Conta não encontrada. |
| 404 | PPP007001 | Order Not Found | The investment_order_key does not exist for this customer. | Ordem não encontrada. |
Ordens e posição
Histórico de ordens
As ordens da conta, da mais nova para a mais antiga, em páginas, com filtro opcional por produto (produto inexistente devolve lista vazia).
Endpoint/customer/{customer_key}/account/{account_key}/investment_orders
MétodoGET
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Parâmetros de caminho
| Campo | Descrição |
|---|
customer_key | Identificador do cliente (UUID). É ele que vai no caminho das rotas /customer/.... |
account_key | Identificador da conta (UUID). |
Parâmetros de query
| Campo | Tipo | Descrição | Valores aceitos |
|---|
page | string | Número da página, a partir de 1 (padrão 1). | inteiro a partir de 1 |
limit | string | Itens por página, de 1 a 100 (padrão 20). | de 1 a 100 |
product_key | string | Identificador do produto (UUID). | até 36 caracteres |
* obrigatório.
Exemplo de requisição
curl -X GET 'http://localhost:3000/customer/4f9a35d6-e269-4e84-a9f6-b50552c5ce56/account/b043541b-19bc-4c23-a079-b18cbff9354c/investment_orders' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>'
Respostas
200 Uma página de ordens.
STATUS 200Ordens da conta
{
"items": [
{
"investment_order_key": "880df567-a99c-4f5f-9b55-244c0aed464c",
"product_key": "cdf625c2-ce65-4211-890b-712c49cb8c8d",
"side": "REDEEM",
"source": "CUSTOMER",
"amount": 30000,
"unit_price": "102.00000000",
"quantity": "2.94117647",
"maturity_date": null,
"status": "EXECUTED",
"transaction_key": "4bd7a432-2b2c-49d0-a497-f0cd5b6755ec",
"acquisition_cost_amount": 30000,
"result_amount": 0,
"return_bps": 0
},
{
"investment_order_key": "be2b11b2-d793-4f30-84a4-caa70db8271c",
"product_key": "cf85fbe3-a0b0-4e7b-b534-a8a602641c41",
"side": "APPLY",
"source": "CUSTOMER",
"amount": 10000,
"unit_price": "111.82266423",
"quantity": "0.89427309",
"maturity_date": "2027-11-03",
"status": "EXECUTED",
"transaction_key": "0346f1fb-72e2-43fb-8b52-334658341ac8"
},
{
"investment_order_key": "a2b5b62b-fd40-421a-bf37-7c3b3808895a",
"product_key": "cdf625c2-ce65-4211-890b-712c49cb8c8d",
"side": "APPLY",
"source": "CUSTOMER",
"amount": 100000,
"unit_price": "102.00000000",
"quantity": "9.80392156",
"maturity_date": null,
"status": "EXECUTED",
"transaction_key": "5beee41e-48dd-400a-afb7-21a32b5d870f"
}
],
"page": 1,
"limit": 20,
"total": 3
}
Campos da resposta
| Campo | Descrição |
|---|
items | A lista de resultados. |
items[].investment_order_key | Identificador da ordem de investimento (UUID). |
items[].product_key | Identificador do produto (UUID). |
items[].side | APPLY (aplicação) ou REDEEM (resgate). |
items[].source | Quem pediu a ordem: CUSTOMER ou MATURITY (o fechamento do dia, pagando uma aplicação vencida). |
items[].amount | Valor em centavos, inteiro e maior que zero (R$ 10,50 = 1050). |
items[].unit_price | Preço de uma cota, como texto com 8 casas decimais ("1.25000000"), para não ser lido como float. |
items[].quantity | Quantidade de cotas, como texto com 8 casas decimais. |
items[].maturity_date | Vencimento desta aplicação (só em produto com prazo). Nulo nos outros casos. |
items[].status | Status da conta: ACTIVE (movimenta), BLOCKED (não movimenta, só consulta) ou CLOSED (encerrada). |
items[].transaction_key | Identificador da movimentação (UUID). |
items[].acquisition_cost_amount | Custo de aquisição das cotas, em centavos. Na posição: quanto custaram as cotas que a conta tem. No resgate: o custo das cotas vendidas (custo médio). |
items[].result_amount | Resultado em centavos: valor menos custo de aquisição. Pode ser negativo. |
items[].return_bps | Rentabilidade em pontos-base: resultado ÷ custo (400 = 4%). |
page | Número da página, a partir de 1 (padrão 1). |
limit | Itens por página, de 1 a 100 (padrão 20). |
total | Total de itens que passam pelos filtros, somando todas as páginas. |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 400 | PPP000001 | Bad Request | (varia: diz o que está errado) | Payload Inválido |
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 404 | PPP001001 | Customer Not Found | The customer_key does not exist. | Cliente não encontrado. |
| 404 | PPP002001 | Account Not Found | The account_key does not exist for this customer. | Conta não encontrada. |
Ordens e posição
Posição e capital total
Cada investimento ao preço do dia (valor atual, custo, resultado e rentabilidade) e o total: quanto o cliente tem nesta conta (caixa + investimentos) e a divisão entre os dois, em pontos-base. É calculado na hora da consulta. Posições zeradas só aparecem com include_closed=true.
Endpoint/customer/{customer_key}/account/{account_key}/positions
MétodoGET
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
Parâmetros de caminho
| Campo | Descrição |
|---|
customer_key | Identificador do cliente (UUID). É ele que vai no caminho das rotas /customer/.... |
account_key | Identificador da conta (UUID). |
Parâmetros de query
| Campo | Tipo | Descrição | Valores aceitos |
|---|
include_closed | string | true para incluir posições zeradas. | true · false |
* obrigatório.
Exemplo de requisição
curl -X GET 'http://localhost:3000/customer/4f9a35d6-e269-4e84-a9f6-b50552c5ce56/account/b043541b-19bc-4c23-a079-b18cbff9354c/positions' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>'
Respostas
200 Posições e total.
STATUS 200Posições e capital total
{
"items": [
{
"product_key": "cf85fbe3-a0b0-4e7b-b534-a8a602641c41",
"quantity": "0.89427309",
"unit_price": "111.82266423",
"current_amount": 9999,
"acquisition_cost_amount": 10000,
"result_amount": -1,
"return_bps": -1
},
{
"product_key": "cdf625c2-ce65-4211-890b-712c49cb8c8d",
"quantity": "6.86274509",
"unit_price": "102.00000000",
"current_amount": 69999,
"acquisition_cost_amount": 70000,
"result_amount": -1,
"return_bps": 0
}
],
"total": {
"investments_amount": 79998,
"acquisition_cost_amount": 80000,
"result_amount": -2,
"return_bps": 0,
"cash_amount": 99800,
"net_worth_amount": 179798,
"cash_bps": 5550,
"investments_bps": 4450
}
}
Campos da resposta
| Campo | Descrição |
|---|
items | A lista de resultados. |
items[].product_key | Identificador do produto (UUID). |
items[].quantity | Quantidade de cotas, como texto com 8 casas decimais. |
items[].unit_price | Preço de uma cota, como texto com 8 casas decimais ("1.25000000"), para não ser lido como float. |
items[].current_amount | Valor das cotas deste produto ao preço do dia, em centavos. |
items[].acquisition_cost_amount | Custo de aquisição das cotas, em centavos. Na posição: quanto custaram as cotas que a conta tem. No resgate: o custo das cotas vendidas (custo médio). |
items[].result_amount | Resultado em centavos: valor menos custo de aquisição. Pode ser negativo. |
items[].return_bps | Rentabilidade em pontos-base: resultado ÷ custo (400 = 4%). |
total | Totais da posição: soma dos investimentos e o capital do cliente no banco. |
total.investments_amount | Valor de todas as posições ao preço do dia, em centavos. Fica fora do balance: o que foi aplicado saiu do saldo. |
total.acquisition_cost_amount | Custo de aquisição das cotas, em centavos. Na posição: quanto custaram as cotas que a conta tem. No resgate: o custo das cotas vendidas (custo médio). |
total.result_amount | Resultado em centavos: valor menos custo de aquisição. Pode ser negativo. |
total.return_bps | Rentabilidade em pontos-base: resultado ÷ custo (400 = 4%). |
total.cash_amount | Dinheiro na conta (o saldo), em centavos. |
total.net_worth_amount | Capital total do cliente no banco: caixa + investimentos ao preço do dia, em centavos. |
total.cash_bps | Parte do capital que está em caixa, em pontos-base. |
total.investments_bps | Parte do capital que está investida, em pontos-base (10000 − cash_bps). |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 400 | PPP000001 | Bad Request | (varia: diz o que está errado) | Payload Inválido |
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 404 | PPP001001 | Customer Not Found | The customer_key does not exist. | Cliente não encontrado. |
| 404 | PPP002001 | Account Not Found | The account_key does not exist for this customer. | Conta não encontrada. |
Operação interna
Bloquear, desbloquear ou encerrar conta (banco)
A operação do banco muda o status de qualquer conta de cliente. Bloquear e encerrar exigem motivo; encerrar exige saldo zero e nenhum investimento. Um evento de status é gravado com actor = BANK.
Endpoint/internal/account/{account_key}/status
MétodoPATCH
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
OPERATOR-TOKEN | sim | Token da operação do banco (variável OPERATOR_TOKEN). |
Content-Type | sim | application/json |
Parâmetros de caminho
| Campo | Descrição |
|---|
account_key | Identificador da conta (UUID). |
Corpo da requisição
| Campo | Tipo | Descrição | Valores aceitos |
|---|
status* | string | Status da conta: ACTIVE (movimenta), BLOCKED (não movimenta, só consulta) ou CLOSED (encerrada). | ACTIVE · BLOCKED · CLOSED |
reason | string | Motivo da mudança de status, até 255 caracteres. Obrigatório para bloquear e encerrar. | até 255 caracteres |
* obrigatório.
Exemplo de requisição
curl -X PATCH 'http://localhost:3000/internal/account/4ec7839a-1587-4d71-99de-7b2576e03be5/status' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>' \
-H 'OPERATOR-TOKEN: <OPERATOR_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"status": "BLOCKED", "reason": "Suspeita de fraude em análise"}'
Respostas
200 Status alterado.
STATUS 200Banco bloqueia a conta
{
"account_key": "4ec7839a-1587-4d71-99de-7b2576e03be5",
"status": "BLOCKED",
"balance": 20000,
"investments_amount": 0,
"net_worth_amount": 20000,
"created_at": "2026-10-11T19:43:59Z",
"updated_at": "2026-10-11T19:44:00Z"
}
STATUS 200Banco desbloqueia a conta
{
"account_key": "4ec7839a-1587-4d71-99de-7b2576e03be5",
"status": "ACTIVE",
"balance": 20000,
"investments_amount": 0,
"net_worth_amount": 20000,
"created_at": "2026-10-11T19:43:59Z",
"updated_at": "2026-10-11T19:44:00Z"
}
Campos da resposta
| Campo | Descrição |
|---|
account_key | Identificador da conta (UUID). |
status | Status da conta: ACTIVE (movimenta), BLOCKED (não movimenta, só consulta) ou CLOSED (encerrada). |
balance | Saldo da conta (só o dinheiro parado nela, sem os investimentos), em centavos. Na resposta de uma movimentação, é o saldo logo depois dela. |
investments_amount | Valor de todas as posições ao preço do dia, em centavos. Fica fora do balance: o que foi aplicado saiu do saldo. |
net_worth_amount | Capital total do cliente no banco: caixa + investimentos ao preço do dia, em centavos. |
created_at | Quando o registro foi criado, em UTC (AAAA-MM-DDTHH:MM:SSZ). |
updated_at | Quando o registro mudou pela última vez, em UTC. |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 400 | PPP000001 | Bad Request | (varia: diz o que está errado) | Payload Inválido |
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 403 | PPP000007 | Operator Only | This route belongs to the bank operation and requires the OPERATOR-TOKEN header. | Esta rota é da operação do banco e exige o token de operador. |
| 404 | PPP002001 | Account Not Found | The account_key does not exist for this customer. | Conta não encontrada. |
| 409 | PPP002003 | Invalid Status Transition | The account cannot move from its current status to the requested one. | A conta não pode passar do status atual para o status pedido. |
| 409 | PPP002004 | Account Not Empty | The account must have zero balance and no investment position to be closed. | Só é possível encerrar uma conta sem saldo e sem investimentos. |
| 422 | PPP002006 | Status Reason Required | Blocking or closing an account requires a reason. | Bloquear ou encerrar a conta exige um motivo. |
Operação interna
Data de movimento
O "hoje" do banco. Só avança pelo fechamento do dia.
Endpoint/internal/business_date
MétodoGET
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
OPERATOR-TOKEN | sim | Token da operação do banco (variável OPERATOR_TOKEN). |
Exemplo de requisição
curl -X GET 'http://localhost:3000/internal/business_date' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>' \
-H 'OPERATOR-TOKEN: <OPERATOR_TOKEN>'
Respostas
200 Data de movimento atual.
STATUS 200Data atual
{
"business_date": "2026-11-03"
}
Campos da resposta
| Campo | Descrição |
|---|
business_date | Data de movimento: o "hoje" do banco, a data real simulada. Num lançamento ou comprovante, o dia de movimento em que a transação aconteceu. |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 403 | PPP000007 | Operator Only | This route belongs to the bank operation and requires the OPERATOR-TOKEN header. | Esta rota é da operação do banco e exige o token de operador. |
Operação interna
Fechar o dia
Busca os preços do dia (Tesouro e administradora do fundo) e as taxas CDI e Selic nas fontes externas, calcula o preço do CDB e da LCI, grava a provisão de receita do dia que fecha (a foto do fim do dia e o que o banco ganharia com ela até o próximo dia; a de sexta vale 3 dias), paga as aplicações da LCI que vencem (transações do dia novo) e abre o próximo dia útil. É tudo ou nada: se uma fonte falhar, nada é gravado e a data não anda. close_date precisa ser a data de movimento atual, então um clique duplo não fecha dois dias.
Endpoint/internal/day_close
MétodoPOST
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
OPERATOR-TOKEN | sim | Token da operação do banco (variável OPERATOR_TOKEN). |
Content-Type | sim | application/json |
Corpo da requisição
| Campo | Tipo | Descrição | Valores aceitos |
|---|
close_date* | string | Data de movimento que está sendo fechada. Precisa ser a data atual (GET /internal/business_date). | data AAAA-MM-DD |
* obrigatório.
Exemplo de requisição
curl -X POST 'http://localhost:3000/internal/day_close' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>' \
-H 'OPERATOR-TOKEN: <OPERATOR_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{"close_date": "2026-11-03"}'
Respostas
201 Dia fechado; a resposta traz a nova data, os preços e as taxas.
STATUS 409Data que não é a atual
{
"title": "Business Date Mismatch",
"description": "The close_date is not the current business date.",
"translation": "A data a fechar não é a data de movimento atual.",
"code": "PPP000006"
}
STATUS 201Dia fechado
{
"business_date": "2026-11-04",
"prices": [
{
"product_key": "681c7fed-81ea-483f-8a5f-caef127c411c",
"name": "Tesouro Selic 2031",
"unit_price": "112.91439545"
},
{
"product_key": "6bcbbf83-3789-4d74-8f99-f944b569d36e",
"name": "CDB Triple P Bank Liquidez",
"unit_price": "112.15034516"
},
{
"product_key": "cf85fbe3-a0b0-4e7b-b534-a8a602641c41",
"name": "LCI Triple P Bank 12 meses",
"unit_price": "111.87999782"
},
{
"product_key": "3e60dd05-c365-467b-90fa-dfebf7db22ef",
"name": "Tesouro Prefixado 2029",
"unit_price": "111.20586561"
},
{
"product_key": "eff42ff3-f013-440d-9ea2-760ecc9e3fa6",
"name": "Tesouro IPCA+ 2035",
"unit_price": "109.29617633"
},
{
"product_key": "cdf625c2-ce65-4211-890b-712c49cb8c8d",
"name": "FGV Quant Capital FIM",
"unit_price": "110.61135282"
}
],
"rates": [
{
"code": "CDI",
"annual_rate_bps": 1490
},
{
"code": "SELIC",
"annual_rate_bps": 1500
}
]
}
Campos da resposta
| Campo | Descrição |
|---|
business_date | Data de movimento: o "hoje" do banco, a data real simulada. Num lançamento ou comprovante, o dia de movimento em que a transação aconteceu. |
prices | Os preços gravados no fechamento, um por produto. |
prices[].product_key | Identificador do produto (UUID). |
prices[].name | Nome. No cadastro: nome completo do cliente, de 3 a 120 caracteres, sem espaço nas pontas. |
prices[].unit_price | Preço de uma cota, como texto com 8 casas decimais ("1.25000000"), para não ser lido como float. |
rates | As taxas de referência do novo dia. |
rates[].code | Código. Na pergunta: Q1 a Q7. Na opção: 1 a 4, que é também a pontuação. Na taxa: CDI ou SELIC. |
rates[].annual_rate_bps | Taxa ao ano, em pontos-base (1490 = 14,9%). |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 400 | PPP000001 | Bad Request | (varia: diz o que está errado) | Payload Inválido |
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 403 | PPP000007 | Operator Only | This route belongs to the bank operation and requires the OPERATOR-TOKEN header. | Esta rota é da operação do banco e exige o token de operador. |
| 409 | PPP000006 | Business Date Mismatch | The close_date is not the current business date. | A data a fechar não é a data de movimento atual. |
| 502 | PPP005006 | Price Source Unavailable | An external price or rate source is down, timed out or answered an error. | Uma fonte externa de preço ou taxa está fora do ar ou respondeu erro. Nada foi gravado. |
| 502 | PPP005007 | Invalid Price Source Response | An external price or rate source answered something that makes no sense. | Uma fonte externa de preço ou taxa respondeu algo sem sentido. Nada foi gravado. |
Operação interna
Receita do banco
Quanto o banco recebeu de tarifas no período (a parte realizada, inclusive no dia ainda aberto) e quanto estima ter ganho com a taxa do fundo, o spread do CDB e da LCI e o float. O período é em datas de movimento. As estimativas são as provisões que o fechamento gravou para cada dia fechado: base do fim do dia × taxa do dia × dias corridos até o próximo dia de movimento / 365, truncado em centavos (a de sexta vale 3 dias). O dia aberto ainda não tem provisão. Cada dia aparece em daily, e o valor é a soma deles. Nada disso movimenta dinheiro. Sem datas, usa o mês da data de movimento atual.
Endpoint/internal/revenue
MétodoGET
Cabeçalhos
| Cabeçalho | Obrigatório | Descrição |
|---|
INTERNAL-TOKEN | sim | Token interno da API (variável INTERNAL_TOKEN). |
OPERATOR-TOKEN | sim | Token da operação do banco (variável OPERATOR_TOKEN). |
Parâmetros de query
| Campo | Tipo | Descrição | Valores aceitos |
|---|
from | string | Data inicial do período (AAAA-MM-DD), inclusive, no calendário do banco (data de movimento). | data AAAA-MM-DD |
to | string | Data final do período (AAAA-MM-DD), inclusive, no calendário do banco (data de movimento). | data AAAA-MM-DD |
* obrigatório.
Exemplo de requisição
curl -X GET 'http://localhost:3000/internal/revenue' \
-H 'INTERNAL-TOKEN: <INTERNAL_TOKEN>' \
-H 'OPERATOR-TOKEN: <OPERATOR_TOKEN>'
Respostas
200 Receita no período.
STATUS 200Tarifas do mês
{
"from": "2026-11-01",
"to": "2026-11-30",
"days": 1,
"realized": {
"fee_amount": 2600
},
"estimated": {
"fund_administration_fee": {
"amount": 1,
"daily": [
{
"date": "2026-11-02",
"calendar_days": 1,
"base_amount": 49999,
"annual_rate_bps": 100,
"amount": 1
}
]
},
"own_issue_spread": {
"amount": 1,
"daily": [
{
"date": "2026-11-02",
"calendar_days": 1,
"base_amount": 50000,
"cdi_annual_rate_bps": 1490,
"amount": 1
}
]
},
"float": {
"amount": 56821,
"daily": [
{
"date": "2026-11-02",
"calendar_days": 1,
"base_amount": 138266700,
"annual_rate_bps": 1500,
"amount": 56821
}
]
}
},
"total_amount": 59423
}
Campos da resposta
| Campo | Descrição |
|---|
from | Data inicial do período (AAAA-MM-DD), inclusive, no calendário do banco (data de movimento). |
to | Data final do período (AAAA-MM-DD), inclusive, no calendário do banco (data de movimento). |
days | Quantos dias fechados do período têm provisão (o dia aberto ainda não tem). |
realized | A receita que de fato entrou na conta do banco no período. |
realized.fee_amount | Soma das tarifas que entraram na conta do banco no período, em centavos. |
estimated | O que o banco estima ter ganho no período com o dinheiro sob sua guarda: as provisões gravadas no fechamento de cada dia. Não movimenta dinheiro. |
estimated.fund_administration_fee | Taxa de administração do fundo: por dia, patrimônio do fundo no fim do dia × taxa × dias corridos / 365. |
estimated.fund_administration_fee.amount | Valor estimado, em centavos (truncado para baixo em cada provisão). Na estimativa, é a soma dos dias. Pode ser zero. |
estimated.fund_administration_fee.daily | O detalhe da estimativa: a provisão de cada dia fechado do período. |
estimated.fund_administration_fee.daily[].date | O dia de movimento que fechou. |
estimated.fund_administration_fee.daily[].calendar_days | Dias corridos que a provisão cobre, até o próximo dia de movimento: 1 de segunda a quinta, 3 na sexta (o fim de semana rende). |
estimated.fund_administration_fee.daily[].base_amount | Base do dia, em centavos: o patrimônio do fundo ao preço do dia, o custo em carteira de CDB e LCI ou a soma dos saldos de cliente, no fim do dia. |
estimated.fund_administration_fee.daily[].annual_rate_bps | Taxa anual do dia, em pontos-base: a taxa de administração do fundo ou a Selic vigente no dia. |
estimated.fund_administration_fee.daily[].amount | Valor estimado, em centavos (truncado para baixo em cada provisão). Na estimativa, é a soma dos dias. Pode ser zero. |
estimated.own_issue_spread | Spread do CDB e da LCI: por dia, custo em carteira × CDI do dia × (1 − percentual do CDI pago) × dias corridos / 365, somado entre os produtos. |
estimated.own_issue_spread.amount | Valor estimado, em centavos (truncado para baixo em cada provisão). Na estimativa, é a soma dos dias. Pode ser zero. |
estimated.own_issue_spread.daily | O detalhe da estimativa: a provisão de cada dia fechado do período. |
estimated.own_issue_spread.daily[].date | O dia de movimento que fechou. |
estimated.own_issue_spread.daily[].calendar_days | Dias corridos que a provisão cobre, até o próximo dia de movimento: 1 de segunda a quinta, 3 na sexta (o fim de semana rende). |
estimated.own_issue_spread.daily[].base_amount | Base do dia, em centavos: o patrimônio do fundo ao preço do dia, o custo em carteira de CDB e LCI ou a soma dos saldos de cliente, no fim do dia. |
estimated.own_issue_spread.daily[].cdi_annual_rate_bps | CDI anual vigente no dia, em pontos-base. |
estimated.own_issue_spread.daily[].amount | Valor estimado, em centavos (truncado para baixo em cada provisão). Na estimativa, é a soma dos dias. Pode ser zero. |
estimated.float | Float: por dia, saldo das contas de cliente no fim do dia (sem a conta do banco) × Selic do dia × dias corridos / 365. |
estimated.float.amount | Valor estimado, em centavos (truncado para baixo em cada provisão). Na estimativa, é a soma dos dias. Pode ser zero. |
estimated.float.daily | O detalhe da estimativa: a provisão de cada dia fechado do período. |
estimated.float.daily[].date | O dia de movimento que fechou. |
estimated.float.daily[].calendar_days | Dias corridos que a provisão cobre, até o próximo dia de movimento: 1 de segunda a quinta, 3 na sexta (o fim de semana rende). |
estimated.float.daily[].base_amount | Base do dia, em centavos: o patrimônio do fundo ao preço do dia, o custo em carteira de CDB e LCI ou a soma dos saldos de cliente, no fim do dia. |
estimated.float.daily[].annual_rate_bps | Taxa anual do dia, em pontos-base: a taxa de administração do fundo ou a Selic vigente no dia. |
estimated.float.daily[].amount | Valor estimado, em centavos (truncado para baixo em cada provisão). Na estimativa, é a soma dos dias. Pode ser zero. |
total_amount | Receita total do período: realizada + estimadas, em centavos. |
Erros
| HTTP | Código | Título | Descrição (en) | Descrição (pt) |
|---|
| 400 | PPP000001 | Bad Request | (varia: diz o que está errado) | Payload Inválido |
| 400 | PPP000005 | Invalid Parameter | (varia: diz o que está errado) | Parâmetros inválidos foram fornecidos na requisição. |
| 403 | PPP000002 | Forbidden | Request must be internal | Requisição precisa ser interna |
| 403 | PPP000007 | Operator Only | This route belongs to the bank operation and requires the OPERATOR-TOKEN header. | Esta rota é da operação do banco e exige o token de operador. |
Saúde
Identificação do serviço
Nome do serviço e o processo que respondeu. Não exige token.
Exemplo de requisição
curl -X GET 'http://localhost:3000/'
Respostas
200 Serviço no ar.
STATUS 200Serviço no ar
{
"service": "bootcamp-library-api",
"id": "8"
}
Campos da resposta
| Campo | Descrição |
|---|
service | Nome do serviço. |
id | Identificador do processo da API que respondeu. |
Saúde
Health check
Confere que a API e o banco de dados estão respondendo. Não exige token. Responde sem corpo.
Endpoint/health_check
MétodoGET
Exemplo de requisição
curl -X GET 'http://localhost:3000/health_check'
Respostas
204 Tudo no ar.
STATUS 204Tudo no ar
Sem corpo.