Pular para o conteúdo principal

Teste de autenticação

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.

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.

Cada perfil tem o próprio host. A chave de API só é reconhecida no host do perfil para o qual foi emitida: a chave de uma gestora enviada para o host de cedentes é recusada como Invalid API Key, mesmo com a assinatura correta.

Seu perfilSandboxProdução
Gestor (gestora de fundos)https://manager-api.sandbox.qidtvm.com.brhttps://manager-api.qidtvm.com.br
Consultor (consultoria)https://consultant-api.sandbox.qidtvm.com.brhttps://consultant-api.qidtvm.com.br
Cedentehttps://assignor-api.sandbox.qidtvm.com.brhttps://assignor-api.qidtvm.com.br
Originadorhttps://originator-api.sandbox.qidtvm.com.brInformado pelo time de integração
Investidorhttps://investor-api.sandbox.qidtvm.com.brhttps://investor-api.qidtvm.com.br
Distribuidorhttps://distributor-api.sandbox.qidtvm.com.brhttps://distributor-api.qidtvm.com.br

Use o perfil informado pela QI quando a integração foi criada — ou, se você criou a integração pelo portal, o perfil do portal em que ela foi cadastrada. A chave de API de sandbox não funciona em produção, e vice-versa.

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​

pip install "python-jose[cryptography]" requests
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​

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:

ValorGestoras e consultorias (portal)Cedentes, originadores, investidores e distribuidores
api_keyNo 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.
client_private_keyO 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.
algorithmO 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

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.

4. Defina os dados da requisição​

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"}
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.

5. Monte o conteúdo da assinatura​

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:

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​

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​

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.

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 codeStatusErroO que verificar
000007401Header ausenteOs dois headers, API-CLIENT-KEY e AUTHORIZATION, foram enviados.
000008401Chave de API inválidaA base_url é a do seu perfil e do ambiente certo, e a chave de API foi copiada sem espaços.
000009400Integração inativaA integração está ativa. Fale com integracao.dtvm@qitech.com.br.
000010401Chave pública pendenteA chave pública já foi cadastrada na integração.
000011401Autenticação inválidaO JWT foi assinado com a chave privada que corresponde à chave pública cadastrada.
000012401Assinatura incompletaO JWT tem os campos timestamp, method e uri.
000013401Método inválidoO method assinado é o mesmo da requisição, em maiúsculas.
000014401Assinatura expiradaO timestamp está em UTC, foi gerado na hora da requisição e o relógio do servidor está sincronizado.
000015401Endpoint inválidoO uri assinado é idêntico ao caminho enviado, incluindo a query string e sem a base_url.
000016401MD5 inválidoO payload_md5 foi calculado sobre os mesmos bytes enviados no body.
000020401Formato de timestampO 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.

Requisições com 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:

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

Exemplo​

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.