# Plasma Checkout - Referência de Integração para Agentes de IA (llms-api.txt) > Contexto único e auto-contido para gerar uma integração correta na primeira > tentativa. Todos os exemplos abaixo foram extraídos de execuções reais contra > a API, não escritos à mão. Request e response correspondem ao que o servidor > de fato envia e recebe. > > Documentação navegável: https://plasmacheckout.com/docs > Visão comercial do produto: https://plasmacheckout.com/llms-full.txt --- ## 0. A REGRA QUE MAIS CAUSA ERRO: dinheiro é centavo inteiro Todo valor monetário da API é **inteiro, em centavos**, e o nome do campo termina em `Cents`. ``` priceCents: 7990 -> R$ 79,90 CORRETO priceCents: 79.90 -> 422 decimal é recusado pelo schema price: 7990 -> 422 o campo `price` não existe mais ``` Converta na borda da sua aplicação: `Math.round(reais * 100)`. Para exibir, divida por 100 apenas na formatação, nunca para armazenar ou calcular. Campos afetados: `priceCents`, `compareAtPriceCents`, `subtotalCents`, `shippingCents`, `discountCents`, `taxCents`, `totalCents`, `totalSpentCents`, `minSpentCents`. --- ## 0.1 PROTOCOLO DE VERIFICAÇÃO (execute, não presuma) Se você é um agente de IA gerando esta integração, **rode as duas checagens abaixo**. Elas custam segundos e evitam entregar código que só falha em runtime. ### Antes de escrever qualquer código Confirme que as chaves funcionam e que você entendeu a unidade monetária. Use as chaves `pk_test_` / `sk_test_`, nunca as `live` para isto. ```bash # 1. As chaves são válidas? Espera-se 200. curl -s -o /dev/null -w "%{http_code}\n" \ "https://api.plasmacheckout.com/api/sdk/products?limit=1" \ -H "X-PLASMA-Public-Key: $PLASMA_PUBLIC_KEY" \ -H "X-PLASMA-Secret-Key: $PLASMA_SECRET_KEY" # 2. O endpoint de checkout é create-link, não session. Espera-se 404 aqui. curl -s -o /dev/null -w "%{http_code}\n" -X POST \ "https://api.plasmacheckout.com/api/sdk/checkout/session" \ -H "X-PLASMA-Public-Key: $PLASMA_PUBLIC_KEY" \ -H "X-PLASMA-Secret-Key: $PLASMA_SECRET_KEY" # 3. Dinheiro é centavo inteiro. Espera-se 422, provando que decimal é recusado. curl -s -o /dev/null -w "%{http_code}\n" -X POST \ "https://api.plasmacheckout.com/api/sdk/products" \ -H "X-PLASMA-Public-Key: $PLASMA_PUBLIC_KEY" \ -H "X-PLASMA-Secret-Key: $PLASMA_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"title":"Sonda","priceCents":79.90}' ``` Se o passo 1 não devolver 200, pare: o problema é credencial, e nenhum código vai funcionar. Se o passo 2 devolver algo diferente de 404 ou o passo 3 algo diferente de 422, esta referência está desatualizada em relação à API: confie na API e avise o desenvolvedor. ### Depois de implementar Exercite o caminho que você escreveu, ponta a ponta, com ambiente de teste: ```bash # Cria produto, cria checkout a partir dele e limpa o que criou. # # `inventory` é obrigatório aqui na prática: sem ele o produto nasce com 0 e # `trackInventory: true`, e o checkout recusa com PRODUCT_OUT_OF_STOCK. PID=$(curl -s -X POST "https://api.plasmacheckout.com/api/sdk/products" \ -H "X-PLASMA-Public-Key: $PLASMA_PUBLIC_KEY" \ -H "X-PLASMA-Secret-Key: $PLASMA_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"title":"Sonda de integracao","priceCents":1000,"sku":"SONDA-1","inventory":1}' \ | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4) curl -s -X POST "https://api.plasmacheckout.com/api/sdk/checkout/create-link" \ -H "X-PLASMA-Public-Key: $PLASMA_PUBLIC_KEY" \ -H "X-PLASMA-Secret-Key: $PLASMA_SECRET_KEY" \ -H "Content-Type: application/json" \ -d "{\"productIds\":[\"$PID\"]}" curl -s -X DELETE "https://api.plasmacheckout.com/api/sdk/products/$PID" \ -H "X-PLASMA-Public-Key: $PLASMA_PUBLIC_KEY" \ -H "X-PLASMA-Secret-Key: $PLASMA_SECRET_KEY" ``` Confira na resposta do checkout que `data.checkoutUrl` existe e que `data.totals.totalCents` vale `1000`. Se o total vier `100000`, você converteu reais para centavos duas vezes. Se vier `10`, dividiu quando não devia. Só considere a integração pronta depois que essa sequência passar. Não apague o passo de limpeza: produto de sonda esquecido no catálogo vira produto à venda. --- ## 1. Autenticação Base URL: `https://api.plasmacheckout.com` Duas chaves obrigatórias, em **todas** as requisições autenticadas: ``` X-PLASMA-Public-Key: pk_test_xxxxxxxxxxxx (ou pk_live_) X-PLASMA-Secret-Key: sk_test_xxxxxxxxxxxx (ou sk_live_) ``` As chaves são geradas no painel em **Loja → API Keys**. A Secret Key aparece uma única vez. Leia ambas de variável de ambiente; nunca escreva no código nem versione `sk_live_` em repositório. As duas precisam ser do mesmo ambiente. Misturar devolve 401: ``` GET /api/sdk/products (sem headers) 401 {"error":"Missing API keys. Provide X-PLASMA-Public-Key and X-PLASMA-Secret-Key headers.","code":"UNAUTHORIZED"} GET /api/sdk/products (pk_live_ + sk_test_) 401 {"error":"Environment mismatch. Both keys must be test or both must be live.","code":"UNAUTHORIZED"} ``` Toda resposta traz `X-RateLimit-Limit`, `X-RateLimit-Remaining` e `X-RateLimit-Reset`. Em 429 vem também `Retry-After`, em segundos. O limite do SDK é 120 requisições por minuto por chave. --- ## 2. Endpoints | Método | Endpoint | Descrição | |---|---|---| | GET | `/api/sdk/products` | Lista produtos, com cursor | | POST | `/api/sdk/products` | Cria produto | | GET | `/api/sdk/products/:id` | Busca produto | | PATCH | `/api/sdk/products/:id` | Atualiza produto | | DELETE | `/api/sdk/products/:id` | Remove produto | | POST | `/api/sdk/checkout/create-link` | Cria link de checkout | | GET | `/api/sdk/checkout/:token` | Busca sessão (público, o token autentica) | | GET | `/api/sdk/orders` | Lista pedidos | | POST | `/api/sdk/orders` | Cria pedido | | GET | `/api/sdk/customers` | Lista clientes | | POST | `/api/sdk/webhooks/endpoints` | Registra endpoint de webhook | Não existe `/api/sdk/checkout/session`. O endpoint de criação é `/api/sdk/checkout/create-link`. --- ## 3. Criar produto ``` POST /api/sdk/products ``` Request: ```json { "title": "Camiseta Premium", "description": "Camiseta 100% algodao", "priceCents": 7990, "compareAtPriceCents": 9990, "sku": "CAM-001", "inventory": 50, "productType": "Vestuario", "vendor": "Minha Marca", "tags": ["algodao", "premium"], "images": ["https://exemplo.com/camiseta.jpg"] } ``` Response `201`: ```json { "data": { "id": "cmswltgex0001rv052rzifmoc", "title": "Camiseta Premium", "description": "Camiseta 100% algodao", "priceCents": 7990, "compareAtPriceCents": 9990, "images": ["https://exemplo.com/camiseta.jpg"], "imageUrl": "https://exemplo.com/camiseta.jpg", "status": "active", "sku": "CAM-001", "inventory": 50, "trackInventory": true, "productType": "Vestuario", "vendor": "Minha Marca", "tags": ["algodao", "premium"], "createdAt": "2026-08-17T02:17:59.384Z", "updatedAt": "2026-08-17T02:17:59.384Z" }, "message": "Product created successfully" } ``` Obrigatórios: `title` e `priceCents`. Regras: título até 255 caracteres, descrição até 5000, SKU único por loja até 100, no máximo 10 imagens, e `compareAtPriceCents` estritamente maior que `priceCents`. Curl equivalente: ```bash curl -X POST "https://api.plasmacheckout.com/api/sdk/products" \ -H "X-PLASMA-Public-Key: $PLASMA_PUBLIC_KEY" \ -H "X-PLASMA-Secret-Key: $PLASMA_SECRET_KEY" \ -H "Content-Type: application/json" \ -d '{"title":"Camiseta Premium","priceCents":7990,"sku":"CAM-001","inventory":50}' ``` --- ## 4. Listar produtos ``` GET /api/sdk/products?limit=1&status=active ``` Response `200`: ```json { "data": [ { "id": "cmswltgex0001rv052rzifmoc", "title": "Camiseta Premium", "priceCents": 7990, "compareAtPriceCents": 9990, "sku": "CAM-001", "status": "active", "inventory": 50, "trackInventory": true, "tags": ["algodao", "premium"], "createdAt": "2026-08-17T02:17:59.384Z", "updatedAt": "2026-08-17T02:17:59.384Z" } ], "pagination": { "hasMore": false, "nextCursor": null, "count": 1, "total": 1 }, "filters": { "status": "active", "search": null, "productType": null, "vendor": null } } ``` Query aceita `limit` (1 a 100), `cursor`, `status` (`active`/`draft`/`archived`), `search`, `productType`, `vendor`, `sku`, `sortBy` (`createdAt`/`updatedAt`/`title`/`priceCents`/`inventory`) e `sortOrder`. Paginação é por cursor: mande `cursor=` até `hasMore` virar `false`. --- ## 5. Criar link de checkout ``` POST /api/sdk/checkout/create-link ``` Duas formas mutuamente exclusivas. Mandar as duas devolve 400. ### 5a. Pelo catálogo (`productIds`), que é a forma preferível O preço vem do servidor, então o cliente não consegue forjar valor. Request: ```json { "productIds": ["cmswltgex0001rv052rzifmoc"], "successUrl": "https://meusite.com/obrigado", "cancelUrl": "https://meusite.com/carrinho", "customer": { "email": "cliente@email.com", "name": "Joao Silva" } } ``` Response `201`: ```json { "data": { "id": "cmswltiti0005rv05rpvt7an1", "checkoutUrl": "https://app.plasmacheckout.com/c/c08b6109-4c32-46ac-9a8a-bb72b7ec71de", "checkoutToken": "c08b6109-4c32-46ac-9a8a-bb72b7ec71de", "status": "pending", "expiresAt": "2026-08-18T02:18:02.507Z", "items": [ { "productId": "cmswltgex0001rv052rzifmoc", "title": "Camiseta Premium", "priceCents": 7990, "quantity": 1, "image": "https://exemplo.com/camiseta.jpg" } ], "totals": { "subtotalCents": 7990, "shippingCents": 0, "discountCents": 0, "totalCents": 7990 }, "currency": "BRL", "customer": { "email": "cliente@email.com", "name": "Joao Silva", "phone": null }, "urls": { "checkout": "https://app.plasmacheckout.com/c/c08b6109-4c32-46ac-9a8a-bb72b7ec71de", "success": "https://meusite.com/obrigado", "cancel": "https://meusite.com/carrinho" } }, "message": "Checkout link created successfully" } ``` ### 5b. Por itens avulsos (`items`) Request: ```json { "items": [{ "title": "Consultoria 1 hora", "priceCents": 15000, "quantity": 1 }], "shippingCents": 1500, "successUrl": "https://meusite.com/obrigado" } ``` Response `201` (trecho): ```json { "data": { "items": [ { "productId": null, "title": "Consultoria 1 hora", "priceCents": 15000, "quantity": 1, "image": null } ], "totals": { "subtotalCents": 15000, "shippingCents": 1500, "discountCents": 0, "totalCents": 16500 }, "currency": "BRL" } } ``` Campos opcionais: `customer` (`email`, `name`, `phone`), `metadata` (objeto até 4KB), `expiresIn` (minutos, 5 a 10080, padrão 1440), `couponCode`, `shippingCents` e `currency` (padrão `BRL`). Depois de criar, redirecione o comprador para `data.checkoutUrl`. Erros específicos: `PRODUCTS_NOT_FOUND` quando algum id não existe ou não é da sua loja, e `PRODUCT_OUT_OF_STOCK` quando o produto controla estoque e está zerado. --- ## 6. Webhooks Registre o endpoint em `POST /api/sdk/webhooks/endpoints`. A cada evento, o Plasma faz POST no seu endpoint com estes headers: ``` X-PLASMA-Event: product.created X-PLASMA-Event-Id: evt_1786933079401_c6674940c02af7ba X-PLASMA-Signature: sha256=22bb6ba28b6d6d4f035ae94823b4c40caf17c900431c5558d474de7fea143b23 X-PLASMA-Timestamp: 2026-08-17T02:18:01.499Z X-PLASMA-Delivery-Id: cmswlti1j0003rv053lksvg93 Content-Type: application/json ``` Body: ```json { "id": "evt_1786933079401_c6674940c02af7ba", "event": "product.created", "timestamp": "2026-08-17T02:18:01.499Z", "storeId": "cmq490bw60008g075bkvdi9rl", "storeName": "Plasma Teste", "data": { "productId": "cmswltgex0001rv052rzifmoc", "title": "Camiseta Premium", "priceCents": 7990, "status": "active", "sku": "CAM-001", "inventory": 50, "productType": "Vestuario", "vendor": "Minha Marca", "createdAt": "2026-08-17T02:17:59.384Z" } } ``` A assinatura é HMAC SHA-256 do corpo **serializado exatamente como recebido**, usando o secret do endpoint. Valide sempre antes de confiar no payload: ```typescript import crypto from 'crypto'; function verificarAssinatura(corpoBruto: string, assinatura: string, secret: string): boolean { const esperada = 'sha256=' + crypto.createHmac('sha256', secret).update(corpoBruto).digest('hex'); if (assinatura.length !== esperada.length) return false; return crypto.timingSafeEqual(Buffer.from(assinatura), Buffer.from(esperada)); } ``` Responda `200` rápido. O Plasma reentrega em caso de falha, com backoff de 1min, 5min, 15min, 1h e 4h, até 5 tentativas. Use `X-PLASMA-Event-Id` para idempotência: o mesmo evento pode chegar mais de uma vez. Eventos: `order.created`, `order.paid`, `order.refunded`, `order.fulfilled`, `order.cancelled`, `product.created`, `product.updated`, `product.deleted`, `checkout.started`, `checkout.completed`, `checkout.abandoned`, `customer.created`, `customer.updated`. --- ## 7. Erros | HTTP | Quando | Exemplo de corpo | |---|---|---| | 401 | chave ausente, inválida, ou ambientes misturados | `{"error":"Environment mismatch...","code":"UNAUTHORIZED"}` | | 422 | payload não bate com o schema (tipo errado, campo obrigatório faltando) | `{"type":"validation","on":"body","property":"/priceCents","summary":"Property 'priceCents' should be one of: 'integer', 'integer'"}` | | 400 | regra de negócio violada (título longo demais, status inválido, compareAtPrice menor que price) | `{"error":"...","code":"VALIDATION_ERROR"}` | | 404 | recurso não existe ou não pertence à sua loja | `{"error":"Product not found","code":"NOT_FOUND"}` | | 409 | SKU duplicado na mesma loja | `{"error":"A product with this SKU already exists","code":"DUPLICATE_SKU"}` | | 429 | rate limit; respeite o header `Retry-After` | `{"error":"Rate limit exceeded...","code":"RATE_LIMITED"}` | A distinção entre 422 e 400 importa: **422 é formato**, e reenviar o mesmo corpo nunca vai funcionar. **400 é regra**, e o corpo precisa de ajuste de valor. Nenhum dos dois deve ser retentado às cegas. Recurso de outra loja devolve `404`, e não `403`, para não revelar existência. --- ## 8. SDK oficial em TypeScript ```bash npm install @plasmacheckout/sdk ``` ```typescript import { PlasmaSDK, PlasmaError } from '@plasmacheckout/sdk'; const plasma = new PlasmaSDK({ apiKey: process.env.PLASMA_PUBLIC_KEY!, // pk_ secretKey: process.env.PLASMA_SECRET_KEY!, // sk_ }); try { const { data: produto } = await plasma.products.create({ title: 'Camiseta Premium', priceCents: 7990, sku: 'CAM-001', inventory: 50, }); const { data: checkout } = await plasma.checkout.createLink({ productIds: [produto.id], successUrl: 'https://meusite.com/obrigado', cancelUrl: 'https://meusite.com/carrinho', }); console.log(checkout.checkoutUrl); } catch (erro) { if (erro instanceof PlasmaError) { console.error(`[${erro.code}] ${erro.message} (HTTP ${erro.status})`); } else { throw erro; } } ``` --- ## 9. Fluxo ponta a ponta 1. Gere as chaves no painel, em Loja → API Keys, e guarde em `.env`. 2. `POST /api/sdk/products` para cada item do catálogo. Guarde o `id`. 3. `POST /api/sdk/webhooks/endpoints` apontando para a sua URL pública, assinando `order.paid` no mínimo. 4. No "finalizar compra", `POST /api/sdk/checkout/create-link` com `productIds`, e redirecione para `data.checkoutUrl`. 5. O comprador paga na página do Plasma. 6. Seu endpoint recebe `order.paid`. **Valide a assinatura**, confira o `X-PLASMA-Event-Id` contra os já processados, e só então libere o pedido. 7. Nunca trate o retorno do navegador para `successUrl` como confirmação de pagamento. A fonte de verdade é o webhook. O passo 7 é o erro mais caro em integrações de checkout: o comprador pode chegar na `successUrl` sem que o pagamento tenha sido aprovado.