# QI Tech — Investment-as-a-Service › Primeiros Passos

Documentação da QI Tech em texto corrido, para colar em um LLM.
Fonte: https://docs.qitech.com.br
8 página(s).

Índice:
- Recebimento de Webhooks (/documentation/iaas/introducao/autenticacao_webhooks)
- Erros da API (/documentation/iaas/introducao/erros)
- Introdução (/documentation/iaas/introducao/inicio)
- Integração pelo portal (/documentation/iaas/introducao/integracao_via_portal)
- Pacote de Endpoints (/documentation/iaas/introducao/pacote_endpoints)
- Endpoints de teste (/documentation/iaas/introducao/teste_de_autenticacao/endpoints_de_teste)
- Teste de autenticação (/documentation/iaas/introducao/teste_de_autenticacao/)
- Troca de Chaves (/documentation/iaas/introducao/troca_de_chaves)

---

# Recebimento de Webhooks

URL: /documentation/iaas/introducao/autenticacao_webhooks

A QI DTVM envia cada webhook como um `POST` com body JSON para a URL que você configurou. A requisição leva o header `SIGNATURE`, um JWT assinado com HS256 usando a _Signature Key_ da sua configuração de webhook: uma chave simétrica, que só a QI e você conhecem.

## Validar a assinatura

```python
import json
from hashlib import md5

from jose import jwt

signature_key = "SUA_SIGNATURE_KEY"
webhook_url = "https://sua-empresa.com.br/webhooks/qi"  # a URL cadastrada na configuração

def validate_webhook(headers: dict, raw_body: bytes) -> dict:
    # jwt.decode recusa o token se a assinatura não bater com a sua Signature Key.
    claims = jwt.decode(headers["SIGNATURE"], key=signature_key, algorithms=["HS256"])

    if claims["uri"] != webhook_url:
        raise ValueError("uri diferente da URL configurada")
    if claims.get("payload_md5") != md5(raw_body).hexdigest():
        raise ValueError("payload_md5 não corresponde ao body recebido")

    return json.loads(raw_body)
```

O JWT decodificado traz:

| Campo         | Descrição                                                                               |
| ------------- | --------------------------------------------------------------------------------------- |
| `timestamp`   | Data e hora da assinatura, em UTC, no formato `AAAA-MM-DDTHH:MM:SS`. Muda a cada envio. |
| `method`      | Método HTTP da requisição: `POST`.                                                      |
| `uri`         | A URL de destino configurada para o seu webhook, completa.                              |
| `payload_md5` | Hash MD5 do body enviado.                                                               |

Calcule o MD5 sobre os **bytes recebidos**, antes de fazer o parse do JSON. Um _re-encode_ do JSON depois do parse muda espaçamento e ordem dos campos, e o hash deixa de bater.

## Formato do body

Todo webhook tem o mesmo envelope. O conteúdo de `data` depende do evento e está na página de webhooks de cada produto.

```json
{
  "webhook_type": "settlement.payment_batch_status_change",
  "webhook_datetime": "2026-10-07T14:32:10Z",
  "data": {}
}
```

| Campo              | Descrição                                                                 |
| ------------------ | ------------------------------------------------------------------------- |
| `webhook_type`     | Evento, no formato `<produto>.<evento>`.                                  |
| `webhook_datetime` | Data e hora em que o evento foi gerado. É a mesma em todas as tentativas. |
| `data`             | Dados do evento: um objeto ou uma lista, conforme o evento.               |

:::danger Atenção!

Não mapeie os webhooks de forma restrita: campos novos podem ser incluídos no envelope e em `data` a qualquer momento.

:::

## Header `AGENT-KEY`

O header `AGENT-KEY` traz a chave do **agente dono da configuração de webhook**, ou seja, a sua chave: o `manager_key` da gestora, o `consultant_key` da consultoria ou o `assignor_key` do cedente. Ele não identifica o fundo. Para saber de qual fundo é o evento, use os campos de `data`, como `fund_class_key`.

## Entrega e tentativas

- A entrega conta como bem-sucedida quando a sua URL responde com status HTTP **menor que 300** em até **30 segundos**. Qualquer outro status, timeout ou erro de rede conta como falha.
- Cada evento é enviado **até 4 vezes**. Depois da 4ª falha, ele é marcado como falho e não é mais reenviado automaticamente.
- Responda rápido: grave o webhook e processe depois. O tempo que a sua aplicação leva para processar conta para os 30 segundos.

:::info Webhook que não chegou

Um webhook que esgotou as tentativas pode ser reenviado pela QI DTVM. Fale com [integracao.dtvm@qitech.com.br](mailto:integracao.dtvm@qitech.com.br) informando o período e o tipo de evento. O reenvio não é uma operação disponível na sua integração.

:::

## Duplicatas

Você pode receber o mesmo webhook mais de uma vez, por dois motivos:

1. A sua aplicação recebeu o webhook, mas a resposta não chegou à QI dentro do prazo. O envio é repetido.
2. O mesmo evento pode ter de ser entregue a mais de um agente: por exemplo, à gestora, à consultoria e ao cedente do fundo. Esses envios são tentados juntos e em sequência. Se a URL de **qualquer** um deles falhar, a tentativa inteira é repetida, e quem já tinha recebido recebe de novo.

O segundo caso significa que uma falha na URL de outro agente gera duplicatas na sua. Trate o recebimento como idempotente: a repetição chega com o **mesmo body, byte a byte** (o mesmo `webhook_type`, `webhook_datetime` e `data`), então o mesmo `payload_md5`. Guarde o `payload_md5` dos webhooks já processados e descarte o que repetir. Não use o JWT do header para isso: o `timestamp` muda a cada envio.

## Validação de origem

Além de validar a assinatura, você pode restringir a sua URL aos IPs de saída da QI:

| Ambiente | IP             |
| -------- | -------------- |
| Produção | 54.205.166.229 |
| Sandbox  | 52.72.221.4    |

---

# Erros da API

URL: /documentation/iaas/introducao/erros

Toda resposta de erro das APIs do IaaS tem o mesmo corpo. O status HTTP e o `code` dizem o que aconteceu; o prefixo do `code` diz em que área.

## Formato do corpo de erro

| Campo         | Tipo   | Descrição                                                                                              |
| ------------- | ------ | ------------------------------------------------------------------------------------------------------ |
| `title`       | string | Nome curto do erro, em inglês.                                                                         |
| `description` | string | Descrição em inglês. Em erros de validação, traz o campo que falhou.                                   |
| `translation` | string | Descrição em português.                                                                                |
| `code`        | string | Três letras que indicam a área, seguidas de seis dígitos. Use este campo no seu tratamento de erro. |

Exemplo: uma integração de gestora sem a permissão de escrita chamando um `POST`.

```json
{
  "title": "Manager does not have permission to access this endpoint",
  "description": "Manager does not have permission to access this endpoint",
  "translation": "Gestor nao tem permissão para acessar esse endpoint",
  "code": "MIT000017"
}
```

Trate o erro pelo status HTTP e pelo `code`. Os textos de `title`, `description` e `translation` podem mudar.

## Prefixo do `code`

| Prefixo | Onde o erro aconteceu                                                                                         |
| ------- | ------------------------------------------------------------------------------------------------------------- |
| `MIT`   | Host da gestora (`manager-api`): autenticação, permissão e roteamento.                                        |
| `CIT`   | Host da consultoria (`consultant-api`): autenticação, permissão e roteamento.                                 |
| `AIT`   | Host do cedente (`assignor-api`): autenticação, permissão e roteamento.                                       |
| `QIT`   | Erro genérico, em qualquer host ou serviço: rota inexistente, método não aceito, body inválido, erro interno. |
| `SET`   | Liquidação de ativos (`/settlement`).                                                                         |
| `TRC`   | Cessão de recebíveis (`/trade_receivables`).                                                                  |
| `TRF`   | Arquivos de cessão (`/trade_receivables_files`).                                                              |
| `TRR`   | Venda e recompra de ativos (`/trade_resolve`).                                                                |
| `TTR`   | Boletador de títulos públicos (`/trade_treasury`).                                                            |
| `ASR`   | Homologação de cedente (`/assignor_registry`).                                                                |
| `ASS`   | Cedentes (`/assignor`).                                                                                       |
| `ACT`   | Contrato de cessão (`/assignment_contract`).                                                                  |
| `AAM`   | Aditamento de recebíveis (`/asset_amendment`).                                                                |
| `ADF`   | Documentos de ativos (`/asset_document_files`).                                                               |
| `BSC`   | Boletos (`/bankslip_collection`).                                                                             |
| `CSH`   | Contas e extrato (`/cash_account`).                                                                           |
| `TSF`   | Transferência interna (`/transfer`).                                                                          |
| `TRV`   | Estorno de transação (`/transaction_reversal`).                                                               |
| `CMP`   | Composição de carteira (`/composition`).                                                                      |
| `WLT`   | Carteira (`/wallet`).                                                                                         |
| `EXP`   | Despesas (`/expense`).                                                                                        |
| `IVR`   | Cadastro de investidor (`/investor_registry`).                                                                |
| `IAD`   | Termo de adesão (`/investor_adhesion`).                                                                       |
| `QTA`   | Cotas (`/quota`).                                                                                             |
| `QOF`   | Controle de oferta (`/quota_offering_control`).                                                               |
| `TFQ`   | Cotas de fundo como ativo (`/trade_fund_quota`).                                                              |

Um `400` com `code` `QIT000001` indica corpo da requisição inválido: o campo com problema vem em `description`.

## Autenticação

Os erros de header, chave de API e assinatura (`*000007` a `*000016` e `*000020`) e como corrigir cada um estão em [Teste de autenticação](/documentation/iaas/introducao/teste_de_autenticacao#erros-de-autenticacao).

## Permissão e rota

Depois de autenticar a requisição, o host confere se a sua integração pode chamar aquela rota. Falta de permissão volta como **401**, não 403: não procure erro de assinatura quando o `code` for um destes.

| Status | `code`      | Quando acontece                                                                                                           | O que fazer                                                                                                                                                                                                                                             |
| ------ | ----------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401    | `MIT000017` | A integração da gestora não tem a permissão que a rota exige: **Leitura**, **Escrita** ou as duas.                        | Confira a permissão no bloco "Disponível em" da página do endpoint e o status em **Permissões**, na tela da integração. Veja [liberação das permissões](/documentation/iaas/introducao/integracao_via_portal#6-liberacao-das-permissoes-da-integracao). |
| 401    | `CIT000018` | A consultoria não tem vínculo ativo com o fundo (`fund_class_key`) ou não tem, nesse fundo, a permissão que a rota exige. | Confira o `fund_class_key` e a permissão indicada em "Disponível em". As permissões da consultoria são dadas por fundo. Se a permissão aparece como **Liberada pelo time de integração**, ela não é configurável no portal: peça a liberação em [integracao.dtvm@qitech.com.br](mailto:integracao.dtvm@qitech.com.br).                                                                                                                                 |
| 401    | `AIT000017` | A integração do cedente não tem a permissão de leitura ou escrita que a rota exige.                                       | Fale com [integracao.dtvm@qitech.com.br](mailto:integracao.dtvm@qitech.com.br).                                                                                                                                                                         |
| 400    | `*000009`   | A integração está desativada.                                                                                             | Reative a integração ([como](/documentation/iaas/introducao/integracao_via_portal#desativar-e-reativar-a-integracao)) ou fale com o time de integração.                                                                                                 |
| 404    | `QIT000404` | O host não expõe esse caminho.                                                                                            | Numa rota documentada, quase sempre é a URL base de outro perfil: confira o bloco "Disponível em" da página e a [URL base do seu perfil](/documentation/iaas/introducao/inicio#ambientes-hosts). Confira também o caminho, sem a `base_url`.            |
| 405    | `QIT000405` | O caminho existe no host, mas não com esse método.                                                                        | Confira o método na página do endpoint.                                                                                                                                                                                                                 |

## Reenvio e duplicidade

Em caso de erro, reenvie a requisição. Se o recurso já existir, a criação devolve um erro de duplicidade em vez de criar outro: consulte o recurso existente.

| Criação                                   | `code` de duplicidade | Status | Como consultar o recurso existente                                                                                                                                 |
| ----------------------------------------- | --------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Lote de pagamento (liquidação)            | `SET000009`           | 409    | [Recuperação do lote](/documentation/iaas/liquidacao_ativos/lote_pagamento/recuperacao), pelo `external_id` do lote.                                              |
| Liquidação dentro de um lote              | `SET000013`           | 400    | [Recuperação da liquidação](/documentation/iaas/liquidacao_ativos/ativos/recuperacao), pelo `external_id` da liquidação.                                          |
| Lote de venda ou recompra                 | `TRR000015`           | 409    | `GET /trade_resolve/fund_class/{fund_class_key}/assignment/{assignment_external_id}`, exposto só no host do cedente. Gestora e consultoria não têm essa consulta. |
| Operação do boletador de títulos públicos | `TTR000044`           | 409    | `GET /trade_treasury/fund_class/{fund_class_key}/operations`, procurando o seu `external_id` na lista.                                                            |

:::caution Boletador: sempre envie `external_id`

No boletador o `external_id` é opcional. Envie-o sempre: sem ele, a API não reconhece uma operação já criada.

:::

## Erro interno

`500` com `QIT000500` é erro do lado da QI. Se persistir, envie a requisição (sem a chave privada), o horário em UTC e o `code` para [integracao.dtvm@qitech.com.br](mailto:integracao.dtvm@qitech.com.br).

---

# Introdução

URL: /documentation/iaas/introducao/inicio

A QI DTVM é uma instituição financeira que presta os serviços de Administração e Custódia de Fundos de Investimento. Nós temos uma série de serviços, utilizando REST APIs que viabilizam uma nova experiência em toda a operação, visando facilidades, automações e uma total transparência para os envolvidos.

Essa documentação tem como objetivo descrever os fluxos, endpoints e estruturas de dados necessárias para operar quaisquer Fundos de Investimentos.

Obs.: Em caso de dúvidas em qualquer etapa do processo favor entre em contato com [integracao.dtvm@qitech.com.br](mailto:integracao.dtvm@qitech.com.br) detalhando seu problema/dúvida que te auxiliaremos.

## Perfis de Acesso

No nosso sistema nós reconhecemos o usuário pelo perfil que ele assume dentro da estrutura da QI DTVM. Possuímos 6 perfis:

1. Gestores;
2. Consultores;
3. Originadores;
4. Cedentes;
5. Investidores;
6. Distribuidores.

Cada perfil tem um host próprio, com as rotas liberadas para ele — veja [Ambientes (Hosts)](#ambientes-hosts).

Para criarmos um Perfil de Acesso para utilização das nossas APIs é necessário que se entre em contato com nosso time através do e-mail [integracao.dtvm@qitech.com.br](mailto:integracao.dtvm@qitech.com.br).

Gestoras e consultorias solicitam apenas o **nome, e-mail e CPF** do usuário master responsável e, a partir daí, criam a integração e cadastram a chave pública pelo próprio portal — sem enviar chaves por e-mail. Veja [Integração pelo portal](/documentation/iaas/introducao/integracao_via_portal).

## Por onde começar

1. Crie a integração: [Integração pelo portal](/documentation/iaas/introducao/integracao_via_portal) (gestoras e consultorias) ou [Troca de chaves](/documentation/iaas/introducao/troca_de_chaves) (demais perfis).
2. Faça a primeira chamada: [Teste de autenticação](/documentation/iaas/introducao/teste_de_autenticacao).
3. Saiba ler os erros e reenviar com segurança: [Erros da API](/documentation/iaas/introducao/erros).

## Ambientes (Hosts)

A QI DTVM possui dois ambientes, SANDBOX e PRODUÇÃO. Ambos os ambientes possuem código e comportamento completamente idênticos, porém, o ambiente de SANDBOX apresenta valores monetários totalmente fictícios, e o ambiente de Produção realiza transações financeiras válidas.

O ambiente Sandbox foi criado para os desenvolvedores realizarem suas integrações, e quando estiverem prontos para entrada em produção, atualizarem apenas as variáveis de ambiente com os parâmetros de Produção.

Além dos hosts por perfil, há um host público em produção, `https://api.qidtvm.com.br`.

:::danger Aviso Importante!

Não devem ser usados dados reais de pessoas físicas e/ou jurídicas nos ambientes de Sandbox da QI Tech.

:::

---

# Integração pelo portal

URL: /documentation/iaas/introducao/integracao_via_portal

Gestoras e consultorias criam a própria integração de API pelo portal: criam a integração, cadastram a chave pública e recebem ali mesmo a API Key. Nenhuma chave trafega por e-mail.

:::tip Este é o caminho padrão

Cadastrar a chave pública pela tela é o procedimento recomendado para gestoras e consultorias, em sandbox e em produção. O envio da chave pública por e-mail é tratado como exceção — veja [Quando o envio por e-mail ainda é usado](#quando-o-envio-por-e-mail-ainda-e-usado).

Pelo portal a chave é cadastrada pelo próprio responsável, com confirmação explícita e fingerprint visível na tela. Isso elimina o repasse manual de arquivos entre caixas de e-mail, reduz o risco de a chave errada ser cadastrada e permite criar novas integrações a qualquer momento, sem abrir chamado.

:::

## Visão geral do fluxo

| Etapa | Quem faz | Onde |
| ----- | -------- | ---- |
| 1. Cadastro do usuário master | Time de integração da QI Tech | A partir do contato por e-mail ou WhatsApp |
| 2. Acesso ao portal | Usuário master | Portal do Gestor ou Portal do Consultor |
| 3. Criação dos demais usuários e permissões | Usuário master | Portal, em **Gestão de Acesso > Usuários** |
| 4. Criação da integração | Usuário master ou usuário com permissão | Portal, em **Gestão de Acesso > Integração API** |
| 5. Cadastro da chave pública | Usuário com permissão | Portal, na tela da integração |
| 6. Liberação das permissões da integração | Time de integração da QI Tech | Aparece na própria tela quando concluída |
| 7. Configuração de webhooks (opcional) | Usuário com permissão | Portal, na tela da integração |

## 1. Solicitar o cadastro do usuário master

Entre em contato com o time de integração da QI Tech por e-mail ([integracao.dtvm@qitech.com.br](mailto:integracao.dtvm@qitech.com.br)) ou WhatsApp informando, do responsável pela homologação:

- **Nome completo**
- **E-mail corporativo**
- **CPF**

Com esses dados criamos o **usuário master** da gestora ou da consultoria no ambiente solicitado. Esse usuário é o ponto de partida: é ele quem cria os demais usuários, concede permissões e cria as integrações de API.

:::danger Não envie chaves por e-mail

A solicitação contém apenas nome, e-mail e CPF do responsável. **Não envie a chave pública nessa mensagem** — ela é cadastrada por você mesmo no portal, na etapa 5. E a chave privada nunca é enviada a ninguém, em nenhuma hipótese: a QI Tech jamais pedirá que você a compartilhe.

:::

## 2. Acessar o portal

| Perfil | Ambiente | Portal |
| ------ | -------- | ------ |
| Gestora | Sandbox | https://portal-do-gestor.sandbox.fundos.qitech.com.br/ |
| Gestora | Produção | https://portal-do-gestor.fundos.qitech.com.br/ |
| Consultoria | Sandbox | https://portal-do-consultor.sandbox.fundos.qitech.com.br/ |
| Consultoria | Produção | https://portal-do-consultor.fundos.qitech.com.br/ |

O acesso é feito por login único (SSO) com o e-mail cadastrado na etapa anterior.

## 3. Criar os demais usuários e conceder permissões

Somente o usuário master (ou quem ele autorizar) enxerga a tela de integração. Para dar acesso a outras pessoas do time:

1. Acesse **Gestão de Acesso > Usuários** e clique em **Criar usuário**.
2. Informe **Nome**, **Sobrenome**, **E-mail** e **CPF**.
3. Abra o usuário criado e conceda a permissão de integração:
   - Gestora: **Gerenciar Integração API**
   - Consultoria: **Gerenciar integração com a API**

Sem essa permissão o item **Integração API** não aparece no menu.

## 4. Criar a integração

Em **Gestão de Acesso > Integração API**, clique em **Criar integração** e informe um nome que identifique o uso (por exemplo, `ETL noturno` ou `Backoffice`).

Ao confirmar, a QI Tech gera as credenciais da integração e a tela de detalhe é aberta. Elas ficam no bloco **Credenciais**, no fim da tela, e podem ser copiadas a qualquer momento pelo mesmo caminho:

![Bloco Credenciais da tela da integração no portal, com a API Key e a URL base da API em destaque](/img/iaas-portal-credenciais.webp)

| Credencial | Para que serve |
| ---------- | -------------- |
| **API Key** | Vai no header `API-CLIENT-KEY` de todas as requisições. É o `api_key` do [Teste de autenticação](/documentation/iaas/introducao/teste_de_autenticacao). |
| **URL base da API** | O host que recebe as chamadas desta integração, já no perfil e no ambiente do portal. É o `base_url` do [Teste de autenticação](/documentation/iaas/introducao/teste_de_autenticacao). |
| **Client Integration Key** | Identificador da integração. Use para referenciá-la em contatos com o time de integração. |

A chave de API só é aceita nessa URL: o host do seu perfil (`manager-api` no Portal do Gestor, `consultant-api` no Portal do Consultor), no ambiente em que a integração foi criada. A lista completa de hosts está em [Ambientes (Hosts)](/documentation/iaas/introducao/inicio#ambientes-hosts).

Uma mesma gestora ou consultoria pode manter **várias integrações ativas ao mesmo tempo**, cada uma com sua própria chave de API e chave pública — útil para separar sistemas ou ambientes internos.

A integração nasce com status **Criada**. Ela só passa a **Ativa** depois que a chave pública é cadastrada.

## 5. Cadastrar a chave pública

Na tela da integração, clique em **Cadastrar chave pública**. Há duas formas de fazer isso.

### Opção A — Gerar o par de chaves no navegador

O portal gera o par direto no seu navegador e baixa a chave privada para a sua máquina. **A chave privada nunca é enviada à QI Tech**: só a chave pública é transmitida.

1. Escolha o **Algoritmo da chave**.
2. Clique em **Gerar par de chaves**. O download da chave privada começa automaticamente.
3. Guarde a chave privada em local seguro — ela não é exibida novamente e não pode ser recuperada. Se necessário, use **Baixar chave privada** e **Baixar chave pública** antes de sair da tela.
4. A chave pública já vem preenchida no formulário. Confirme para cadastrá-la.

Cada algoritmo determina o `alg` que você deve usar ao assinar o JWT das requisições:

| Algoritmo no portal | Assinatura do JWT |
| ------------------- | ----------------- |
| RSA 2048 (recomendado) | `RS256` |
| RSA 4096 | `RS256` |
| EC P-256 | `ES256` |
| EC P-384 | `ES384` |
| EC P-521 | `ES512` |

### Opção B — Enviar a sua própria chave pública

Se você já gerou o par fora do portal — veja [Troca de chaves](/documentation/iaas/introducao/troca_de_chaves) — envie apenas a chave pública:

- **Arraste o arquivo** para a área indicada, ou clique para selecioná-lo (`.pem`, `.pub`, `.key`, `.crt` ou `.txt`); ou
- **Cole o conteúdo** do PEM no campo de texto.

O portal identifica o algoritmo da chave e informa, abaixo do campo, com qual `alg` você deve assinar suas requisições.

### Requisitos e recusas

A chave precisa estar em PEM, no bloco `-----BEGIN PUBLIC KEY-----`. O portal recusa o cadastro nos casos abaixo:

| Situação | Motivo |
| -------- | ------ |
| Conteúdo de chave privada (`BEGIN ... PRIVATE KEY`) | A chave privada nunca deve ser enviada |
| Certificado (`BEGIN CERTIFICATE`) | Não é uma chave pública |
| Chave no formato OpenSSH (`ssh-rsa`, `ecdsa-sha2-...`) | Converta para PEM |
| RSA com menos de 2048 bits | Abaixo do mínimo aceito |
| PEM ilegível | Conteúdo corrompido ou incompleto |

Para confirmar, digite `CADASTRAR` no campo de confirmação. **O cadastro é imediato**: a integração passa a usar essa chave assim que você confirma.

:::caution A chave pública não pode ser trocada

Cada integração recebe uma única chave pública. Se precisar de outra chave — por rotação periódica, perda ou vazamento da chave privada —, crie uma nova integração. Veja [Substituir a chave de uma integração](#substituir-a-chave-de-uma-integracao).

:::

Concluído o cadastro, a tela exibe o **Fingerprint (SHA-256)** da chave e a data do cadastro. Use o fingerprint para conferir que a chave cadastrada é mesmo a sua.

## 6. Liberação das permissões da integração

:::info Apenas para gestoras

Consultorias não passam por esta etapa: a autorização é feita pelas permissões de fundo da consultoria, e a integração já fica pronta para uso depois do cadastro da chave.

:::

Para gestoras, o último passo é a liberação das permissões de **Leitura** e **Escrita** da integração, feita pelo time de integração da QI Tech. Não é preciso aguardar na tela — o status aparece em **Permissões** quando a liberação for concluída.

## 7. Configurar webhooks (opcional)

Ainda na tela da integração, o bloco **Webhooks** permite cadastrar a URL de destino das notificações. Os sistemas disponíveis são:

| Sistema | Eventos |
| ------- | ------- |
| Cadastro de cedentes | Análises, registro de cedentes e apontamentos |
| Contratos de cessão | Status de contratos de cessão e produtos |
| Recebíveis | Cessões e ativos da esteira de recebíveis |
| Liquidação | Lotes de pagamento e liquidações |

Você pode usar a mesma URL para todos os sistemas ou uma URL por sistema. Cada sistema gera uma **chave de assinatura (HMAC)** própria, usada para validar as entregas recebidas — veja [Recebimento de Webhooks](/documentation/iaas/introducao/autenticacao_webhooks).

:::caution Webhooks são configurados por agente

A configuração de webhooks vale para a gestora ou consultoria como um todo, não por integração. Se houver mais de uma integração de API, todas compartilham a mesma configuração.

:::

## Desativar e reativar a integração

**Desativar integração** (confirmando com `DESATIVAR`) faz com que toda chamada feita com aquela credencial passe a ser recusada. Nada é apagado: chave pública, permissões e webhooks continuam salvos e voltam a valer ao reativar.

Use a desativação como resposta imediata a uma suspeita de vazamento da chave privada. Como a chave não pode ser trocada, não reative a integração comprometida: substitua-a por uma nova.

## Substituir a chave de uma integração

Para passar a usar outra chave sem interromper a operação:

1. Crie uma nova integração ([etapa 4](#4-criar-a-integracao)) e cadastre nela a nova chave pública ([etapa 5](#5-cadastrar-a-chave-publica)).
2. Para gestoras, aguarde a liberação das permissões da nova integração ([etapa 6](#6-liberacao-das-permissoes-da-integracao)).
3. Atualize a sua aplicação com a **nova chave de API** e a **nova chave privada** — as duas mudam juntas.
4. Rode o [Teste de autenticação](/documentation/iaas/introducao/teste_de_autenticacao) com as novas credenciais.
5. Desative a integração antiga.

Os webhooks não precisam ser reconfigurados: a configuração vale para a gestora ou consultoria, não por integração.

## Entrada em produção

O procedimento em produção é o mesmo. Envie por e-mail ao time de integração o **nome, e-mail e CPF do usuário master** da gestora ou da consultoria no ambiente de produção. A partir do acesso desse usuário master, a criação dos demais usuários, a concessão de permissões e a criação das integrações de API são feitas por você, pelo portal — sem novo contato com o time.

As credenciais de sandbox não valem em produção: cada ambiente tem suas próprias integrações, chaves e API Keys.

:::danger Aviso Importante!

Não devem ser usados dados reais de pessoas físicas e/ou jurídicas nos ambientes de Sandbox da QI Tech.

:::

## Quando o envio por e-mail ainda é usado

O cadastro pelo portal está disponível para **gestoras** e **consultorias**. Os demais perfis de acesso — cedentes, originadores, investidores e distribuidores — continuam enviando a chave pública ao time de integração, conforme descrito em [Troca de chaves](/documentation/iaas/introducao/troca_de_chaves).

Se você é gestora ou consultoria e ainda não tem acesso ao portal, solicite o usuário master pela etapa 1 em vez de enviar a chave por e-mail.

---

# Pacote de Endpoints

URL: /documentation/iaas/introducao/pacote_endpoints

O pacote de endpoints é um conjunto de scripts Python, um por chamada da API do IaaS, organizados nas mesmas seções desta documentação. Cada script monta os headers `API-CLIENT-KEY` e `AUTHORIZATION`, envia a requisição e imprime o status e a resposta. Serve para quem está integrando e quer testar uma chamada em sandbox sem escrever a assinatura do zero.

**[Baixar o pacote (.zip)](/downloads/integracao_python_iaas.zip)**

## Configurar

1. Instale as dependências: `pip install -r requirements.txt`.
2. Copie `.env.example` para `.env`, na raiz do pacote, e preencha:

   | Variável           | O que é                                                                                                                                      |
   | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
   | `BASE_URL`         | URL base do host do **seu perfil** e ambiente. Veja [Ambientes (Hosts)](/documentation/iaas/introducao/inicio#ambientes-hosts).              |
   | `API_KEY`          | Chave de API da sua integração.                                                                                                              |
   | `PRIVATE_KEY_PATH` | Caminho do arquivo `.pem` da sua chave privada.                                                                                              |
   | `ALGORITHM`        | O algoritmo da sua chave: `RS256` para RSA; `ES256`, `ES384` ou `ES512` para EC P-256, P-384 ou P-521. A chave EC P-521 da [troca de chaves](/documentation/iaas/introducao/troca_de_chaves) usa `ES512`. |

3. Abra o script, troque os valores marcados com ` ` e rode. Comece por `5.1 - Primeiros passos/5.1 - GET Teste de autenticação.py`.

:::caution A chave de API só funciona no host do seu perfil

Nem toda chamada existe em todos os hosts. O cabeçalho de cada script lista os hosts que expõem aquela chamada, e o `README.md` do pacote traz a tabela por pasta. Chamada para o host de outro perfil é recusada com `Invalid API Key`.

:::

:::danger Aviso Importante!

Os scripts usam CPFs e CNPJs fictícios. Não use dados reais de pessoas físicas ou jurídicas em sandbox.

:::

---

# Endpoints de teste

URL: /documentation/iaas/introducao/teste_de_autenticacao/endpoints_de_teste

`/authentication_test` responde `200` só quando o host, a chave de API e a assinatura estão corretos. Use o `GET` para testar uma requisição sem body e o `POST` para testar também o `payload_md5`. Como montar os headers: [Teste de autenticação](/documentation/iaas/introducao/teste_de_autenticacao).

:::info Disponível em

| Perfil      | Host             | Permissão exigida |
| ----------- | ---------------- | ----------------- |
| Gestora     | `manager-api`    | —                 |
| Consultoria | `consultant-api` | —                 |
| Cedente     | `assignor-api`   | —                 |

URL base de cada host: [Ambientes (Hosts)](/documentation/iaas/introducao/inicio#ambientes-hosts).

:::

## GET

ENDPOINT /authentication_test
MÉTODO GET

Sem body e sem `payload_md5` na assinatura.

### Response

STATUS 200

```json
{
  "success": "Congrats!"
}
```

## POST

ENDPOINT /authentication_test
MÉTODO POST

### Atributos do body

name
string
obrigatório
Qualquer texto de 1 a 255 caracteres. A resposta devolve o mesmo valor. O body não aceita outros campos.

```json
{
  "name": "QI Tech"
}
```

### Response

STATUS 200

```json
{
  "name": "QI Tech",
  "success": "Congrats!"
}
```

## Erros

| Status | Código      | Quando acontece                                                                                 |
| ------ | ----------- | ----------------------------------------------------------------------------------------------- |
| 400    | `QIT000001` | `POST` sem `name`, com `name` vazio, com outro campo além de `name` ou com body que não é JSON. |
| 401    | `*000008`   | Chave de API inválida. Quase sempre é a URL base de outro perfil.                               |
| 401    | `*000016`   | O `payload_md5` assinado não bate com os bytes enviados no `POST`.                              |

Os demais erros de assinatura estão em [Erros de autenticação](/documentation/iaas/introducao/teste_de_autenticacao#erros-de-autenticacao).

Erros de autenticação, permissão e host: veja [Erros da API](/documentation/iaas/introducao/erros).

---

# Teste de autenticação

URL: /documentation/iaas/introducao/teste_de_autenticacao/

Toda requisição às APIs da QI DTVM leva dois headers:

- `API-CLIENT-KEY`: a chave de API da sua integração.
- `AUTHORIZATION`: um JWT assinado com a **sua** chave privada. A QI valida a assinatura com a chave pública que você cadastrou na [troca de chaves](/documentation/iaas/introducao/troca_de_chaves).

Esta página monta os dois headers em Python e chama `/authentication_test`, que só responde `200` quando host, chave de API e assinatura estão corretos. Faça este teste antes de qualquer outra chamada.

## 1. Escolha a URL base do seu perfil

Este é o erro mais comum na primeira chamada: copiar o exemplo com o host de outro perfil.

:::tip Gestoras e consultorias

Copie a URL base direto do portal: ela aparece no bloco **Credenciais** da integração, ao lado da API Key, já no perfil e no ambiente certos. Veja a imagem no passo 3.

:::

## 2. Instale e importe as bibliotecas

```bash
pip install "python-jose[cryptography]" requests
```

```python
from datetime import datetime, timezone
import json
from hashlib import md5

from jose import jwt
import requests
```

## 3. Informe a chave de API, a chave privada e o algoritmo

```python title="Dados da criptografia"
api_key = "SUA_API_KEY"

client_private_key = """-----BEGIN PRIVATE KEY-----
SUA_CHAVE_PRIVADA
-----END PRIVATE KEY-----"""

algorithm = "RS256"  # o algoritmo da sua chave; veja a tabela abaixo
```

Onde encontrar cada valor depende de como a sua integração foi criada:

| Valor                | Gestoras e consultorias (portal)                                                                                                                                                       | Cedentes, originadores, investidores e distribuidores                                                     |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `api_key`            | No portal, em **Gestão de Acesso > Integração API**: abra a integração e copie o campo **API Key** do bloco **Credenciais**.                                                          | Enviada pelo time de integração da QI depois da [troca de chaves](/documentation/iaas/introducao/troca_de_chaves). |
| `client_private_key` | O arquivo `qi-integration-...-private-key.pem` baixado ao gerar o par no portal, ou a chave privada do par que você mesmo gerou.                                                       | A chave privada do par que você gerou na [troca de chaves](/documentation/iaas/introducao/troca_de_chaves). |
| `algorithm`          | O indicado no portal abaixo do algoritmo escolhido: `RS256` para RSA (padrão do portal), `ES256`, `ES384` ou `ES512` para EC P-256, P-384 ou P-521.                                    | `ES512`, para a chave EC P-521 gerada na troca de chaves.                                                  |

![Bloco Credenciais da tela da integração no portal, com a API Key e a URL base da API em destaque](/img/iaas-portal-credenciais.webp)

Cole o conteúdo inteiro do arquivo da chave privada, com as linhas `BEGIN` e `END` — o cabeçalho pode ser `PRIVATE KEY` ou `EC PRIVATE KEY`, conforme a ferramenta que gerou o par. A chave de API identifica a integração, e a assinatura com a chave privada prova que a requisição é sua: trate as duas como credenciais. A chave privada nunca é enviada à QI. Em produção, carregue a chave privada de um cofre de segredos ou variável de ambiente — nunca a deixe no código-fonte.

O passo a passo do portal, da criação da integração ao cadastro da chave pública, está em [Integração pelo portal](/documentation/iaas/introducao/integracao_via_portal).

## 4. Defina os dados da requisição

```python title="Dados da requisição"
# Troque pela URL do seu perfil e ambiente (tabela do passo 1).
base_url = "URL_BASE_DO_SEU_PERFIL"  # ex.: "https://manager-api.sandbox.qidtvm.com.br"

method = "POST"
endpoint = "/authentication_test"
body = {"name": "QI Tech"}
```

:::caution Atenção

O campo `uri` da assinatura deve ser **idêntico** ao caminho enviado na requisição. Se o endpoint receber parâmetros na _query string_, eles também fazem parte da URI assinada — veja [Requisições com query string](#query-string).

:::

## 5. Monte o conteúdo da assinatura

```python title="Dicionário base"
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")

dict_to_sign = {"timestamp": timestamp, "method": method, "uri": endpoint}
```

O `timestamp` segue o formato `AAAA-MM-DDTHH:MM:SS`, sem fuso e sem milissegundos, é sempre em **UTC** e é aceito com até **10 minutos** de diferença do horário da QI. Gere um novo a cada requisição e mantenha o relógio do servidor sincronizado.

### 5.1. Se houver body, adicione o MD5

Requisições com _body_ incluem o MD5 dos bytes exatos que serão enviados:

```python title="Dicionário base"
body_bytes = json.dumps(body).encode()

dict_to_sign["payload_md5"] = md5(body_bytes).hexdigest()
```

Envie exatamente esses `body_bytes` no passo 7. Se o JSON for serializado de novo — com outra ordem de campos, espaçamento ou acentuação —, o MD5 deixa de bater.

## 6. Assine o JWT

```python
encoded_header_token = jwt.encode(
    claims=dict_to_sign,
    key=client_private_key,
    algorithm=algorithm,
    headers={"alg": algorithm, "typ": "JWT"},
)
```

O `algorithm` precisa ser da família da chave: `RS*` para RSA e `ES*` para EC. Se não for, o `jwt.encode` falha antes de a requisição sair, com um `JWSError` pouco descritivo — confira o `algorithm` primeiro.

## 7. Envie a requisição

```python
headers = {
    "API-CLIENT-KEY": api_key,
    "AUTHORIZATION": encoded_header_token,
    "Content-Type": "application/json",
}

resp = requests.post(url=f"{base_url}{endpoint}", headers=headers, data=body_bytes)
print(resp.status_code, resp.json())
```

A resposta esperada é `200` com `{"name": "QI Tech", "success": "Congrats!"}`. O contrato de `GET` e `POST /authentication_test` está em [Endpoints de teste](/documentation/iaas/introducao/teste_de_autenticacao/endpoints_de_teste).

## Erros de autenticação

O `code` dos erros de autenticação termina com o mesmo número em todos os hosts. O **prefixo** indica qual host respondeu: `MIT` para `manager-api`, `CIT` para `consultant-api` e `AIT` para `assignor-api`. Se o prefixo não é o do seu perfil, a `base_url` está errada.

| Final do `code` | Status | Erro                       | O que verificar                                                                                        |
| --------------- | ------ | -------------------------- | ------------------------------------------------------------------------------------------------------ |
| `000007`        | 401    | Header ausente             | Os dois headers, `API-CLIENT-KEY` e `AUTHORIZATION`, foram enviados.                                   |
| `000008`        | 401    | Chave de API inválida      | A `base_url` é a do seu perfil e do ambiente certo, e a chave de API foi copiada sem espaços.                |
| `000009`        | 400    | Integração inativa         | A integração está ativa. Fale com [integracao.dtvm@qitech.com.br](mailto:integracao.dtvm@qitech.com.br). |
| `000010`        | 401    | Chave pública pendente     | A chave pública já foi cadastrada na integração.                                                       |
| `000011`        | 401    | Autenticação inválida      | O JWT foi assinado com a chave privada que corresponde à chave pública cadastrada.                     |
| `000012`        | 401    | Assinatura incompleta      | O JWT tem os campos `timestamp`, `method` e `uri`.                                                     |
| `000013`        | 401    | Método inválido            | O `method` assinado é o mesmo da requisição, em maiúsculas.                                            |
| `000014`        | 401    | Assinatura expirada        | O `timestamp` está em UTC, foi gerado na hora da requisição e o relógio do servidor está sincronizado.              |
| `000015`        | 401    | Endpoint inválido          | O `uri` assinado é idêntico ao caminho enviado, incluindo a _query string_ e sem a `base_url`.         |
| `000016`        | 401    | MD5 inválido               | O `payload_md5` foi calculado sobre os mesmos bytes enviados no body.                                  |
| `000020`        | 401    | Formato de timestamp       | O `timestamp` está no formato `AAAA-MM-DDTHH:MM:SS`, sem fuso e sem milissegundos. Só `manager-api` e `consultant-api` devolvem este código: no `assignor-api`, timestamp fora do formato volta como erro `500`. |

Erros de permissão, de rota e dos serviços estão em [Erros da API](/documentation/iaas/introducao/erros).

## Requisições com query string {#query-string}

Endpoints paginados ou com filtros recebem parâmetros na _query string_ — por exemplo `?page=0&limit=20`. Nesses casos, a _query string_ **faz parte da URI assinada**.

O campo `uri` deve ser igual ao caminho enviado na requisição: mesmos parâmetros, na mesma ordem e com a mesma codificação. Se a URI assinada e a URI enviada não forem iguais, a requisição é recusada com status `401`:

```json title="Resposta"
{
  "title": "Invalid endpoint",
  "description": "Invalid endpoint.",
  "translation": "O endpoint é invalido",
  "code": "MIT000015"
}
```

### Exemplo

```python title="GET com paginação"
# base_url do passo 4. Este endpoint é da gestora (manager-api).
path = "/quota/fund_class/{fund_class_key}/investor_positions"
query_string = "page=0&limit=20"

# a query string faz parte da uri assinada
uri = f"{path}?{query_string}"

method = "GET"
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S")

dict_to_sign = {"timestamp": timestamp, "method": method, "uri": uri}

encoded_header_token = jwt.encode(
    claims=dict_to_sign,
    key=client_private_key,
    algorithm=algorithm,
    headers={"alg": algorithm, "typ": "JWT"},
)

headers = {"API-CLIENT-KEY": api_key, "AUTHORIZATION": encoded_header_token}

resp = requests.get(url=f"{base_url}{uri}", headers=headers)
print(resp.json())
```

### Recomendações

- Monte a _query string_ e envie a URL completa. Evite usar o argumento `params` do `requests` junto com uma URI já assinada: o cliente HTTP pode reordenar ou recodificar os parâmetros e invalidar a assinatura.
- Requisições `GET` normalmente não têm corpo. Nesse caso, não envie _body_ nem o campo `payload_md5`.
- Valores com caracteres especiais (pontuação de documentos, datas, espaços) devem ser assinados já no formato final em que serão enviados na URL.

---

# Troca de Chaves

URL: /documentation/iaas/introducao/troca_de_chaves

Esta página é para **cedentes, originadores, investidores e distribuidores**: você gera o par de chaves e envia a chave pública ao time de integração da QI Tech.

:::info Gestoras e consultorias

Gestoras e consultorias cadastram a chave pública e recebem a chave de API pelo próprio portal, sem enviar chaves por e-mail. Siga [Integração pelo portal](/documentation/iaas/introducao/integracao_via_portal).

:::

## 1. Requisição assinada

Todas as requisições às APIs da QI DTVM usam **HTTPS**, com **TLS 1.2 ou 1.3**, e levam dois headers:

1. `API-CLIENT-KEY`: a chave de API que identifica a sua integração, enviada pelo time de integração.
2. `AUTHORIZATION`: um JWT assinado com a sua chave privada, montado como em [Teste de autenticação](/documentation/iaas/introducao/teste_de_autenticacao).

A QI DTVM usa chaves assimétricas: a **chave privada** assina as requisições e a **chave pública** permite à QI validar a assinatura. Você gera o par e entrega à QI só a chave pública.

:::caution Atenção

A chave privada é de uso exclusivo da sua empresa e deve ser armazenada com segurança. A QI DTVM nunca vai pedir que você a compartilhe.

:::

## 2. Gerar o par

Em um terminal UNIX, gere a chave privada EC P-521:

```bash
ssh-keygen -t ecdsa -b 521 -m PEM -f private.key
```

A partir dela, gere a chave pública:

```bash
openssl ec -in private.key -pubout -outform PEM -out public.key.pub
```

A chave pública é o arquivo `public.key.pub`. Só esse arquivo é enviado à QI. Ao assinar as requisições com a `private.key`, use o algoritmo **`ES512`**, que corresponde à curva P-521.

## 3. Entregar a chave pública

Envie o arquivo `public.key.pub` para [integracao.dtvm@qitech.com.br](mailto:integracao.dtvm@qitech.com.br) e aguarde a configuração da integração. O time de integração responde com a chave de API do seu perfil. A chave de API só funciona no host desse perfil: veja [Ambientes (Hosts)](/documentation/iaas/introducao/inicio#ambientes-hosts).

## Vídeo explicativo