Integração TIDC v1
Neste guia você vai aprender a integrar com a API TIDC v1 da Liqi para criar lotes de crédito, submetê-los e acompanhar o fluxo de liquidação. A autenticação usa RSA-SHA256 para assinatura digital de todas as requisições.
Visão geral
A API TIDC v1 permite que parceiros criem e submetam lotes de crédito (credit batches) e lotes de liquidação (settlement batches) de forma programática. Cada requisição deve ser assinada digitalmente com sua chave privada RSA.
Importante
1Gerar par de chaves RSA-SHA256
Gere um par de chaves RSA de 2048 bits. A chave privada ficará no seu servidor (nunca compartilhe). A chave pública será registrada na Liqi.
# Gerar chave privada RSA de 2048 bits openssl genrsa -out private_key.pem 2048 # Extrair chave pública openssl rsa -in private_key.pem -pubout -out public_key.pem # Verificar a chave openssl rsa -in private_key.pem -check
import crypto from "crypto";
import fs from "fs";
// Gerar par de chaves RSA
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
publicKeyEncoding: { type: "spki", format: "pem" },
privateKeyEncoding: { type: "pkcs8", format: "pem" },
});
fs.writeFileSync("private_key.pem", privateKey);
fs.writeFileSync("public_key.pem", publicKey);
console.log("Chaves geradas com sucesso!");Atenção
2Registrar chave pública na Liqi
Envie sua chave pública PEM para a Liqi através da API ou do painel de desenvolvedor. A Liqi usará essa chave para verificar suas assinaturas.
curl -X POST "https://api.liqi.com.br/api/tidc/v1/keys" \
-H "X-API-Key: lq_dev_xxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqh...\n-----END PUBLIC KEY-----",
"label": "Produção - Servidor Principal"
}'{
"id": "key_abc123def456",
"label": "Produção - Servidor Principal",
"fingerprint": "SHA256:xY7k2mN9pQ3r...",
"status": "active",
"createdAt": "2026-02-21T10:00:00Z"
}3Assinar requisições
Cada requisição deve incluir um header X-Signature contendo a assinatura RSA-SHA256 do corpo da requisição, codificada em Base64.
O payload assinado e composto por:
{timestamp}.{method}.{path}.{body}import crypto from "crypto";
import fs from "fs";
const privateKey = fs.readFileSync("private_key.pem", "utf8");
function signRequest(method: string, path: string, body: string): string {
const timestamp = Math.floor(Date.now() / 1000).toString();
const payload = `${timestamp}.${method}.${path}.${body}`;
const sign = crypto.createSign("RSA-SHA256");
sign.update(payload);
const signature = sign.sign(privateKey, "base64");
return signature;
}
// Exemplo de uso
const body = JSON.stringify({
trancheTicker: "ROB1SR06",
items: [
{ investorDocument: "12345678900", amount: "1000.00" }
]
});
const signature = signRequest("POST", "/api/tidc/v1/credit-batches", body);
const timestamp = Math.floor(Date.now() / 1000).toString();
// Headers obrigatórios:
// X-Signature: {signature}
// X-Timestamp: {timestamp}
// X-Key-Id: key_abc123def456import time
import json
import base64
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
with open("private_key.pem", "rb") as f:
private_key = serialization.load_pem_private_key(f.read(), password=None)
def sign_request(method: str, path: str, body: str) -> str:
timestamp = str(int(time.time()))
payload = f"{timestamp}.{method}.{path}.{body}"
signature = private_key.sign(
payload.encode(),
padding.PKCS1v15(),
hashes.SHA256()
)
return base64.b64encode(signature).decode()
# Exemplo de uso
body = json.dumps({
"trancheTicker": "ROB1SR06",
"items": [
{"investorDocument": "12345678900", "amount": "1000.00"}
]
})
signature = sign_request("POST", "/api/tidc/v1/credit-batches", body)
timestamp = str(int(time.time()))
# Headers obrigatórios:
# X-Signature: {signature}
# X-Timestamp: {timestamp}
# X-Key-Id: key_abc123def456# Gerar assinatura com OpenSSL
TIMESTAMP=$(date +%s)
BODY='{"trancheTicker":"ROB1SR06","items":[{"investorDocument":"12345678900","amount":"1000.00"}]}'
PAYLOAD="$TIMESTAMP.POST./api/tidc/v1/credit-batches.$BODY"
SIGNATURE=$(echo -n "$PAYLOAD" | \
openssl dgst -sha256 -sign private_key.pem | \
base64 -w 0)
curl -X POST "https://api.liqi.com.br/api/tidc/v1/credit-batches" \
-H "Content-Type: application/json" \
-H "X-Signature: $SIGNATURE" \
-H "X-Timestamp: $TIMESTAMP" \
-H "X-Key-Id: key_abc123def456" \
-d "$BODY"4Criar lote de crédito
Um lote de crédito (credit batch) agrupa múltiplas alocações de tokens para investidores. O lote é criado no status DRAFT e pode ser editado antes da submissão.
curl -X POST "https://api.liqi.com.br/api/tidc/v1/credit-batches" \
-H "Content-Type: application/json" \
-H "X-Signature: {signature}" \
-H "X-Timestamp: {timestamp}" \
-H "X-Key-Id: key_abc123def456" \
-d '{
"trancheTicker": "ROB1SR06",
"items": [
{
"investorDocument": "12345678900",
"amount": "1000.00",
"externalReference": "INV-001"
},
{
"investorDocument": "98765432100",
"amount": "2500.00",
"externalReference": "INV-002"
}
]
}'{
"id": "cb_abc123def456",
"trancheTicker": "ROB1SR06",
"status": "DRAFT",
"itemCount": 2,
"totalAmount": "3500.00",
"items": [
{
"id": "cbi_001",
"investorDocument": "12345678900",
"amount": "1000.00",
"tokenQuantity": "10",
"externalReference": "INV-001",
"status": "PENDING"
},
{
"id": "cbi_002",
"investorDocument": "98765432100",
"amount": "2500.00",
"tokenQuantity": "25",
"externalReference": "INV-002",
"status": "PENDING"
}
],
"createdAt": "2026-02-21T10:00:00Z"
}| Campo | Descrição |
|---|---|
| trancheTicker | Ticker da série para alocar os tokens |
| investorDocument | CPF ou CNPJ do investidor (somente números) |
| amount | Valor em BRL a ser alocado |
| externalReference | Referência interna do seu sistema (opcional) |
5Submeter o lote
Após revisar o lote, submeta-o para processamento. Uma vez submetido, o lote não pode mais ser editado.
curl -X POST "https://api.liqi.com.br/api/tidc/v1/credit-batches/cb_abc123def456/submit" \
-H "X-Signature: {signature}" \
-H "X-Timestamp: {timestamp}" \
-H "X-Key-Id: key_abc123def456"{
"id": "cb_abc123def456",
"status": "SUBMITTED",
"submittedAt": "2026-02-21T10:05:00Z"
}| Status | Descrição |
|---|---|
| DRAFT | Lote criado, pode ser editado |
| SUBMITTED | Lote enviado para processamento |
| PROCESSING | Lote está sendo processado |
| APPROVED | Todas as alocações foram aprovadas |
| PARTIALLY_APPROVED | Algumas alocações falharam |
| REJECTED | Lote inteiro rejeitado |
6Acompanhar status
Você pode acompanhar o status do lote via polling ou configurando webhooks para receber notificações automáticas.
curl -s "https://api.liqi.com.br/api/tidc/v1/credit-batches/cb_abc123def456" \
-H "X-Signature: {signature}" \
-H "X-Timestamp: {timestamp}" \
-H "X-Key-Id: key_abc123def456"{
"id": "cb_abc123def456",
"trancheTicker": "ROB1SR06",
"status": "APPROVED",
"itemCount": 2,
"totalAmount": "3500.00",
"items": [
{
"id": "cbi_001",
"investorDocument": "12345678900",
"amount": "1000.00",
"tokenQuantity": "10",
"status": "COMPLETED",
"blockchainTxHash": "0xabc123..."
},
{
"id": "cbi_002",
"investorDocument": "98765432100",
"amount": "2500.00",
"tokenQuantity": "25",
"status": "COMPLETED",
"blockchainTxHash": "0xdef456..."
}
],
"submittedAt": "2026-02-21T10:05:00Z",
"completedAt": "2026-02-21T10:07:00Z"
}Dica
credit_batch.approved para ser notificado automaticamente.7Fluxo de liquidação
A liquidação (settlement) é o processo de transferir os valores financeiros correspondentes aos tokens alocados. Crie um lote de liquidação para processar os pagamentos.
curl -X POST "https://api.liqi.com.br/api/tidc/v1/settlement-batches" \
-H "Content-Type: application/json" \
-H "X-Signature: {signature}" \
-H "X-Timestamp: {timestamp}" \
-H "X-Key-Id: key_abc123def456" \
-d '{
"creditBatchId": "cb_abc123def456",
"settlementDate": "2026-02-22",
"bankAccount": {
"bankCode": "341",
"agency": "1234",
"accountNumber": "56789-0",
"document": "12345678000199"
}
}'{
"id": "sb_xyz789",
"creditBatchId": "cb_abc123def456",
"status": "PENDING",
"settlementDate": "2026-02-22",
"totalAmount": "3500.00",
"createdAt": "2026-02-21T10:10:00Z"
}Diagrama de sequência
O fluxo completo de integração segue estas etapas:
Tratamento de erros
A API retorna erros estruturados com códigos HTTP padrão. Aqui estão os cenários mais comuns:
| HTTP | Código | Descrição |
|---|---|---|
| 400 | INVALID_BODY | Corpo da requisição inválido ou campos faltando |
| 401 | INVALID_SIGNATURE | Assinatura RSA inválida ou expirada |
| 401 | KEY_NOT_FOUND | Chave pública não encontrada (X-Key-Id) |
| 404 | BATCH_NOT_FOUND | Lote não encontrado |
| 409 | BATCH_ALREADY_SUBMITTED | Lote já foi submetido |
| 409 | DUPLICATE_INVESTOR | Investidor duplicado no mesmo lote |
| 422 | INSUFFICIENT_TOKENS | Tokens insuficientes na série |
| 422 | INVESTOR_NOT_VERIFIED | Investidor não passou pelo KYC |
| 429 | RATE_LIMITED | Muitas requisições (aguarde e tente novamente) |
{
"statusCode": 422,
"error": "INSUFFICIENT_TOKENS",
"message": "Tranche ROB1SR06 has only 150 tokens available, but 200 were requested.",
"details": {
"available": 150,
"requested": 200
}
}- Valide o timestamp — Requisições com timestamp com mais de 5 minutos de diferença serão rejeitadas com 401.
- Implemente retry com backoff — Para erros 429 e 5xx, use exponential backoff (1s, 2s, 4s, 8s).
- Use idempotency keys — Inclua o header
X-Idempotency-Keypara evitar lotes duplicados em caso de retry.