LiqiDevelopers
Avançado 25 min

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

A API TIDC v1 requer autenticação RSA-SHA256. Você precisará gerar um par de chaves e registrar a chave pública com a Liqi antes de começar.

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.

Bash (OpenSSL)
# 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
Node.js
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

Nunca compartilhe sua chave privada. Armazene-a de forma segura (ex: variável de ambiente ou cofre de segredos como AWS Secrets Manager).

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
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"
  }'
Resposta
{
  "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:

Formato
{timestamp}.{method}.{path}.{body}
Node.js
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_abc123def456
Python
import 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
cURL
# 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
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"
      }
    ]
  }'
Resposta
{
  "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"
}
CampoDescrição
trancheTickerTicker da série para alocar os tokens
investorDocumentCPF ou CNPJ do investidor (somente números)
amountValor em BRL a ser alocado
externalReferenceReferê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
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"
Resposta
{
  "id": "cb_abc123def456",
  "status": "SUBMITTED",
  "submittedAt": "2026-02-21T10:05:00Z"
}
StatusDescrição
DRAFTLote criado, pode ser editado
SUBMITTEDLote enviado para processamento
PROCESSINGLote está sendo processado
APPROVEDTodas as alocações foram aprovadas
PARTIALLY_APPROVEDAlgumas alocações falharam
REJECTEDLote inteiro rejeitado

6Acompanhar status

Você pode acompanhar o status do lote via polling ou configurando webhooks para receber notificações automáticas.

Polling
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"
Resposta (processado)
{
  "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

Recomendamos usar webhooks em vez de polling. Configure o evento 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
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"
    }
  }'
Resposta
{
  "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:

TIDC v1 — Fluxo de Cessão + Baixa
ClientLiqi APIBlockchainPOST /credit-batches201 { batchId, status: "DRAFT" }POST /credit-batches/:id/submit200 { status: "SUBMITTED" }Mint tokens para investidoresTx confirmadaWebhook: credit_batch.approvedPOST /settlement-batches201 { settlementId, "PENDING" }Webhook: settlement.processed

Tratamento de erros

A API retorna erros estruturados com códigos HTTP padrão. Aqui estão os cenários mais comuns:

HTTPCódigoDescrição
400INVALID_BODYCorpo da requisição inválido ou campos faltando
401INVALID_SIGNATUREAssinatura RSA inválida ou expirada
401KEY_NOT_FOUNDChave pública não encontrada (X-Key-Id)
404BATCH_NOT_FOUNDLote não encontrado
409BATCH_ALREADY_SUBMITTEDLote já foi submetido
409DUPLICATE_INVESTORInvestidor duplicado no mesmo lote
422INSUFFICIENT_TOKENSTokens insuficientes na série
422INVESTOR_NOT_VERIFIEDInvestidor não passou pelo KYC
429RATE_LIMITEDMuitas requisições (aguarde e tente novamente)
Resposta de erro
{
  "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-Key para evitar lotes duplicados em caso de retry.

Próximos passos