# Assinex API: guia para integração com IA Você vai integrar a API da Assinex ao meu sistema. A Assinex é um fornecedor de assinaturas e serviços digitais para revenda: eu tenho saldo numa carteira (colocado por Pix no painel), compro produtos do catálogo pela API e recebo o acesso (link, código ou conta) para entregar ao meu cliente. Use somente o que está descrito abaixo. Não invente endpoints, campos, parâmetros nem status. Se faltar alguma informação sobre o meu sistema, pergunte antes de escrever o código. ## Antes de começar, pergunte para mim 1. Qual linguagem e framework o meu sistema usa, e onde o código de servidor roda. 2. Onde ficam os meus pedidos (tabela, loja, planilha) e qual campo é o número do pedido. 3. Como o acesso deve chegar ao meu cliente (e-mail, WhatsApp, área do cliente, outro). 4. Se eu quero receber a entrega por webhook (recomendado) ou só consultando o pedido. ## Regras obrigatórias - O token da API fica só no servidor, em variável de ambiente `ASSINEX_TOKEN`. Nunca no navegador, no app do celular, em código de frontend, em log ou no Git. Em ferramentas como Lovable, Bolt ou v0, a chamada precisa ficar numa função de servidor (por exemplo, uma Edge Function) com o token guardado em Secrets. - Todo pedido leva `external_id` = o número do pedido no meu sistema. É isso que impede cobrança duplicada. - Em timeout, erro de rede ou HTTP 5xx ao criar pedido, repita a MESMA requisição com o MESMO `external_id`. Nunca gere um `external_id` novo para a mesma venda. - Use timeout de pelo menos 30 segundos no `POST /orders`: a resposta pode esperar a entrega por alguns segundos. - Trate os erros pelo campo `error.code`, não pela mensagem. - No webhook, verifique a assinatura com o corpo BRUTO antes de confiar no conteúdo, responda 2xx rápido e ignore eventos repetidos pelo `id` do evento. - Não guarde nem registre em log o conteúdo da entrega além do necessário para entregar ao cliente. ## Conceitos - Base URL: `https://assinex.com/api/v1` - Autenticação: cabeçalho `Authorization: Bearer `. O token começa com `ax_live_` e é criado no painel em Integração > Criar token. - Formato: JSON UTF-8. Valores em reais (BRL) como número com duas casas (ex.: `11.5`). Datas em ISO 8601 UTC. - Limite: 120 requisições por minuto por token. Cabeçalhos `X-RateLimit-Limit` e `X-RateLimit-Remaining`. Acima disso, HTTP 429 com `Retry-After` (segundos). - IDs públicos: produto `prd_...`, pedido `ord_...`, evento `evt_...`, conta `acc_...`. ## Erros Formato de todo erro: ```json { "error": { "code": "saldo_insuficiente", "message": "Saldo insuficiente na carteira para esta compra." } } ``` | HTTP | code | Significado | O que fazer | |---|---|---|---| | 401 | token_invalido | Token ausente, errado ou revogado | Não repetir; corrigir o token | | 403 | conta_suspensa | Conta suspensa | Não repetir; avisar o responsável | | 403 | api_desativada | Acesso pela API desligado para esta conta | Não repetir; avisar o responsável | | 403 | limite_diario_excedido | Pedido passaria do limite de gasto diário da conta; nada foi cobrado | Não repetir hoje; repetir no dia seguinte (horário de Brasília) com o mesmo `external_id` | | 503 | vendas_pausadas | Vendas pausadas pela Assinex; nada foi cobrado | Temporário; repetir mais tarde com o mesmo `external_id` | | 429 | limite_de_requisicoes | Limite por minuto | Esperar `Retry-After` e repetir | | 402 | saldo_insuficiente | Saldo não cobre o pedido; nada foi cobrado | Avisar para colocar saldo; repetir depois com o mesmo `external_id` | | 409 | produto_indisponivel | Sem estoque ou pausado agora | Temporário; tentar mais tarde | | 404 | produto_nao_encontrado | `product_id` não existe no catálogo | Atualizar o catálogo | | 409 | external_id_em_uso | `external_id` já usado com outro produto ou quantidade | Erro de lógica no meu sistema; não repetir | | 400 | quantidade_invalida | `quantity` fora de 1 até `max_per_order` | Corrigir | | 400 | external_id_obrigatorio, external_id_invalido | `external_id` ausente ou fora do formato | Corrigir | | 400 | product_id_obrigatorio, json_invalido | Corpo incompleto ou JSON inválido | Corrigir | | 404 | pedido_nao_encontrado | Pedido não existe nesta conta | Conferir o id | | 404 | rota_nao_encontrada | Endpoint inexistente | Usar só os endpoints abaixo | | 500 | erro_interno | Falha da Assinex | Repetir com o mesmo `external_id` | `external_id`: de 1 a 100 caracteres, apenas letras, números e `. _ : -`. ## Endpoints ### GET /me Dados da conta. ```json { "id": "acc_Z3k9QmX2pLr8Tw", "name": "Loja Exemplo", "status": "ativa", "created_at": "2026-09-30T12:00:00.000Z" } ``` ### GET /balance Saldo da carteira. ```json { "balance": 138.5, "currency": "BRL" } ``` ### GET /products Catálogo com o MEU preço. Compre só itens com `available: true`. Use o `id` no pedido. ```json { "data": [ { "id": "prd_Kd8Xq2mLp0Za1w", "name": "Assinatura digital 18 meses", "description": null, "price": 11.5, "currency": "BRL", "available": true, "max_per_order": 50 } ] } ``` ### POST /orders Cria um pedido pago com o saldo. Corpo: | Campo | Tipo | Obrigatório | Descrição | |---|---|---|---| | product_id | texto | sim | `id` de `GET /products` | | quantity | inteiro | não (padrão 1) | 1 até `max_per_order` | | external_id | texto | sim | Número do pedido no meu sistema, único por venda | ```bash curl -X POST https://assinex.com/api/v1/orders \ -H "Authorization: Bearer $ASSINEX_TOKEN" \ -H "Content-Type: application/json" \ -d '{"product_id": "prd_Kd8Xq2mLp0Za1w", "quantity": 1, "external_id": "loja-1042"}' ``` Respostas: - `201 Created`: pedido novo, `"idempotent_replay": false`. - `200 OK`: mesmo `external_id` enviado de novo; devolve o pedido já existente, `"idempotent_replay": true`, sem nova cobrança. ```json { "order": { "id": "ord_7Qx2kP9mLsT1aB", "external_id": "loja-1042", "product_id": "prd_Kd8Xq2mLp0Za1w", "product_name": "Assinatura digital 18 meses", "quantity": 1, "unit_price": 11.5, "total": 11.5, "currency": "BRL", "status": "entregue", "delivery": { "items": [{ "link": "https://exemplo.com/ativar/x8Kq" }], "text": "Link: https://exemplo.com/ativar/x8Kq" }, "error": null, "refunded": false, "created_at": "2026-09-30T12:00:00.000Z", "delivered_at": "2026-09-30T12:00:02.000Z", "updated_at": "2026-09-30T12:00:02.000Z" }, "idempotent_replay": false } ``` ### GET /orders/{id} Consulta um pedido pelo `id` (`ord_...`). Resposta: `{ "order": { ... } }` no mesmo formato acima. ### GET /orders Lista pedidos. Parâmetros opcionais: `external_id` (busca pelo meu id), `status` (`processando`, `entregue`, `falhou`), `limit` (até 100, padrão 50), `before` (valor de `next` da página anterior). ```json { "data": [ { "id": "ord_7Qx2kP9mLsT1aB", "status": "entregue" } ], "next": null } ``` ## Situação do pedido | status | Significado | Ação | |---|---|---| | processando | Pago com o saldo; acesso sendo liberado | Esperar o webhook ou consultar `GET /orders/{id}` a cada 10 a 30 segundos | | entregue | Acesso em `delivery` | Entregar ao cliente | | falhou | Compra não concluída. `refunded: true` = valor devolvido ao saldo. Detalhe em `error.code` e `error.message` | Avisar o cliente ou criar outro pedido com OUTRO `external_id` | Um pedido `entregue` nunca muda de estado. ## Entrega `delivery.items` é uma lista, um item por unidade. O campo `type` diz a forma: - `account`: conta pronta. Use `login`, `password` e, quando existir, `totp` (código ou link do 2FA). Não junte esses campos num texto só. - `link`: link para reivindicar. Use `link`. - `code`: código de resgate. Use `code`. - `text`: outro formato. Use `content` (texto livre) e, quando vierem, `link` ou `code`. `instructions` é o como usar. Mostre esse texto ao cliente final junto com o acesso. `delivery.text` junta os campos com rótulo e termina com a linha `Como usar:`, pronta para um .txt ou uma mensagem. Quando `type` é `account`, use `login` e `password`; a resposta não traz o texto cru da conta. ```json { "type": "account", "login": "ada@outlook.com", "password": "segredo123", "instructions": "Entre com o e-mail e a senha entregues. No primeiro acesso, troque a senha." } ``` ## Webhooks Configurados no painel em Integração > Webhook (URL https pública). O segredo `whsec_...` aparece uma única vez ao salvar; guarde em `ASSINEX_WEBHOOK_SECRET`. Eventos: `pedido.entregue`, `pedido.falhou`, `teste`. Requisição que a Assinex envia (POST): ``` Content-Type: application/json User-Agent: Assinex-Webhooks/1.0 X-Assinex-Event: pedido.entregue X-Assinex-Delivery: evt_Hn2Lq8rVx1Ck9d X-Assinex-Timestamp: 1790770400 X-Assinex-Signature: sha256= ``` ```json { "id": "evt_Hn2Lq8rVx1Ck9d", "type": "pedido.entregue", "created_at": "2026-09-30T12:00:02.000Z", "data": { "order": { "id": "ord_7Qx2kP9mLsT1aB", "external_id": "loja-1042", "status": "entregue" } } } ``` `data.order` tem o mesmo formato do pedido em `POST /orders`. O evento `teste` traz `data.message` em vez de `data.order`. Assinatura: HMAC-SHA256, com o segredo do webhook, do texto `{X-Assinex-Timestamp}.{corpo bruto}`, em hexadecimal, depois de `sha256=`. Compare em tempo constante e recuse timestamps com mais de 5 minutos de diferença. ```js import crypto from "node:crypto"; export function assinaturaValida(corpoBruto, cabecalhos, segredo) { const ts = cabecalhos["x-assinex-timestamp"]; const recebido = String(cabecalhos["x-assinex-signature"] || "").replace("sha256=", ""); if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; const esperado = crypto.createHmac("sha256", segredo).update(`${ts}.${corpoBruto}`).digest("hex"); return recebido.length === esperado.length && crypto.timingSafeEqual(Buffer.from(recebido), Buffer.from(esperado)); } ``` Responda 2xx em até 10 segundos (processe depois, em fila, se precisar). Sem 2xx, a Assinex tenta de novo em 1 min, 5 min, 15 min, 1 h, 3 h, 6 h e 12 h (8 tentativas no total). Use o `id` do evento para não processar o mesmo evento duas vezes. ## Fluxo recomendado 1. Ao sincronizar o catálogo: `GET /products`, guardar `id`, `name`, `price` e `available`. 2. Quando o cliente pagar na minha loja: `POST /orders` com `external_id` = número do meu pedido. 3. Se a resposta vier `entregue`: enviar `delivery.text` ao cliente. 4. Se vier `processando`: esperar `pedido.entregue` no webhook (ou consultar `GET /orders/{id}`). 5. Se vier `falhou` ou `pedido.falhou`: avisar e decidir se cria outro pedido com outro `external_id`. 6. Em erro de rede ou 5xx: repetir o passo 2 com o mesmo `external_id`. ## O que entregar para mim - O código da integração no meu stack, com o token lido de variável de ambiente. - O endpoint de webhook com verificação de assinatura. - O tratamento de todos os `error.code` acima. - Um teste simples: `GET /me` para validar o token e um pedido de exemplo com um `external_id` de teste. - Uma lista curta do que eu preciso configurar (variáveis de ambiente, URL do webhook no painel). Documentação completa: https://assinex.com/docs