# Cancelar Assinatura Source: https://docs.garu.com.br/api-reference/assinaturas/cancelar POST /api/subscriptions/{id}/cancel Cancele uma assinatura imediatamente ou ao fim do período ## Visão Geral Cancele uma assinatura de duas formas: imediatamente (cliente perde acesso na hora) ou agendado para o fim do período pago (cliente mantém acesso até a data de renovação). ## Cancelamento Imediato O cliente perde acesso imediatamente e nenhuma cobrança futura será realizada. ```bash cURL theme={null} curl -X POST https://garu.com.br/api/subscriptions/100/cancel \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "cancelImmediately": true, "reason": "Cliente solicitou cancelamento" }' ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/subscriptions/100/cancel', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ cancelImmediately: true, reason: 'Cliente solicitou cancelamento' }) } ); const result = await response.json(); console.log(`Status: ${result.status}`); // "canceled" ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/subscriptions/100/cancel" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}", "Content-Type": "application/json" } payload = { "cancelImmediately": True, "reason": "Cliente solicitou cancelamento" } response = requests.post(url, json=payload, headers=headers) result = response.json() print(f"Status: {result['status']}") # "canceled" ``` **Resultado:** * Status muda para `canceled` * Acesso revogado imediatamente * Nenhuma cobrança futura ## Cancelamento Agendado O cliente mantém acesso até o fim do período pago atual. ```bash cURL theme={null} curl -X POST https://garu.com.br/api/subscriptions/100/cancel \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "cancelImmediately": false, "reason": "Cliente optou por não renovar" }' ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/subscriptions/100/cancel', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ cancelImmediately: false, reason: 'Cliente optou por não renovar' }) } ); const result = await response.json(); console.log(`Cancelamento agendado para: ${result.cancelAt}`); ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/subscriptions/100/cancel" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}", "Content-Type": "application/json" } payload = { "cancelImmediately": False, "reason": "Cliente optou por não renovar" } response = requests.post(url, json=payload, headers=headers) result = response.json() print(f"Cancelamento agendado para: {result['cancelAt']}") ``` **Resultado:** * Status muda para `pending_cancellation` * Cliente mantém acesso até `currentPeriodEnd` * Campo `cancelAt` preenchido com a data de cancelamento * No fim do período, status muda para `canceled` ## Parâmetros `true` = cancelamento imediato, `false` = cancelamento no fim do período. Motivo do cancelamento (opcional, para registro interno). ## Resposta de Sucesso ### Cancelamento Imediato (200 OK) ```json theme={null} { "id": 100, "status": "canceled", "cancelledAt": "2024-02-20T10:00:00.000Z", "message": "Assinatura cancelada com sucesso" } ``` ### Cancelamento Agendado (200 OK) ```json theme={null} { "id": 100, "status": "pending_cancellation", "cancelAt": "2024-03-15T00:00:00.000Z", "message": "Assinatura será cancelada em 2024-03-15" } ``` ## Erros Comuns ```json theme={null} { "statusCode": 404, "message": "Subscription not found" } ``` **Solução:** Verifique se o ID da assinatura está correto. ```json theme={null} { "statusCode": 400, "message": "Subscription is already canceled" } ``` **Solução:** A assinatura já foi cancelada anteriormente. ## Próximos Passos Pause temporariamente as cobranças Receba notificações de cancelamento # Detalhes da Assinatura Source: https://docs.garu.com.br/api-reference/assinaturas/detalhes GET /api/subscriptions/{id} Consulte os detalhes completos de uma assinatura específica ## Visão Geral Este endpoint retorna os detalhes completos de uma assinatura específica, incluindo informações do cliente, preço, método de pagamento e histórico de eventos. ## Exemplo de Requisição ```bash cURL theme={null} curl -X GET https://garu.com.br/api/subscriptions/100 \ -H "Authorization: Bearer sk_test_sua_chave" ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/subscriptions/100', { headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } } ); const subscription = await response.json(); console.log(`Cliente: ${subscription.customer.name}`); console.log(`Status: ${subscription.status}`); console.log(`Próximo pagamento: ${subscription.nextPaymentAt}`); ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/subscriptions/100" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}" } response = requests.get(url, headers=headers) subscription = response.json() print(f"Cliente: {subscription['customer']['name']}") print(f"Status: {subscription['status']}") print(f"Próximo pagamento: {subscription['nextPaymentAt']}") ``` ## Resposta de Sucesso ID interno da assinatura. Identificador único da assinatura. Status atual: `pending`, `active`, `pending_cancellation`, `canceled`, `failed`. Início do período de cobrança atual (ISO 8601). Fim do período de cobrança atual (ISO 8601). Data de término do período de teste. Data agendada para cancelamento. Se o cancelamento ocorrerá ao fim do período. Data da próxima cobrança. Data do último pagamento. Número de tentativas de pagamento falhas. Detalhes do preço/plano da assinatura. Dados do cliente. Método de pagamento associado. ### Exemplo de Resposta (200 OK) ```json theme={null} { "id": 100, "uuid": "sub_xyz789", "customerId": 50, "subscriptionPriceId": 456, "sellerId": 1, "status": "active", "currentPeriodStart": "2024-02-15T00:00:00.000Z", "currentPeriodEnd": "2024-03-15T00:00:00.000Z", "trialEndsAt": null, "cancelAt": null, "cancelledAt": null, "cancelAtPeriodEnd": false, "paymentMethodId": 789, "lastPaymentAt": "2024-02-15T10:00:00.000Z", "nextPaymentAt": "2024-03-15T10:00:00.000Z", "failedPaymentCount": 0, "createdAt": "2024-01-15T10:30:00.000Z", "updatedAt": "2024-02-15T10:00:00.000Z", "subscriptionPrice": { "id": 456, "uuid": "price_def456uvw", "name": "Plano Mensal", "unitAmount": 49.90, "billingInterval": "monthly", "product": { "id": 123, "name": "Premium Membership" } }, "customer": { "id": 50, "name": "João Silva", "email": "joao@example.com" }, "paymentMethod": { "id": 789, "cardLast4": "4242", "cardBrand": "visa" } } ``` ## Exemplo Completo ```javascript theme={null} async function verificarStatusAssinatura(subscriptionId) { const response = await fetch( `https://garu.com.br/api/subscriptions/${subscriptionId}`, { headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } } ); const subscription = await response.json(); // Verificar se está ativa const isActive = subscription.status === 'active'; // Verificar se está em período de teste const inTrial = subscription.trialEndsAt && new Date(subscription.trialEndsAt) > new Date(); // Verificar se tem cancelamento agendado const willCancel = subscription.cancelAtPeriodEnd || subscription.status === 'pending_cancellation'; return { isActive, inTrial, willCancel, nextPayment: subscription.nextPaymentAt, plan: subscription.subscriptionPrice.name, customer: subscription.customer }; } // Uso const status = await verificarStatusAssinatura(100); if (status.isActive) { console.log(`Assinatura ativa: ${status.plan}`); console.log(`Próxima cobrança: ${status.nextPayment}`); } ``` ## Próximos Passos Histórico de eventos e pagamentos Cancele uma assinatura Pause temporariamente as cobranças Autoatendimento para assinantes # Eventos da Assinatura Source: https://docs.garu.com.br/api-reference/assinaturas/eventos GET /api/subscriptions/{id}/events Consulte o histórico de eventos e billing history de uma assinatura ## Visão Geral Consulte o histórico completo de eventos de uma assinatura, incluindo pagamentos, mudanças de status, períodos de teste e cancelamentos. ## Exemplo de Requisição ```bash cURL theme={null} curl -X GET https://garu.com.br/api/subscriptions/100/events \ -H "Authorization: Bearer sk_test_sua_chave" ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/subscriptions/100/events', { headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } } ); const events = await response.json(); events.forEach(event => { console.log(`${event.createdAt}: ${event.eventType}`); }); ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/subscriptions/100/events" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}" } response = requests.get(url, headers=headers) events = response.json() for event in events: print(f"{event['createdAt']}: {event['eventType']}") ``` ## Resposta de Sucesso (200 OK) ```json theme={null} [ { "id": 789, "subscriptionId": 100, "eventType": "payment_success", "eventData": { "amount": 49.90, "transactionId": "tx_abc123", "paidAt": "2024-02-15T10:00:00.000Z" }, "createdAt": "2024-02-15T10:00:00.000Z" }, { "id": 788, "subscriptionId": 100, "eventType": "subscription.activated", "eventData": {}, "createdAt": "2024-01-15T10:30:00.000Z" }, { "id": 787, "subscriptionId": 100, "eventType": "subscription.created", "eventData": { "priceId": 456, "trialDays": 7 }, "createdAt": "2024-01-08T10:30:00.000Z" } ] ``` ## Tipos de Evento | Tipo | Descrição | | - | - | | `subscription.created` | Assinatura criada | | `subscription.activated` | Assinatura ativada após primeiro pagamento | | `trial_started` | Período de teste iniciado | | `trial_ended` | Período de teste encerrado | | `payment_success` | Pagamento recorrente bem-sucedido | | `payment_failed` | Tentativa de pagamento falhou | | `subscription.paused` | Assinatura pausada | | `subscription.resumed` | Assinatura retomada após pausa | | `subscription.canceled` | Assinatura cancelada | | `subscription.expired` | Assinatura expirou | ## Campos do Evento ID único do evento. ID da assinatura relacionada. Tipo do evento (veja tabela acima). Dados adicionais específicos do evento. Data e hora do evento (ISO 8601). ## Exemplo: Construindo um Billing History ```javascript theme={null} async function getBillingHistory(subscriptionId) { const response = await fetch( `https://garu.com.br/api/subscriptions/${subscriptionId}/events`, { headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } } ); const events = await response.json(); // Filtrar apenas eventos de pagamento const payments = events.filter(e => e.eventType === 'payment_success' || e.eventType === 'payment_failed' ); return payments.map(payment => ({ date: payment.createdAt, status: payment.eventType === 'payment_success' ? 'Pago' : 'Falhou', amount: payment.eventData.amount, transactionId: payment.eventData.transactionId })); } // Uso const history = await getBillingHistory(100); console.log('Histórico de Pagamentos:'); history.forEach(p => { console.log(`${p.date}: R$ ${p.amount.toFixed(2)} - ${p.status}`); }); ``` ## Próximos Passos Consulte os detalhes completos da assinatura Receba eventos em tempo real # Listar Assinaturas Source: https://docs.garu.com.br/api-reference/assinaturas/listar GET /api/subscriptions Consulte todas as assinaturas com filtros e paginação ## Visão Geral Este endpoint retorna uma lista paginada de todas as assinaturas. Você pode filtrar por status, cliente, produto e buscar por nome ou email do cliente. ## Exemplo de Requisição ```bash cURL theme={null} curl -X GET "https://garu.com.br/api/subscriptions?status=active&page=1&limit=20" \ -H "Authorization: Bearer sk_test_sua_chave" ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/subscriptions?status=active&page=1&limit=20', { headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } } ); const { data, count, totalCount, totalPages } = await response.json(); data.forEach(sub => { console.log(`${sub.customer.name}: ${sub.status}`); }); ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/subscriptions" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}" } params = { "status": "active", "page": 1, "limit": 20 } response = requests.get(url, headers=headers, params=params) result = response.json() for sub in result['data']: print(f"{sub['customer']['name']}: {sub['status']}") ``` ## Parâmetros de Query Número da página para paginação. Quantidade de itens por página. Filtrar por status: `pending`, `active`, `pending_cancellation`, `canceled`, `failed`. Filtrar por ID do cliente. Filtrar por ID do produto. Buscar por nome ou email do cliente. ## Resposta de Sucesso Lista de assinaturas. Total de assinaturas retornadas nesta página. Total de assinaturas que correspondem aos filtros. Página atual. Total de páginas disponíveis. ### Exemplo de Resposta (200 OK) ```json theme={null} { "data": [ { "id": 100, "uuid": "sub_xyz789", "customerId": 50, "subscriptionPriceId": 456, "status": "active", "currentPeriodStart": "2024-02-15T00:00:00.000Z", "currentPeriodEnd": "2024-03-15T00:00:00.000Z", "trialEndsAt": null, "cancelAt": null, "lastPaymentAt": "2024-02-15T10:00:00.000Z", "nextPaymentAt": "2024-03-15T10:00:00.000Z", "customer": { "id": 50, "name": "João Silva", "email": "joao@email.com" }, "subscriptionPrice": { "id": 456, "name": "Plano Mensal", "unitAmount": 49.90, "billingInterval": "monthly" } }, { "id": 101, "uuid": "sub_abc123", "customerId": 51, "subscriptionPriceId": 457, "status": "active", "currentPeriodStart": "2024-02-10T00:00:00.000Z", "currentPeriodEnd": "2025-02-10T00:00:00.000Z", "trialEndsAt": null, "cancelAt": null, "lastPaymentAt": "2024-02-10T10:00:00.000Z", "nextPaymentAt": "2025-02-10T10:00:00.000Z", "customer": { "id": 51, "name": "Maria Santos", "email": "maria@email.com" }, "subscriptionPrice": { "id": 457, "name": "Plano Anual", "unitAmount": 479.00, "billingInterval": "yearly" } } ], "count": 2, "totalCount": 50, "page": 1, "totalPages": 25 } ``` ## Status de Assinatura | Status | Descrição | | - | - | | `pending` | Aguardando primeiro pagamento | | `active` | Assinatura ativa e em dia | | `pending_cancellation` | Cancelamento agendado para fim do período | | `canceled` | Assinatura encerrada | | `failed` | Pagamento falhou após todas as tentativas | ## Paginação Para percorrer todas as assinaturas: ```javascript theme={null} async function listarTodasAssinaturas(status = 'active') { const assinaturas = []; let page = 1; let totalPages = 1; do { const response = await fetch( `https://garu.com.br/api/subscriptions?status=${status}&page=${page}&limit=50`, { headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } } ); const result = await response.json(); assinaturas.push(...result.data); totalPages = result.totalPages; page++; } while (page <= totalPages); return assinaturas; } ``` ## Próximos Passos Consulte os detalhes completos de uma assinatura Cancelar, pausar e retomar assinaturas # Pausar Assinatura Source: https://docs.garu.com.br/api-reference/assinaturas/pausar POST /api/subscriptions/{id}/pause Pause temporariamente as cobranças de uma assinatura ## Visão Geral Pause temporariamente as cobranças de uma assinatura. O cliente pode manter ou perder acesso durante a pausa, dependendo da sua implementação. Use o endpoint de [retomar](/api-reference/assinaturas/retomar) para reativar as cobranças. ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST https://garu.com.br/api/subscriptions/100/pause \ -H "Authorization: Bearer sk_test_sua_chave" ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/subscriptions/100/pause', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } } ); const result = await response.json(); console.log(`Status: ${result.status}`); // "paused" console.log(`Pausada em: ${result.pausedAt}`); ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/subscriptions/100/pause" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}" } response = requests.post(url, headers=headers) result = response.json() print(f"Status: {result['status']}") # "paused" print(f"Pausada em: {result['pausedAt']}") ``` ## Resultado * Status muda para `paused` * Cobranças são suspensas * Nenhum pagamento será tentado enquanto pausada * Use [retomar](/api-reference/assinaturas/retomar) para reativar ## Resposta de Sucesso (200 OK) ```json theme={null} { "id": 100, "status": "paused", "pausedAt": "2024-02-20T10:00:00.000Z", "message": "Assinatura pausada com sucesso" } ``` ## Considerações **Acesso durante a pausa**: A decisão de manter ou revogar o acesso do cliente durante a pausa é da sua aplicação. A Garu apenas suspende as cobranças. Assinaturas pausadas por muito tempo podem precisar de atualização do método de pagamento ao serem retomadas. ## Erros Comuns ```json theme={null} { "statusCode": 404, "message": "Subscription not found" } ``` **Solução:** Verifique se o ID da assinatura está correto. ```json theme={null} { "statusCode": 400, "message": "Cannot pause subscription with current status" } ``` **Solução:** Apenas assinaturas ativas podem ser pausadas. ## Próximos Passos Reative as cobranças de uma assinatura pausada Cancele definitivamente uma assinatura # Portal do Cliente Source: https://docs.garu.com.br/api-reference/assinaturas/portal POST /api/portal/sessions Permitir que assinantes gerenciem suas assinaturas de forma autônoma ## Visão Geral O Portal do Cliente é uma interface de autoatendimento que permite aos assinantes gerenciar suas assinaturas. Você controla o acesso gerando tokens de sessão seguros. ## Como Funciona 1. **Você cria uma sessão** do portal via API 2. **Recebe um token** seguro (válido por 1 hora) 3. **Redireciona o cliente** para o portal com o token 4. **Cliente gerencia** sua assinatura conforme sua configuração ## Criar Sessão do Portal Gere um token de acesso para um cliente: ```bash theme={null} curl -X POST https://garu.com.br/api/portal/sessions \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "customerId": 50, "productId": 123, "returnUrl": "https://seusite.com/conta" }' ``` ### Parâmetros ID do cliente na Garu. ID do produto para configuração específica (opcional). URL para redirecionar após ações no portal. ### Resposta ```json theme={null} { "sessionId": 789, "token": "a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456", "expiresAt": "2024-01-15T11:30:00.000Z", "portalUrl": "/portal?token=a1b2c3d4e5f6..." } ``` ### URL do Portal ``` https://garu.com.br/portal?token={session_token} ``` O token expira em **1 hora**. Gere um novo token a cada acesso do cliente. ## Configurar o Portal ### Configuração Global (Padrão para Todos os Produtos) ```bash theme={null} curl -X PUT https://garu.com.br/api/portal/configuration \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "businessName": "Sua Empresa", "logoUrl": "https://seu-cdn.com/logo.png", "primaryColor": "#257264", "allowCancelSubscription": true, "allowUpdatePaymentMethod": true, "allowUpdateBillingInfo": true, "allowViewInvoices": true, "cancelAtPeriodEndOnly": true, "requireCancelReason": false, "sendCancellationEmail": true, "sendPaymentMethodUpdatedEmail": true }' ``` ### Configuração por Produto Para sobrescrever a configuração global por produto (B2B2C — coach/instrutor/professor como Product), use o endpoint canônico documentado em **[Portal customizado por produto](/api-reference/produtos/portal-config)**. Na API v1, `{uuid}` é o identificador do produto — o identificador estável retornado por `GET /api/v1/products` e `list_products`. ### Opções de Configuração | Opção | Tipo | Descrição | | - | - | - | | `businessName` | string | Nome da empresa exibido no portal | | `logoUrl` | string | URL do logotipo | | `primaryColor` | string | Cor primária em hex (padrão: `#257264`) | | `allowCancelSubscription` | boolean | Permitir cancelamento | | `allowUpdatePaymentMethod` | boolean | Permitir trocar cartão | | `allowUpdateBillingInfo` | boolean | Permitir alterar dados de cobrança | | `allowViewInvoices` | boolean | Permitir ver histórico de pagamentos | | `cancelAtPeriodEndOnly` | boolean | Forçar cancelamento apenas no fim do período | | `requireCancelReason` | boolean | Exigir motivo ao cancelar | | `sendCancellationEmail` | boolean | Enviar email ao cancelar | | `sendPaymentMethodUpdatedEmail` | boolean | Enviar email ao trocar cartão | | `customWelcomeText` | string | Mensagem de boas-vindas personalizada | | `customSuccessMessage` | string | Mensagem de sucesso personalizada | | `customCancellationMessage` | string | Mensagem de confirmação de cancelamento | ## Ações Disponíveis no Portal Baseado na sua configuração, clientes podem: | Ação | Flag de Configuração | Padrão | | - | - | - | | Cancelar assinatura | `allowCancelSubscription` | `true` | | Atualizar cartão | `allowUpdatePaymentMethod` | `true` | | Atualizar dados de cobrança | `allowUpdateBillingInfo` | `true` | | Ver histórico de pagamentos | `allowViewInvoices` | `true` | | Pausar assinatura | `allowCancelSubscription` | `true` | | Retomar assinatura | `allowCancelSubscription` | `true` | ## Endpoints do Portal (Token-Based) Todos os endpoints do portal requerem o token de sessão como query parameter. ### Obter Dados do Portal ```bash theme={null} curl -X GET "https://garu.com.br/api/portal?token={session_token}" ``` Retorna informações do cliente, assinaturas ativas e configuração. ### Cancelar Assinatura ```bash theme={null} curl -X POST "https://garu.com.br/api/portal/subscriptions/100/cancel?token={session_token}" ``` ### Pausar Assinatura ```bash theme={null} curl -X POST "https://garu.com.br/api/portal/subscriptions/100/pause?token={session_token}" ``` ### Retomar Assinatura ```bash theme={null} curl -X POST "https://garu.com.br/api/portal/subscriptions/100/resume?token={session_token}" ``` ### Reativar Assinatura Cancelada ```bash theme={null} curl -X POST "https://garu.com.br/api/portal/subscriptions/100/reactivate?token={session_token}" ``` ### Atualizar Método de Pagamento ```bash theme={null} curl -X POST "https://garu.com.br/api/portal/subscriptions/100/update-payment-method?token={session_token}" \ -H "Content-Type: application/json" \ -d '{"paymentMethodId": 456}' ``` ### Adicionar Novo Cartão ```bash theme={null} curl -X POST "https://garu.com.br/api/portal/subscriptions/100/add-card-and-update?token={session_token}" \ -H "Content-Type: application/json" \ -d '{ "cardNumber": "4111111111111111", "holderName": "JOAO SILVA", "expiryMonth": 12, "expiryYear": 2025, "cvv": "123" }' ``` ### Ver Histórico de Pagamentos ```bash theme={null} curl -X GET "https://garu.com.br/api/portal/subscriptions/100/events?token={session_token}" ``` ### Atualizar Informações do Cliente ```bash theme={null} curl -X PUT "https://garu.com.br/api/portal/customer-info?token={session_token}" \ -H "Content-Type: application/json" \ -d '{ "name": "João Silva", "email": "joao@email.com", "phone": "11999998888" }' ``` ## Integração Frontend ### JavaScript ```javascript theme={null} class GaruPortal { constructor(apiKey) { this.apiKey = apiKey; this.baseUrl = 'https://garu.com.br/api'; this.portalUrl = 'https://garu.com.br/portal'; } async criarSessao(customerId, options = {}) { const response = await fetch(`${this.baseUrl}/portal/sessions`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ customerId, productId: options.productId, returnUrl: options.returnUrl || window.location.href }) }); return response.json(); } // Redirecionar para o portal async redirecionar(customerId, options = {}) { const session = await this.criarSessao(customerId, options); window.location.href = `${this.portalUrl}?token=${session.token}`; } // Abrir portal em nova janela async abrirJanela(customerId, options = {}) { const session = await this.criarSessao(customerId, options); window.open( `${this.portalUrl}?token=${session.token}`, 'GaruPortal', 'width=600,height=800' ); } } // Uso const portal = new GaruPortal('sk_test_sua_chave'); // Botão "Gerenciar Assinatura" document.getElementById('btn-gerenciar').addEventListener('click', async () => { await portal.redirecionar(usuarioAtual.garuCustomerId, { returnUrl: 'https://seusite.com/conta/assinaturas' }); }); ``` ### React ```jsx theme={null} import { useCallback } from 'react'; function BotaoPortal({ customerId }) { const abrirPortal = useCallback(async () => { const response = await fetch('/api/portal-session', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ customerId }) }); const { portalUrl } = await response.json(); window.location.href = `https://garu.com.br${portalUrl}`; }, [customerId]); return ( ); } ``` ### Python (Flask) ```python theme={null} from flask import Flask, redirect, request import requests import os app = Flask(__name__) GARU_API_KEY = os.environ['GARU_API_KEY'] @app.route('/portal') def abrir_portal(): customer_id = request.args.get('customer_id') response = requests.post( 'https://garu.com.br/api/portal/sessions', headers={ 'Authorization': f'Bearer {GARU_API_KEY}', 'Content-Type': 'application/json' }, json={ 'customerId': int(customer_id), 'returnUrl': request.url_root + 'conta' } ) session = response.json() return redirect(f"https://garu.com.br/portal?token={session['token']}") ``` ## Boas Práticas de Segurança Tokens de sessão são sensíveis. Não os registre em logs de aplicação. Não pré-gere tokens. Crie um novo a cada acesso do cliente. Sempre use URLs seguras para redirecionamento. Certifique-se de que o cliente pertence à sua conta antes de gerar o token. Use webhooks para acompanhar ações dos clientes no portal. ## Próximos Passos Receber notificações de ações no portal Voltar ao guia completo # Atualizar Preço de Assinatura Source: https://docs.garu.com.br/api-reference/assinaturas/precos/atualizar PATCH /api/subscription-prices/{id} Atualize um preço de assinatura existente ## Visão Geral Atualize um preço de assinatura existente. Alterações de valor e período de teste afetam apenas **novas assinaturas**. Assinaturas existentes mantêm os valores originais. ## Exemplo de Requisição ```bash cURL theme={null} curl -X PATCH https://garu.com.br/api/subscription-prices/456 \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "name": "Plano Mensal Premium", "unitAmount": 59.90, "isActive": true }' ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/subscription-prices/456', { method: 'PATCH', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Plano Mensal Premium', unitAmount: 59.90, isActive: true }) } ); const preco = await response.json(); console.log(`Preço atualizado: ${preco.name}`); ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/subscription-prices/456" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}", "Content-Type": "application/json" } payload = { "name": "Plano Mensal Premium", "unitAmount": 59.90, "isActive": True } response = requests.patch(url, json=payload, headers=headers) preco = response.json() print(f"Preço atualizado: {preco['name']}") ``` ## Parâmetros Nome do plano. Novo valor em **Reais** (BRL decimal, ex: `59.90`), nunca em centavos. Afeta apenas novas assinaturas. Precisa ser de **pelo menos R\$ 5,00**: um valor menor, inclusive `0`, é recusado com `400`. Omita o campo para manter o valor atual. Na atualização o campo se chama `unitAmount`, não `amount` (o nome usado na criação). Um `amount` enviado aqui é ignorado, e o valor não muda. Intervalo de cobrança: `daily`, `weekly`, `monthly`, `annually` (afeta apenas novas assinaturas). Dias de teste gratuito (afeta apenas novas assinaturas). Na atualização o campo se chama `trialPeriodDays`, não `trialDays`. Se o plano está disponível para novos assinantes. ## Campos Atualizáveis | Campo | Descrição | Afeta assinaturas existentes? | | - | - | - | | `name` | Nome do plano | Não | | `unitAmount` | Valor em Reais (mínimo R\$ 5,00) | **Não** - apenas novas | | `billingInterval` | Intervalo de cobrança | **Não** - apenas novas | | `trialPeriodDays` | Dias de teste | **Não** - apenas novas | | `isActive` | Disponibilidade | Não | Alterações de `unitAmount` e `trialPeriodDays` afetam apenas **novas** assinaturas. Assinaturas existentes mantêm os valores originais contratados. ## Resposta de Sucesso (200 OK) ```json theme={null} { "id": "price_def456uvw", "productId": "prod_abc123xyz", "sellerId": 1, "name": "Plano Mensal Premium", "unitAmount": 59.90, "currency": "BRL", "billingInterval": "monthly", "trialPeriodDays": 7, "isActive": true, "createdAt": "2024-01-15T10:35:00.000Z", "updatedAt": "2024-01-20T15:00:00.000Z" } ``` ## Desativar um Plano Para impedir novas assinaturas em um plano sem excluí-lo: ```bash theme={null} curl -X PATCH https://garu.com.br/api/subscription-prices/456 \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{"isActive": false}' ``` Desativar um plano não afeta assinaturas existentes. Elas continuam sendo cobradas normalmente. ## Erros Comuns ```json theme={null} { "statusCode": 404, "message": "Subscription price not found" } ``` **Solução:** Verifique se o ID do preço está correto. ```json theme={null} { "statusCode": 400, "timestamp": "2026-09-30T21:00:00.000Z", "path": "/api/subscription-prices/456", "message": "O preço mínimo de um produto é R$ 5,00." } ``` **Solução:** Envie um `unitAmount` de pelo menos `5.00`, em Reais. Um preço de assinatura não pode ser `0`. ## Próximos Passos Remova um preço Consulte assinaturas ativas # Criar Preço de Assinatura Source: https://docs.garu.com.br/api-reference/assinaturas/precos/criar POST /api/subscription-prices Crie planos de preço para produtos de assinatura ## Visão Geral Crie um preço de assinatura para definir quanto e com que frequência os clientes serão cobrados. Um produto pode ter múltiplos preços (ex: mensal, anual). ## Parâmetros ### Campos Obrigatórios UUID do produto pai (ex: `prod_abc123xyz`). O produto deve ser do tipo `subscription`. Nome do plano exibido ao cliente (ex: "Plano Mensal"). Valor cobrado a cada ciclo, em **Reais** (BRL decimal, ex: `49.90` = R$ 49,90), nunca em centavos. Precisa ser de **pelo menos R$ 5,00\*\*, o preço mínimo da plataforma: um valor menor, inclusive `0`, é recusado com `400`. Intervalo de cobrança: `daily`, `weekly`, `monthly`, `annually`. ### Campos Opcionais Dias de teste gratuito antes da primeira cobrança. Se o plano está disponível para novos assinantes. A moeda padrão é `BRL` (Real Brasileiro). Não é necessário enviar o campo `currency` na requisição. ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST https://garu.com.br/api/subscription-prices \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "productId": "prod_abc123xyz", "name": "Plano Mensal", "amount": 49.90, "billingInterval": "monthly", "trialDays": 7 }' ``` ```javascript JavaScript theme={null} const response = await fetch('https://garu.com.br/api/subscription-prices', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ productId: 'prod_abc123xyz', name: 'Plano Mensal', amount: 49.90, billingInterval: 'monthly', trialDays: 7 }) }); const preco = await response.json(); console.log(`Checkout: https://garu.com.br/pay/prod_abc123xyz?priceId=${preco.uuid}`); ``` ```python Python theme={null} import requests import os response = requests.post( 'https://garu.com.br/api/subscription-prices', headers={ 'Authorization': f'Bearer {os.environ["GARU_API_KEY"]}', 'Content-Type': 'application/json' }, json={ 'productId': 'prod_abc123xyz', 'name': 'Plano Mensal', 'amount': 49.90, 'billingInterval': 'monthly', 'trialDays': 7 } ) preco = response.json() print(f"Preço criado: {preco['uuid']}") ``` ## Resposta de Sucesso ID interno do preço. Identificador único usado no link de checkout. UUID do produto pai. Nome do plano. Valor em Reais. Intervalo de cobrança. Dias de teste gratuito. Se o plano está ativo. ### Exemplo de Resposta (201 Created) ```json theme={null} { "id": "price_def456uvw", "productId": "prod_abc123xyz", "sellerId": 1, "name": "Plano Mensal", "amount": 49.90, "currency": "BRL", "billingInterval": "monthly", "trialDays": 7, "isActive": true, "createdAt": "2024-01-15T10:35:00.000Z", "updatedAt": "2024-01-15T10:35:00.000Z" } ``` ## Erros Comuns ```json theme={null} { "statusCode": 400, "timestamp": "2026-09-30T21:00:00.000Z", "path": "/api/subscription-prices", "message": "O preço mínimo de um produto é R$ 5,00." } ``` **Solução:** Envie um `amount` de pelo menos `5.00`, em Reais. Um preço de assinatura não pode ser `0`. ## Intervalos de Cobrança | Intervalo | Valor | Descrição | | - | - | - | | Diário | `daily` | Cobrança a cada dia | | Semanal | `weekly` | Cobrança a cada 7 dias | | Mensal | `monthly` | Cobrança no mesmo dia todo mês | | Anual | `annually` | Cobrança uma vez por ano | ## Link de Pagamento Após criar o preço de assinatura, você pode gerar o link de pagamento combinando o UUID do produto com o ID do preço. ### Formato do Link ``` https://garu.com.br/pay/{product_uuid}?priceId={price_id} ``` | Parâmetro | Origem | Descrição | | - | - | - | | `product_uuid` | Resposta de `POST /api/v1/products` | UUID do produto (campo `uuid`) | | `price_id` | Resposta de `POST /api/subscription-prices` | ID do preço (campo `id`) | ### Exemplo Considerando: * Produto criado com `uuid`: `c69c63d2-4207-4613-ad69-28e7e902544b` * Preço criado com `id`: `price_gNgQVRrG5goab0Ql` O link de pagamento será: ``` https://garu.com.br/pay/c69c63d2-4207-4613-ad69-28e7e902544b?priceId=price_gNgQVRrG5goab0Ql ``` ### Exemplo Completo em Código ```javascript theme={null} // 1. Criar produto de assinatura const produto = await fetch('https://garu.com.br/api/v1/products', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Plano Premium', image: 'https://meusite.com/imagens/plano-premium.jpg', value: 0, // o preço fica nos preços de assinatura isSubscription: true }) }).then(r => r.json()); // 2. Criar preço de assinatura const preco = await fetch('https://garu.com.br/api/subscription-prices', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ productId: produto.uuid, name: 'Mensal', amount: 49.90, billingInterval: 'monthly' }) }).then(r => r.json()); // 3. Gerar link de pagamento const linkPagamento = `https://garu.com.br/pay/${produto.uuid}?priceId=${preco.id}`; console.log(linkPagamento); // https://garu.com.br/pay/c69c63d2-4207-4613-ad69-28e7e902544b?priceId=price_gNgQVRrG5goab0Ql ``` Salve o `uuid` do produto e o `id` do preço no seu banco de dados para poder gerar links de pagamento a qualquer momento. ## Múltiplos Planos Crie diferentes planos para o mesmo produto: ```javascript theme={null} // Plano Básico - Mensal await criarPreco({ productId: 'prod_abc123xyz', name: 'Básico Mensal', amount: 29.90, billingInterval: 'monthly' }); // Plano Pro - Mensal await criarPreco({ productId: 'prod_abc123xyz', name: 'Pro Mensal', amount: 49.90, billingInterval: 'monthly', trialDays: 7 }); // Plano Pro - Anual (com desconto de 20%) await criarPreco({ productId: 'prod_abc123xyz', name: 'Pro Anual', amount: 479.00, // 12 meses com 20% de desconto billingInterval: 'annually', trialDays: 14 }); ``` ## Próximos Passos Consulte todos os preços cadastrados Consulte as assinaturas ativas # Detalhes do Preço Source: https://docs.garu.com.br/api-reference/assinaturas/precos/detalhes GET /api/subscription-prices/{id} Consulte os detalhes de um preço de assinatura específico ## Visão Geral Retorna os detalhes completos de um preço de assinatura específico pelo ID. ## Exemplo de Requisição ```bash cURL theme={null} curl -X GET https://garu.com.br/api/subscription-prices/456 \ -H "Authorization: Bearer sk_test_sua_chave" ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/subscription-prices/456', { headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } } ); const preco = await response.json(); console.log(`Plano: ${preco.name}`); console.log(`Valor: R$ ${preco.amount.toFixed(2)}`); console.log(`Intervalo: ${preco.billingInterval}`); ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/subscription-prices/456" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}" } response = requests.get(url, headers=headers) preco = response.json() print(f"Plano: {preco['name']}") print(f"Valor: R$ {preco['amount']:.2f}") print(f"Intervalo: {preco['billingInterval']}") ``` ## Resposta de Sucesso ID interno do preço. Identificador único usado no link de checkout. UUID do produto pai. Nome do plano. Valor em Reais. Intervalo de cobrança: `daily`, `weekly`, `monthly`, `annually`. Dias de teste gratuito. Se o plano está ativo para novas assinaturas. ### Exemplo de Resposta (200 OK) ```json theme={null} { "id": "price_def456uvw", "productId": "prod_abc123xyz", "sellerId": 1, "name": "Plano Mensal", "amount": 49.90, "currency": "BRL", "billingInterval": "monthly", "trialDays": 7, "isActive": true, "createdAt": "2024-01-15T10:35:00.000Z", "updatedAt": "2024-01-15T10:35:00.000Z" } ``` ## Erros Comuns ```json theme={null} { "statusCode": 404, "message": "Subscription price not found" } ``` **Solução:** Verifique se o ID do preço está correto. ## Próximos Passos Atualize um preço existente Remova um preço # Excluir Preço de Assinatura Source: https://docs.garu.com.br/api-reference/assinaturas/precos/excluir DELETE /api/subscription-prices/{id} Exclua um preço de assinatura ## Visão Geral Exclua um preço de assinatura. Preços com assinaturas ativas não podem ser excluídos. Não é possível excluir preços com assinaturas ativas. Desative o preço (`isActive: false`) usando o endpoint de [atualização](/api-reference/assinaturas/precos/atualizar) em vez de excluir. ## Exemplo de Requisição ```bash cURL theme={null} curl -X DELETE https://garu.com.br/api/subscription-prices/456 \ -H "Authorization: Bearer sk_test_sua_chave" ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/subscription-prices/456', { method: 'DELETE', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } } ); if (response.status === 204) { console.log('Preço excluído com sucesso'); } ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/subscription-prices/456" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}" } response = requests.delete(url, headers=headers) if response.status_code == 204: print("Preço excluído com sucesso") ``` ## Resposta de Sucesso (204 No Content) A resposta não contém corpo quando a exclusão é bem-sucedida. ## Quando Usar * O preço foi criado por engano * O plano nunca foi utilizado * Não há assinaturas ativas neste preço ## Alternativa: Desativar Se o preço tem assinaturas ativas, desative-o em vez de excluir: ```bash theme={null} curl -X PATCH https://garu.com.br/api/subscription-prices/456 \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{"isActive": false}' ``` Isso impede novas assinaturas sem afetar as existentes. ## Erros Comuns ```json theme={null} { "statusCode": 400, "message": "Cannot delete price with active subscriptions" } ``` **Solução:** Desative o preço em vez de excluí-lo, ou cancele todas as assinaturas primeiro. ```json theme={null} { "statusCode": 404, "message": "Subscription price not found" } ``` **Solução:** Verifique se o ID do preço está correto. ## Próximos Passos Crie um novo preço de assinatura Consulte todos os preços # Listar Preços de Assinatura Source: https://docs.garu.com.br/api-reference/assinaturas/precos/listar GET /api/subscription-prices Consulte todos os preços de assinatura cadastrados ## Visão Geral Retorna todos os preços de assinatura do vendedor autenticado. ## Exemplo de Requisição ```bash cURL theme={null} curl -X GET https://garu.com.br/api/subscription-prices \ -H "Authorization: Bearer sk_test_sua_chave" ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/subscription-prices', { headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } } ); const { data } = await response.json(); data.forEach(preco => { console.log(`${preco.name}: R$ ${preco.amount.toFixed(2)}/${preco.billingInterval}`); }); ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/subscription-prices" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}" } response = requests.get(url, headers=headers) result = response.json() for preco in result['data']: print(f"{preco['name']}: R$ {preco['amount']:.2f}/{preco['billingInterval']}") ``` ## Parâmetros de Query Filtrar por UUID do produto (ex: `prod_abc123xyz`). Filtrar por status ativo/inativo. ## Resposta de Sucesso (200 OK) ```json theme={null} { "data": [ { "id": "price_def456uvw", "productId": "prod_abc123xyz", "name": "Plano Mensal", "amount": 49.90, "currency": "BRL", "billingInterval": "monthly", "trialDays": 7, "isActive": true, "createdAt": "2024-01-15T10:35:00.000Z" }, { "id": "price_ghi789abc", "productId": "prod_abc123xyz", "name": "Plano Anual", "amount": 479.00, "currency": "BRL", "billingInterval": "annually", "trialDays": 14, "isActive": true, "createdAt": "2024-01-15T10:40:00.000Z" } ] } ``` ## Exemplo: Listar Preços por Produto ```javascript theme={null} async function listarPrecosPorProduto(productId) { const response = await fetch( `https://garu.com.br/api/subscription-prices?productId=${productId}`, { headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } } ); const { data } = await response.json(); return data.map(preco => ({ id: preco.id, nome: preco.name, valor: preco.amount, moeda: preco.currency, intervalo: preco.billingInterval, trialDias: preco.trialDays, checkoutUrl: `https://garu.com.br/pay/${productId}?priceId=${preco.id}` })); } // Uso: const precos = await listarPrecosPorProduto('prod_abc123xyz'); ``` ## Próximos Passos Consulte os detalhes de um preço específico Atualize um preço existente # Retomar Assinatura Source: https://docs.garu.com.br/api-reference/assinaturas/retomar POST /api/subscriptions/{id}/resume Retome uma assinatura pausada e reative as cobranças ## Visão Geral Retome uma assinatura que foi pausada, reativando as cobranças recorrentes. A próxima cobrança será agendada automaticamente. ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST https://garu.com.br/api/subscriptions/100/resume \ -H "Authorization: Bearer sk_test_sua_chave" ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/subscriptions/100/resume', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } } ); const result = await response.json(); console.log(`Status: ${result.status}`); // "active" console.log(`Próximo pagamento: ${result.nextPaymentAt}`); ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/subscriptions/100/resume" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}" } response = requests.post(url, headers=headers) result = response.json() print(f"Status: {result['status']}") # "active" print(f"Próximo pagamento: {result['nextPaymentAt']}") ``` ## Resultado * Status muda para `active` * Cobranças são retomadas * Próxima cobrança é agendada automaticamente ## Resposta de Sucesso (200 OK) ```json theme={null} { "id": 100, "status": "active", "nextPaymentAt": "2024-03-20T10:00:00.000Z", "message": "Assinatura retomada com sucesso" } ``` ## Erros Comuns ```json theme={null} { "statusCode": 404, "message": "Subscription not found" } ``` **Solução:** Verifique se o ID da assinatura está correto. ```json theme={null} { "statusCode": 400, "message": "Subscription is not paused" } ``` **Solução:** Apenas assinaturas com status `paused` podem ser retomadas. ```json theme={null} { "statusCode": 400, "message": "Payment method is no longer valid" } ``` **Solução:** O cliente precisa atualizar o método de pagamento através do [Portal do Cliente](/api-reference/assinaturas/portal). ## Próximos Passos Consulte o status atual da assinatura Permita que clientes gerenciem seus dados # Autenticação Source: https://docs.garu.com.br/api-reference/autenticacao Configure suas chaves de API para acessar os endpoints da Garu ## Métodos de Autenticação A API da Garu suporta dois métodos de autenticação: | Método | Formato do Header | Caso de Uso | | - | - | - | | **Chave de API** | `Bearer sk_live_xxx` ou `Bearer sk_test_xxx` | Integrações servidor-para-servidor | | **Token JWT** | `Bearer eyJhbG...` | Dashboard e aplicações frontend | Para integrações de API, recomendamos usar **Chaves de API** por serem mais simples e seguras para comunicação entre servidores. ## Obtendo sua Chave de API Faça login no [Dashboard da Garu](https://garu.com.br/inicio) Vá para **Configurações** → **Desenvolvedores** Clique em **Criar chave de API** Escolha o tipo de chave: * `sk_test_` - Para desenvolvimento e testes (sem cobranças reais) * `sk_live_` - Para produção (processa pagamentos reais) **Importante:** Salve a chave imediatamente. Ela só será exibida uma vez! ## Usando sua Chave de API Inclua sua chave de API no header `Authorization` de todas as requisições: ```bash theme={null} curl -X GET https://garu.com.br/api/v1/products \ -H "Authorization: Bearer sk_test_abc123xyz..." ``` ### Exemplos por Linguagem ```javascript JavaScript theme={null} const response = await fetch('https://garu.com.br/api/v1/products', { headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' } }); ``` ```python Python theme={null} import requests import os headers = { 'Authorization': f'Bearer {os.environ["GARU_API_KEY"]}', 'Content-Type': 'application/json' } response = requests.get( 'https://garu.com.br/api/v1/products', headers=headers ) ``` ```php PHP theme={null} true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$apiKey}", 'Content-Type: application/json' ] ]); $response = curl_exec($ch); curl_close($ch); ``` ## Ambientes | Ambiente | Prefixo da Chave | Descrição | | - | - | - | | **Sandbox** | `sk_test_` | Ambiente de testes. Nenhuma cobrança real é processada. | | **Produção** | `sk_live_` | Ambiente de produção. Processa pagamentos reais. | **Nunca** use chaves `sk_live_` em ambientes de desenvolvimento ou testes. Cobranças reais serão processadas! ## Boas Práticas de Segurança Chaves de API devem ser usadas apenas em código servidor. Nunca inclua chaves em JavaScript frontend, aplicativos móveis ou repositórios públicos. Armazene suas chaves em variáveis de ambiente, não diretamente no código: ```bash theme={null} # .env GARU_API_KEY=sk_test_abc123xyz... ``` Crie chaves separadas para desenvolvimento, staging e produção. Isso facilita a rotação e auditoria. Se uma chave for exposta, revogue-a imediatamente no Dashboard e crie uma nova. A API tem limite de 100 requisições por minuto. Implemente backoff exponencial para lidar com erros 429. ## Erros de Autenticação ### 401 Unauthorized ```json theme={null} { "statusCode": 401, "message": "API key is required" } ``` **Causas comuns:** * Header `Authorization` ausente * Prefixo `Bearer` faltando * Chave de API inválida ou expirada **Solução:** Verifique se o header está no formato correto: `Authorization: Bearer sk_xxx` ### 403 Forbidden ```json theme={null} { "statusCode": 403, "message": "Insufficient permissions" } ``` **Causas comuns:** * Chave de API não tem permissão para o endpoint * Tentando acessar recursos de outro vendedor **Solução:** Verifique as permissões da sua chave no Dashboard. ## Testando sua Autenticação Use este comando para verificar se sua chave está funcionando: ```bash theme={null} curl -X GET https://garu.com.br/api/v1/products \ -H "Authorization: Bearer sk_test_sua_chave_api" \ -w "\nHTTP Status: %{http_code}\n" ``` Se você receber HTTP Status 200, sua autenticação está configurada corretamente! ## Próximos Passos Aprenda a criar seu primeiro produto Obtenha o link para receber pagamentos # Adiar Parcela Source: https://docs.garu.com.br/api-reference/carne/adiar POST /api/v1/installment-plans/{uuid}/installments/{number}/postpone Move uma parcela para uma data posterior ## Visão Geral Move **uma** parcela. As outras mantêm as datas — isso adia um pagamento, não re-parcela o carnê. Um boleto já emitido continua pagável na data original até expirar. Adiar não mata o código de barras que o comprador já tem. ## Exemplo ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/installment-plans/e5d0d8fe-0000-4000-8000-000000000001/installments/4/postpone \ -H "Authorization: Bearer sk_live_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "newDueDate": "2026-12-20" }' ``` ```javascript JavaScript theme={null} await garu.installmentPlans.postponeInstallment(uuid, 4, { newDueDate: '2026-12-20' }); ``` ## Parâmetros Nova data em `YYYY-MM-DD`. Há um limite de quanto uma parcela pode ser empurrada — adiar por um ano seria re-parcelar pela porta dos fundos, e os totais do plano deixariam de descrever o que o comprador aceitou. # Cancelar Carnê Source: https://docs.garu.com.br/api-reference/carne/cancelar POST /api/v1/installment-plans/{uuid}/cancel Encerra o carnê e cancela os boletos em aberto ## Visão Geral Para a emissão e os lembretes, e cancela no banco os boletos ainda abertos. **O dinheiro já recebido não volta.** Cancelar encerra a cobrança futura; para devolver o que já entrou, abra um [pedido de estorno](/api-reference/carne/solicitar-estorno). Um carnê cancelado pelo vendedor **nunca** é revivido por um pagamento atrasado. Dinheiro que chegar depois abre um pedido de estorno automaticamente, em vez de reativar o plano. ## Exemplo ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/installment-plans/e5d0d8fe-0000-4000-8000-000000000001/cancel \ -H "Authorization: Bearer sk_live_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "note": "Comprador desistiu" }' ``` ```javascript JavaScript theme={null} await garu.installmentPlans.cancel(uuid, { note: 'Comprador desistiu' }); ``` ## Parâmetros Registrada no evento de cancelamento, para a trilha de auditoria. # Criar Carnê Source: https://docs.garu.com.br/api-reference/carne/criar POST /api/v1/installment-plans Venda um produto em até 12 boletos mensais ## Visão Geral Cria um carnê e registra **apenas o primeiro boleto**. As parcelas seguintes são emitidas mês a mês, e a venda só se torna real quando a parcela 1 compensa. Carnê é **crédito concedido por você**, não parcelamento de cartão. Ninguém garante um boleto: se o comprador parar na parcela 4, você fica com 4 parcelas. Leia o [guia do carnê](/guias/carne) antes de ativar. **Envie sempre `X-Idempotency-Key`.** Esta chamada registra um boleto de verdade no banco — uma retentativa sem a chave coloca dois códigos de barras pagáveis na mão do mesmo comprador. A mesma chave devolve o carnê original por 24h. ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/installment-plans \ -H "Authorization: Bearer sk_live_sua_chave" \ -H "Content-Type: application/json" \ -H "X-Idempotency-Key: 6c4c2a1e-4c2b-4f1c-9a8d-1b2e3f4a5b6c" \ -d '{ "productId": "40381e8e-6ee7-4b8e-9393-766a6e2109d2", "customerId": 4821, "installments": 12 }' ``` ```javascript JavaScript theme={null} import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY }); const carne = await garu.installmentPlans.create({ productId: '40381e8e-6ee7-4b8e-9393-766a6e2109d2', customerId: 4821, installments: 12 }); console.log(carne.totalScheduled); // 1560 console.log(carne.installmentAmount); // 130 ``` ```python Python theme={null} import os, uuid, requests r = requests.post( "https://garu.com.br/api/v1/installment-plans", headers={ "Authorization": f"Bearer {os.environ['GARU_API_KEY']}", "X-Idempotency-Key": str(uuid.uuid4()), }, json={ "productId": "40381e8e-6ee7-4b8e-9393-766a6e2109d2", "customerId": 4821, "installments": 12, }, ) print(r.json()["totalScheduled"]) # 1560 ``` ## Parâmetros UUID do produto. O produto precisa ter carnê habilitado. ID numérico do cliente. Clientes já têm um `uuid` público (veja [Clientes](/api-reference/clientes/criar)), mas este endpoint ainda linka pelo id numérico interno — obtenha-o via `POST /api/customers` (API interna) ou no dashboard. Número de parcelas, de 2 a 12. Uma parcela só não é carnê. O teto da plataforma é 12 e o vendedor pode definir um menor por produto. Vencimento da parcela 1 em `YYYY-MM-DD`. Padrão: hoje. Deve cair nos próximos 90 dias. As demais parcelas caem no mesmo dia dos meses seguintes, calculadas a partir dessa âncora — por isso um carnê ancorado em 31/01 não “escorrega” depois de fevereiro. Afiliado que fez a venda. **Fixado no momento da venda**: todas as parcelas seguintes herdam ele, então omitir aqui significa não pagar comissão nenhuma no carnê inteiro. Precisa ter afiliação ativa neste produto — caso contrário a chamada é recusada, em vez de descartar a atribuição em silêncio. ## Resposta ```json theme={null} { "uuid": "e5d0d8fe-0000-4000-8000-000000000001", "status": "pending_activation", "installments": 12, "installmentsPaid": 0, "baseValue": 1200, "fator": 1.3, "installmentAmount": 130, "totalScheduled": 1560, "totalCollected": 0, "firstDueDate": "2026-09-05", "installmentsDetail": [ { "number": 1, "amount": 130, "dueDate": "2026-09-05", "status": "scheduled", "boleto": { "barcodeLine": "50990...", "pdfUrl": "https://garu.com.br/..." }, "reissueCount": 0 } ] } ``` Só a parcela 1 aparece com boleto. As demais ainda não existem como slip pagável — elas são criadas quando a parcela 1 compensa. ## Erros | Código | Quando | | - | - | | `400` | Produto não aceita carnê, número de parcelas fora de 2..12, ou afiliado sem afiliação ativa | | `404` | Produto não encontrado nesta conta | | `409` | Um carnê idêntico acabou de ser criado para este cliente | # Detalhes do Carnê Source: https://docs.garu.com.br/api-reference/carne/detalhes GET /api/v1/installment-plans/{uuid} Um carnê com todas as suas parcelas ## Visão Geral Retorna o carnê com **todas as parcelas**: vencimento, status, linha digitável e PDF do boleto. ## Exemplo ```bash cURL theme={null} curl https://garu.com.br/api/v1/installment-plans/e5d0d8fe-0000-4000-8000-000000000001 \ -H "Authorization: Bearer sk_live_sua_chave" ``` ```javascript JavaScript theme={null} const carne = await garu.installmentPlans.get(uuid); const emAberto = carne.installmentsDetail.filter((p) => p.status !== 'paid'); console.log(carne.totalCollected); // o que realmente entrou ``` ## Campos de dinheiro `totalScheduled` é o que o carnê **cobra**. `totalCollected` é o que **entrou**. Eles divergem quando o banco soma multa ou mora, e por isso `totalCollected` pode ficar acima de `totalScheduled`. Não use um no lugar do outro para conciliar. | Campo | Significado | | - | - | | `baseValue` | Preço à vista do produto | | `fator` | Multiplicador de juros, congelado no momento da venda | | `installmentAmount` | Valor de cada parcela | | `totalScheduled` | `installmentAmount × installments` | | `totalCollected` | O que compensou de fato | ## Parcelas Cada item de `installmentsDetail` traz: Posição da parcela, começando em 1. `scheduled`, `due_today`, `paid`, `overdue`, `failed` ou `canceled`. `null` enquanto a parcela não foi emitida. Parcelas futuras não têm boleto — não mostre um código de barras vazio ao comprador como se fosse pagável. Quantas segundas vias já foram emitidas. # Listar Carnês Source: https://docs.garu.com.br/api-reference/carne/listar GET /api/v1/installment-plans Lista os carnês da conta, com filtros e paginação ## Visão Geral Lista os carnês do vendedor autenticado, do mais recente para o mais antigo. `dueFrom` e `dueTo` filtram pelo vencimento da **primeira** parcela, que é o que identifica o carnê. Filtrar por qualquer parcela devolveria o mesmo carnê doze vezes. ## Exemplo ```bash cURL theme={null} curl "https://garu.com.br/api/v1/installment-plans?status=active&status=defaulted&limit=50" \ -H "Authorization: Bearer sk_live_sua_chave" ``` ```javascript JavaScript theme={null} const emRisco = await garu.installmentPlans.list({ status: 'defaulted' }); const doCliente = await garu.installmentPlans.list({ status: ['active', 'pending_activation'], customerId: 4821 }); ``` ## Parâmetros de Query Página, padrão 1. Itens por página, padrão 20, máximo 100. Repita o parâmetro para aceitar vários. Valores: `pending_activation`, `active`, `completed`, `defaulted`, `canceled`, `refunded`. Filtra por cliente. Filtra pelo UUID do produto. Primeira parcela vence a partir desta data (`YYYY-MM-DD`). Primeira parcela vence até esta data (`YYYY-MM-DD`). ## Resposta ```json theme={null} { "data": [ { "uuid": "e5d0d8fe-0000-4000-8000-000000000001", "status": "active", "installmentsPaid": 3 } ], "count": 1, "totalCount": 1, "totalPages": 1 } ``` A lista não traz o array de parcelas. Use [Detalhes do Carnê](/api-reference/carne/detalhes) para isso. # Marcar Parcela como Paga Source: https://docs.garu.com.br/api-reference/carne/marcar-como-paga POST /api/v1/installment-plans/{uuid}/installments/{number}/mark-paid Registra o pagamento de uma parcela quando o webhook não chegou ## Visão Geral Para quando o comprador pagou o boleto mas o webhook não chegou. A Garu **pergunta ao provedor** se a cobrança realmente compensou antes de registrar. Essa ação liquida a transação e paga comissões de afiliado e coprodutor — se o provedor estiver fora do ar, a ação é recusada em vez de confiar na afirmação. ## Exemplo ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/installment-plans/e5d0d8fe-0000-4000-8000-000000000001/installments/3/mark-paid \ -H "Authorization: Bearer sk_live_sua_chave" ``` ```javascript JavaScript theme={null} const parcela = await garu.installmentPlans.markInstallmentPaid(uuid, 3); console.log(parcela.status); // 'paid' ``` O valor registrado é o valor de face do boleto. Multa e mora não são conhecidas sem o provedor. ## Erros | Código | Quando | | - | - | | `409` | O provedor não reporta essa cobrança como paga | # Segunda Via de Parcela Source: https://docs.garu.com.br/api-reference/carne/segunda-via POST /api/v1/installment-plans/{uuid}/installments/{number}/reissue Emite um novo boleto para uma parcela vencida ## Visão Geral Emite uma segunda via para uma parcela — **somente depois que o boleto atual expirou**. Um boleto continua pagável em qualquer banco até o vencimento **mais cinco dias**. Por isso a Garu recusa a segunda via enquanto o código antigo ainda está vivo: dois códigos vivos para a mesma parcela é como o comprador acaba pagando duas vezes. Permitido **uma vez por parcela por dia**, limite compartilhado com o cron de emissão. ## Exemplo ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/installment-plans/e5d0d8fe-0000-4000-8000-000000000001/installments/4/reissue \ -H "Authorization: Bearer sk_live_sua_chave" ``` ```javascript JavaScript theme={null} const resultado = await garu.installmentPlans.reissueInstallment(uuid, 4); if (resultado.status === 'emitted') { enviar(resultado.installment.boleto.barcodeLine); } ``` ## Resposta ```json theme={null} { "status": "emitted", "reason": null, "installment": { "number": 4, "reissueCount": 1 } } ``` | `status` | Significado | | - | - | | `emitted` | Nova via emitida; use a linha digitável retornada | | `skipped` | Nada a fazer — veja `reason` | | `failed` | O provedor recusou; veja `reason` | # Solicitar Estorno de Carnê Source: https://docs.garu.com.br/api-reference/carne/solicitar-estorno POST /api/v1/installment-plans/{uuid}/refund-requests Abre um pedido de estorno — a Garu não move esse dinheiro ## Visão Geral **A Garu não devolve esse dinheiro por você.** Um boleto não tem estorno e o valor já foi liquidado para a sua conta — a devolução é uma transferência bancária que só você pode fazer. Esta chamada **registra o pedido** e avisa o seu time. O carnê **continua rodando** enquanto o pedido está pendente: as próximas parcelas ainda são emitidas. Só a confirmação encerra o plano. ## Exemplo ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/installment-plans/e5d0d8fe-0000-4000-8000-000000000001/refund-requests \ -H "Authorization: Bearer sk_live_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "reason": "Produto não entregue" }' ``` ```javascript JavaScript theme={null} const pedido = await garu.installmentPlans.requestRefund(uuid, { reason: 'Produto não entregue' }); console.log(pedido.status); // 'pending' — nada se moveu ainda ``` ## Parâmetros Valor em BRL decimal. Padrão: tudo o que o carnê **realmente arrecadou** — que não é o mesmo que o total agendado. Motivo do pedido. ## Idempotência Envie `X-Idempotency-Key` para reenviar a chamada com segurança sem abrir um segundo pedido. A API já recusa um segundo pedido pendente para o mesmo carnê, então o header é uma proteção extra para a janela de rede entre o pedido ser aberto e a resposta chegar. ## Próximo passo Depois de devolver o dinheiro, feche o pedido com [Confirmar Estorno](/api-reference/estornos/confirmar). # Criar Checkout Session Source: https://docs.garu.com.br/api-reference/checkout/criar POST /api/checkout/sessions Crie links de pagamento programaticamente com dados do cliente pré-preenchidos ## Visão Geral Cria uma nova checkout session e retorna uma URL de pagamento. Para entender quando e como usar Checkout Sessions, consulte o [guia completo](/guias/checkout-sessions). ## Headers Sua chave de API (`Bearer YOUR_API_KEY`) `application/json` Chave única para evitar requisições duplicadas (válida por 24h) ## Request Body ### Campo Obrigatório ID do produto a ser vendido ### Campos Opcionais ID do preço de assinatura (para pagamentos recorrentes) Dados do cliente para pré-preencher o formulário de checkout Nome completo do cliente (máx 255 caracteres) Email do cliente (máx 255 caracteres) CPF ou CNPJ (máx 14 caracteres, apenas números) Telefone (máx 11 caracteres, apenas números) CEP (máx 8 caracteres, apenas números) Rua (máx 255 caracteres) Número do endereço (máx 20 caracteres) Complemento (máx 255 caracteres) Bairro (máx 100 caracteres) Cidade (máx 100 caracteres) Estado (2 letras, ex: "SP") Restringe os métodos de pagamento disponíveis. Valores aceitos: `pix`, `creditCard`, `boleto` Dados personalizados para seu uso (máx 50 chaves, 500 caracteres por valor) URL de redirecionamento após pagamento bem-sucedido (máx 500 caracteres). Use `{SESSION_ID}` como placeholder. URL de redirecionamento se o cliente cancelar (máx 500 caracteres) Seu ID de referência interno (máx 200 caracteres) ID do afiliado para atribuição de comissão Data de expiração da session (ISO 8601). Padrão: 24 horas após criação ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST https://api.garu.com.br/api/checkout/sessions \ -H "Authorization: Bearer sk_test_sua_chave_api" \ -H "Content-Type: application/json" \ -H "X-Idempotency-Key: order_12345_checkout" \ -d '{ "product_id": 123, "customer": { "name": "João Silva", "email": "joao@email.com" }, "success_url": "https://seusite.com/sucesso?session_id={SESSION_ID}", "cancel_url": "https://seusite.com/cancelado", "metadata": { "order_id": "12345" } }' ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.garu.com.br/api/checkout/sessions', { method: 'POST', headers: { 'Authorization': 'Bearer ' + process.env.GARU_API_KEY, 'Content-Type': 'application/json', 'X-Idempotency-Key': 'order_' + Date.now() }, body: JSON.stringify({ product_id: 123, customer: { name: 'João Silva', email: 'joao@email.com' }, success_url: 'https://seusite.com/sucesso?session_id={SESSION_ID}', cancel_url: 'https://seusite.com/cancelado', metadata: { order_id: '12345' } }) }); const { data } = await response.json(); // Redirecionar cliente para data.url ``` ```python Python theme={null} import requests import os response = requests.post( 'https://api.garu.com.br/api/checkout/sessions', headers={ 'Authorization': 'Bearer ' + os.environ['GARU_API_KEY'], 'X-Idempotency-Key': 'order_12345_checkout' }, json={ 'product_id': 123, 'customer': { 'name': 'João Silva', 'email': 'joao@email.com' }, 'success_url': 'https://seusite.com/sucesso?session_id={SESSION_ID}', 'cancel_url': 'https://seusite.com/cancelado', 'metadata': {'order_id': '12345'} } ) data = response.json()['data'] # Redirecionar cliente para data['url'] ``` ## Resposta de Sucesso Identificador único da session (ex: `cs_ABC123xyz`) Status da session: `open`, `complete` ou `expired` URL do checkout para redirecionar o cliente ID do produto ID do preço de assinatura (se aplicável) Email do cliente pré-preenchido Nome do cliente pré-preenchido Seus metadados personalizados URL de redirecionamento de sucesso URL de redirecionamento de cancelamento Seu ID de referência ID do afiliado ID da transação (preenchido quando completa) Data de expiração (ISO 8601) Data de conclusão (ISO 8601, null se não completa) Data de criação (ISO 8601) ### Exemplo de Resposta (201 Created) ```json theme={null} { "data": { "id": "cs_ABC123xyz", "status": "open", "url": "https://pay.garu.com.br/pay/session/abc123...", "product_id": 123, "price_id": null, "customer_email": "joao@email.com", "customer_name": "João Silva", "metadata": { "order_id": "12345" }, "success_url": "https://seusite.com/sucesso?session_id={SESSION_ID}", "cancel_url": "https://seusite.com/cancelado", "client_reference_id": null, "affiliate_id": null, "transaction_id": null, "expires_at": "2025-01-20T12:00:00.000Z", "completed_at": null, "created_at": "2025-01-19T12:00:00.000Z" } } ``` ## Erros Comuns ```json theme={null} { "statusCode": 400, "message": "product_id is required", "error": "Bad Request" } ``` **Solução:** Inclua o campo `product_id` na requisição. ```json theme={null} { "statusCode": 400, "message": "Invalid payment method", "error": "Bad Request" } ``` **Solução:** Use apenas valores válidos em `payment_methods`: `pix`, `creditCard`, `boleto`. ```json theme={null} { "statusCode": 401, "message": "Unauthorized" } ``` **Solução:** Verifique se o header `Authorization` está correto. ```json theme={null} { "statusCode": 404, "message": "Product not found" } ``` **Solução:** Verifique se o `product_id` existe e pertence ao seu vendedor. ```json theme={null} { "statusCode": 429, "message": "Too Many Requests" } ``` **Solução:** Aguarde antes de fazer novas requisições. Limite: 100 requisições por minuto. ## Próximos Passos Entenda quando e como usar Consulte suas sessions # Detalhes da Checkout Session Source: https://docs.garu.com.br/api-reference/checkout/detalhes GET /api/checkout/sessions/{id} Consulte os detalhes de uma checkout session específica ## Visão Geral Retorna os detalhes completos de uma checkout session específica. ## Parâmetros de Path ID da session (ex: `cs_ABC123xyz`) ## Exemplo de Requisição ```bash cURL theme={null} curl -X GET "https://api.garu.com.br/api/checkout/sessions/cs_ABC123xyz" \ -H "Authorization: Bearer sk_test_sua_chave_api" ``` ```javascript JavaScript theme={null} const sessionId = 'cs_ABC123xyz'; const response = await fetch( 'https://api.garu.com.br/api/checkout/sessions/' + sessionId, { headers: { 'Authorization': 'Bearer ' + process.env.GARU_API_KEY } } ); const result = await response.json(); console.log('Status:', result.data.status); console.log('Transaction ID:', result.data.transaction_id); ``` ```python Python theme={null} import requests import os session_id = 'cs_ABC123xyz' response = requests.get( 'https://api.garu.com.br/api/checkout/sessions/' + session_id, headers={'Authorization': 'Bearer ' + os.environ['GARU_API_KEY']} ) data = response.json()['data'] print('Status:', data['status']) print('Transaction ID:', data['transaction_id']) ``` ## Resposta de Sucesso Identificador único da session Status da session: `open`, `complete` ou `expired` URL do checkout ID do produto ID do preço de assinatura (se aplicável) Email do cliente Nome do cliente Metadados personalizados URL de redirecionamento de sucesso URL de redirecionamento de cancelamento ID de referência do cliente ID do afiliado ID da transação (preenchido quando `status` = `complete`) Data de expiração (ISO 8601) Data de conclusão (ISO 8601) Data de criação (ISO 8601) ### Exemplo de Resposta (200 OK) ```json theme={null} { "data": { "id": "cs_ABC123xyz", "status": "complete", "url": "https://pay.garu.com.br/pay/session/abc123...", "product_id": 123, "price_id": null, "customer_email": "joao@email.com", "customer_name": "João Silva", "metadata": { "order_id": "12345" }, "success_url": "https://seusite.com/sucesso?session_id={SESSION_ID}", "cancel_url": "https://seusite.com/cancelado", "client_reference_id": "order_abc123", "affiliate_id": null, "transaction_id": 789, "expires_at": "2025-01-20T12:00:00.000Z", "completed_at": "2025-01-19T14:30:00.000Z", "created_at": "2025-01-19T12:00:00.000Z" } } ``` ## Erros Comuns ```json theme={null} { "statusCode": 401, "message": "Unauthorized" } ``` **Solução:** Verifique se o header `Authorization` está correto. ```json theme={null} { "statusCode": 404, "message": "Checkout session not found" } ``` **Solução:** Verifique se o ID da session está correto e pertence ao seu vendedor. ## Próximos Passos Expire manualmente uma session Consulte todas as suas sessions # Expirar Checkout Session Source: https://docs.garu.com.br/api-reference/checkout/expirar POST /api/checkout/sessions/{id}/expire Expire manualmente uma checkout session aberta ## Visão Geral Expira manualmente uma checkout session que ainda está aberta. Útil quando o pedido foi cancelado no seu sistema ou você precisa invalidar a session. Sessions expiram automaticamente após **24 horas** por padrão. Use este endpoint apenas quando precisar expirar antes desse prazo. ## Parâmetros de Path ID da session a ser expirada (ex: `cs_ABC123xyz`) ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST "https://api.garu.com.br/api/checkout/sessions/cs_ABC123xyz/expire" \ -H "Authorization: Bearer sk_test_sua_chave_api" ``` ```javascript JavaScript theme={null} const sessionId = 'cs_ABC123xyz'; const response = await fetch( 'https://api.garu.com.br/api/checkout/sessions/' + sessionId + '/expire', { method: 'POST', headers: { 'Authorization': 'Bearer ' + process.env.GARU_API_KEY } } ); const result = await response.json(); console.log('Session expirada:', result.data.status); // 'expired' ``` ```python Python theme={null} import requests import os session_id = 'cs_ABC123xyz' response = requests.post( 'https://api.garu.com.br/api/checkout/sessions/' + session_id + '/expire', headers={'Authorization': 'Bearer ' + os.environ['GARU_API_KEY']} ) data = response.json()['data'] print('Session expirada:', data['status']) # 'expired' ``` ## Resposta de Sucesso Retorna a session atualizada com `status: "expired"`. ### Exemplo de Resposta (200 OK) ```json theme={null} { "data": { "id": "cs_ABC123xyz", "status": "expired", "url": "https://pay.garu.com.br/pay/session/abc123...", "product_id": 123, "price_id": null, "customer_email": "joao@email.com", "customer_name": "João Silva", "metadata": { "order_id": "12345" }, "success_url": "https://seusite.com/sucesso", "cancel_url": "https://seusite.com/cancelado", "client_reference_id": "order_abc123", "affiliate_id": null, "transaction_id": null, "expires_at": "2025-01-19T15:00:00.000Z", "completed_at": null, "created_at": "2025-01-19T12:00:00.000Z" } } ``` ## Webhook de Expiração Quando uma session é expirada, um webhook é enviado para seu endpoint configurado: ```json theme={null} { "event": "checkout.session.expired", "data": { "id": "cs_ABC123xyz", "status": "expired", "product_id": 123, "metadata": { "order_id": "12345" }, "client_reference_id": "order_abc123", "expires_at": "2025-01-19T15:00:00.000Z" }, "created_at": "2025-01-19T15:00:01.000Z" } ``` ## Erros Comuns ```json theme={null} { "statusCode": 401, "message": "Unauthorized" } ``` **Solução:** Verifique se o header `Authorization` está correto. ```json theme={null} { "statusCode": 404, "message": "Checkout session not found" } ``` **Solução:** Verifique se o ID da session está correto e pertence ao seu vendedor. ```json theme={null} { "statusCode": 409, "message": "Session cannot be expired" } ``` **Solução:** A session já está `complete` ou `expired`. Apenas sessions com status `open` podem ser expiradas. ## Próximos Passos Crie uma nova checkout session Configure notificações de expiração # Listar Checkout Sessions Source: https://docs.garu.com.br/api-reference/checkout/listar GET /api/checkout/sessions Consulte suas checkout sessions com paginação e filtros ## Visão Geral Retorna uma lista paginada de todas as suas checkout sessions, com opção de filtrar por status. ## Parâmetros de Query Número da página Itens por página (máx 100) Filtrar por status: `open`, `complete` ou `expired` ## Exemplo de Requisição ```bash cURL theme={null} curl -X GET "https://api.garu.com.br/api/checkout/sessions?page=1&limit=10&status=complete" \ -H "Authorization: Bearer sk_test_sua_chave_api" ``` ```javascript JavaScript theme={null} const params = new URLSearchParams({ page: '1', limit: '10', status: 'complete' }); const response = await fetch( 'https://api.garu.com.br/api/checkout/sessions?' + params, { headers: { 'Authorization': 'Bearer ' + process.env.GARU_API_KEY } } ); const result = await response.json(); console.log('Total de sessions:', result.total_count); ``` ```python Python theme={null} import requests import os response = requests.get( 'https://api.garu.com.br/api/checkout/sessions', headers={'Authorization': 'Bearer ' + os.environ['GARU_API_KEY']}, params={ 'page': 1, 'limit': 10, 'status': 'complete' } ) result = response.json() print('Total de sessions:', result['total_count']) ``` ## Resposta de Sucesso Lista de checkout sessions Número de sessions na página atual Total de sessions (considerando filtros) Total de páginas ### Exemplo de Resposta (200 OK) ```json theme={null} { "data": [ { "id": "cs_ABC123xyz", "status": "complete", "url": "https://pay.garu.com.br/pay/session/abc123...", "product_id": 123, "price_id": null, "customer_email": "joao@email.com", "customer_name": "João Silva", "metadata": { "order_id": "12345" }, "transaction_id": 789, "expires_at": "2025-01-20T12:00:00.000Z", "completed_at": "2025-01-19T14:30:00.000Z", "created_at": "2025-01-19T12:00:00.000Z" } ], "count": 1, "total_count": 150, "total_pages": 15 } ``` ## Status de Session | Status | Descrição | | - | - | | `open` | Session ativa, aguardando pagamento | | `complete` | Pagamento bem-sucedido, transação criada | | `expired` | Session expirou antes do pagamento | ## Erros Comuns ```json theme={null} { "statusCode": 401, "message": "Unauthorized" } ``` **Solução:** Verifique se o header `Authorization` está correto. ```json theme={null} { "statusCode": 429, "message": "Too Many Requests" } ``` **Solução:** Aguarde antes de fazer novas requisições. Limite: 100 requisições por minuto. ## Próximos Passos Consulte uma session específica Crie novas checkout sessions # Atualizar Cliente Source: https://docs.garu.com.br/api-reference/clientes/atualizar PATCH /api/v1/customers/{uuid} Atualize os dados de um cliente para o seu perfil, sem afetar outros sellers ## Visão Geral Atualiza campos do cliente para o **seu** perfil (`CustomerSellerProfile`). Só os campos enviados mudam — atualização parcial. Outros sellers vinculados ao mesmo cliente global não são afetados: cada um mantém sua própria visão de nome, e-mail e telefone. ## Headers Sua chave de API (`Bearer sk_live_...`) `application/json` ## Path Parameters UUID do cliente ## Request Body Todos os campos são opcionais — envie apenas o que quer mudar. Nome completo E-mail válido Telefone com DDD, 10 ou 11 dígitos CPF (11 dígitos) ou CNPJ (14 dígitos), apenas números `fisica` ou `juridica` CEP com 8 dígitos Logradouro Número do endereço Complemento Bairro Cidade Sigla do estado, 2 letras maiúsculas Para mudar apenas o e-mail de cobrança **sem** alterar o e-mail de contato do cliente, use [`PATCH /api/v1/customers/{uuid}/billing-email-override`](/api-reference/clientes/definir-email-cobranca) em vez deste endpoint. ## Exemplo de Requisição ```bash cURL theme={null} curl -X PATCH https://garu.com.br/api/v1/customers/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ -H "Authorization: Bearer sk_live_sua_chave_api" \ -H "Content-Type: application/json" \ -d '{ "name": "Maria Santos" }' ``` ```javascript JavaScript theme={null} const response = await fetch( `https://garu.com.br/api/v1/customers/${customerUuid}`, { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Maria Santos' }) } ); const customer = await response.json(); ``` ```python Python theme={null} import os import requests response = requests.patch( f"https://garu.com.br/api/v1/customers/{customer_uuid}", headers={ "Authorization": f"Bearer {os.environ['GARU_API_KEY']}", "Content-Type": "application/json", }, json={"name": "Maria Santos"}, ) customer = response.json() ``` ## Resposta de Sucesso (200 OK) ```json theme={null} { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Maria Santos", "email": "maria@exemplo.com.br", "phone": "11987654321", "document": "12345678909", "personType": "fisica", "zipCode": null, "street": null, "number": null, "complement": null, "neighborhood": null, "city": null, "state": null, "billingEmail": "maria@exemplo.com.br", "hasBillingEmailOverride": false, "createdAt": "2026-01-15T10:30:00.000Z", "updatedAt": "2026-01-15T11:00:00.000Z" } ``` ## Erros | Código | Quando acontece | | - | - | | `400` | Dados inválidos | | `401` | Chave de API ausente ou inválida | | `404` | O cliente não existe ou não está vinculado à conta da chave usada | # Registrar Cliente Source: https://docs.garu.com.br/api-reference/clientes/criar POST /api/v1/customers Cadastre um cliente para o seller autenticado, com dedup automática por CPF/CNPJ ## Visão Geral Registra um cliente para a conta autenticada. Se já existir um cliente global com o mesmo `document` (CPF ou CNPJ) — vinculado a outro seller — a Garu **não** cria um registro duplicado: reaproveita o cliente global e cria (ou atualiza) o seu próprio perfil vinculado a ele. **Dedup por documento, não por e-mail.** Dois sellers podem cadastrar o mesmo CPF com e-mails diferentes; cada um enxerga seu próprio perfil (nome, e-mail, telefone) para esse cliente, sem vazar dados do outro seller. ## Headers Sua chave de API (`Bearer sk_live_...`) `application/json` Opcional. Chave única para reenviar a requisição com segurança sem criar um cliente duplicado. A mesma chave devolve o cliente originalmente criado/casado por 24h, por vendedor. ## Request Body Nome completo E-mail válido CPF (11 dígitos) ou CNPJ (14 dígitos), apenas números Telefone com DDD, 10 ou 11 dígitos, apenas números `fisica` ou `juridica` CEP com 8 dígitos, sem hífen Logradouro Número do endereço Complemento Bairro Cidade Sigla do estado, 2 letras maiúsculas (ex: `SP`) ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/customers \ -H "Authorization: Bearer sk_live_sua_chave_api" \ -H "Content-Type: application/json" \ -d '{ "name": "Maria Silva", "email": "maria@exemplo.com.br", "document": "12345678909", "phone": "11987654321", "personType": "fisica" }' ``` ```javascript JavaScript theme={null} const response = await fetch('https://garu.com.br/api/v1/customers', { method: 'POST', headers: { Authorization: `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Maria Silva', email: 'maria@exemplo.com.br', document: '12345678909', phone: '11987654321', personType: 'fisica' }) }); const customer = await response.json(); console.log(customer.uuid); ``` ```python Python theme={null} import os import requests response = requests.post( "https://garu.com.br/api/v1/customers", headers={ "Authorization": f"Bearer {os.environ['GARU_API_KEY']}", "Content-Type": "application/json", }, json={ "name": "Maria Silva", "email": "maria@exemplo.com.br", "document": "12345678909", "phone": "11987654321", "personType": "fisica", }, ) customer = response.json() print(customer["uuid"]) ``` ## Resposta de Sucesso (201 Created) ```json theme={null} { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Maria Silva", "email": "maria@exemplo.com.br", "phone": "11987654321", "document": "12345678909", "personType": "fisica", "zipCode": null, "street": null, "number": null, "complement": null, "neighborhood": null, "city": null, "state": null, "billingEmail": "maria@exemplo.com.br", "hasBillingEmailOverride": false, "createdAt": "2026-01-15T10:30:00.000Z", "updatedAt": "2026-01-15T10:30:00.000Z" } ``` `billingEmail` é o e-mail resolvido para envios de cobrança: `billingEmailOverride` (se definido) → e-mail deste perfil → e-mail global do cliente. Veja [Definir e-mail de cobrança](/api-reference/clientes/definir-email-cobranca). ## Erros | Código | Quando acontece | | - | - | | `400` | Dados inválidos (documento, telefone, e-mail) | | `401` | Chave de API ausente ou inválida | ## Próximos passos * [**Cobranças agendadas**](/guias/cobrancas-agendadas) — agende PIX/Boleto para uma data futura para este cliente. * [**Boleto parcelado**](/guias/carne) — venda em carnê para este cliente. # Definir E-mail de Cobrança Source: https://docs.garu.com.br/api-reference/clientes/definir-email-cobranca PATCH /api/v1/customers/{uuid}/billing-email-override Fixe (ou limpe) o e-mail que recebe cobranças deste cliente, independente do e-mail de contato ## Visão Geral Define um e-mail fixo para onde vão as cobranças **enviadas por você** a este cliente (lembretes de cobrança agendada, boletos de carnê, etc.) — útil quando o cliente quer que isso caia num endereço diferente do e-mail de contato dele (ex: `financeiro@empresa.com.br`). O override é **sticky**: uma vez definido, ele não é sobrescrito automaticamente por um novo pagamento ou re-cadastro. Só muda quando você chama este endpoint de novo. Resolução do e-mail de cobrança: `billingEmailOverride` (se definido) → e-mail deste perfil → e-mail global do cliente. ## Headers Sua chave de API (`Bearer sk_live_...`) `application/json` ## Path Parameters UUID do cliente ## Request Body O e-mail fixo. Envie `null` para limpar o override e voltar a usar o e-mail padrão. ## Exemplo de Requisição ```bash cURL theme={null} # Definir curl -X PATCH https://garu.com.br/api/v1/customers/a1b2c3d4-e5f6-7890-abcd-ef1234567890/billing-email-override \ -H "Authorization: Bearer sk_live_sua_chave_api" \ -H "Content-Type: application/json" \ -d '{ "billingEmailOverride": "financeiro@empresa.com.br" }' # Limpar curl -X PATCH https://garu.com.br/api/v1/customers/a1b2c3d4-e5f6-7890-abcd-ef1234567890/billing-email-override \ -H "Authorization: Bearer sk_live_sua_chave_api" \ -H "Content-Type: application/json" \ -d '{ "billingEmailOverride": null }' ``` ```javascript JavaScript theme={null} await fetch( `https://garu.com.br/api/v1/customers/${customerUuid}/billing-email-override`, { method: 'PATCH', headers: { Authorization: `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ billingEmailOverride: 'financeiro@empresa.com.br' }) } ); ``` ```python Python theme={null} import os import requests requests.patch( f"https://garu.com.br/api/v1/customers/{customer_uuid}/billing-email-override", headers={ "Authorization": f"Bearer {os.environ['GARU_API_KEY']}", "Content-Type": "application/json", }, json={"billingEmailOverride": "financeiro@empresa.com.br"}, ) ``` ## Resposta de Sucesso (200 OK) ```json theme={null} { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Maria Silva", "email": "maria@exemplo.com.br", "billingEmail": "financeiro@empresa.com.br", "hasBillingEmailOverride": true, "createdAt": "2026-01-15T10:30:00.000Z", "updatedAt": "2026-01-15T11:15:00.000Z" } ``` ## Erros | Código | Quando acontece | | - | - | | `400` | `billingEmailOverride` não é `null` nem um e-mail válido | | `401` | Chave de API ausente ou inválida | | `404` | O cliente não existe ou não está vinculado à conta da chave usada | # Consultar Cliente Source: https://docs.garu.com.br/api-reference/clientes/detalhes GET /api/v1/customers/{uuid} Recupere um cliente pelo uuid ## Visão Geral Retorna um cliente vinculado à conta autenticada pelo `uuid`. ## Headers Sua chave de API (`Bearer sk_live_...`) ## Path Parameters UUID do cliente, devolvido no cadastro ## Exemplo de Requisição ```bash cURL theme={null} curl https://garu.com.br/api/v1/customers/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ -H "Authorization: Bearer sk_live_sua_chave_api" ``` ```javascript JavaScript theme={null} const response = await fetch( `https://garu.com.br/api/v1/customers/${customerUuid}`, { headers: { Authorization: `Bearer ${process.env.GARU_API_KEY}` } } ); const customer = await response.json(); console.log(customer.billingEmail); ``` ```python Python theme={null} import os import requests response = requests.get( f"https://garu.com.br/api/v1/customers/{customer_uuid}", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, ) print(response.json()["billingEmail"]) ``` ## Resposta de Sucesso (200 OK) ```json theme={null} { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Maria Silva", "email": "maria@exemplo.com.br", "phone": "11987654321", "document": "12345678909", "personType": "fisica", "zipCode": null, "street": null, "number": null, "complement": null, "neighborhood": null, "city": null, "state": null, "billingEmail": "maria@exemplo.com.br", "hasBillingEmailOverride": false, "createdAt": "2026-01-15T10:30:00.000Z", "updatedAt": "2026-01-15T10:30:00.000Z" } ``` ## Erros | Código | Quando acontece | | - | - | | `401` | Chave de API ausente ou inválida | | `404` | O cliente não existe ou não está vinculado à conta da chave usada | # Remover Cliente Source: https://docs.garu.com.br/api-reference/clientes/excluir DELETE /api/v1/customers/{uuid} Desvincule um cliente da sua conta ## Visão Geral Desvincula o cliente da conta autenticada — remove **apenas o seu perfil** (`CustomerSellerProfile`). O cliente global e os perfis de outros sellers vinculados a ele não são afetados. Isto não apaga o histórico de transações do cliente com você — apenas o vínculo. Cobranças e carnês já existentes continuam no seu histórico. ## Headers Sua chave de API (`Bearer sk_live_...`) ## Path Parameters UUID do cliente ## Exemplo de Requisição ```bash cURL theme={null} curl -X DELETE https://garu.com.br/api/v1/customers/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ -H "Authorization: Bearer sk_live_sua_chave_api" ``` ```javascript JavaScript theme={null} await fetch(`https://garu.com.br/api/v1/customers/${customerUuid}`, { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.GARU_API_KEY}` } }); ``` ```python Python theme={null} import os import requests requests.delete( f"https://garu.com.br/api/v1/customers/{customer_uuid}", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, ) ``` ## Resposta de Sucesso (200 OK) ```json theme={null} { "removed": true } ``` ## Erros | Código | Quando acontece | | - | - | | `401` | Chave de API ausente ou inválida | | `404` | O cliente não existe ou não está vinculado à conta da chave usada | # Listar Clientes Source: https://docs.garu.com.br/api-reference/clientes/listar GET /api/v1/customers Liste os clientes da sua conta, com busca e filtro por status de inadimplência ## Visão Geral Lista os clientes vinculados à conta autenticada, do mais recente para o mais antigo. ## Headers Sua chave de API (`Bearer sk_live_...`) ## Query Parameters Página desejada Itens por página, máximo 100 Busca por nome, e-mail ou documento `overdue` retorna apenas clientes com pelo menos uma cobrança agendada (avulsa, recorrente ou carnê) em atraso ## Exemplo de Requisição ```bash cURL theme={null} curl -G https://garu.com.br/api/v1/customers \ -H "Authorization: Bearer sk_live_sua_chave_api" \ -d search=maria \ -d limit=50 ``` ```javascript JavaScript theme={null} const params = new URLSearchParams({ search: 'maria', limit: '50' }); const response = await fetch(`https://garu.com.br/api/v1/customers?${params}`, { headers: { Authorization: `Bearer ${process.env.GARU_API_KEY}` } }); const { data, totalCount } = await response.json(); console.log(`${data.length} de ${totalCount} clientes`); ``` ```python Python theme={null} import os import requests response = requests.get( "https://garu.com.br/api/v1/customers", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, params={"search": "maria", "limit": 50}, ) body = response.json() print(f"{len(body['data'])} de {body['totalCount']} clientes") ``` ## Resposta de Sucesso (200 OK) ```json theme={null} { "data": [ { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Maria Silva", "email": "maria@exemplo.com.br", "phone": "11987654321", "document": "12345678909", "personType": "fisica", "zipCode": null, "street": null, "number": null, "complement": null, "neighborhood": null, "city": null, "state": null, "billingEmail": "maria@exemplo.com.br", "hasBillingEmailOverride": false, "createdAt": "2026-01-15T10:30:00.000Z", "updatedAt": "2026-01-15T10:30:00.000Z" } ], "count": 1, "totalCount": 42, "totalPages": 3 } ``` | Campo | Significado | | - | - | | `count` | Itens nesta página | | `totalCount` | Total de clientes que casam com os filtros | | `totalPages` | Número de páginas com o `limit` informado | ## Erros | Código | Quando acontece | | - | - | | `401` | Chave de API ausente ou inválida | # Adiar Cobrança Source: https://docs.garu.com.br/api-reference/cobrancas-agendadas/adiar POST /api/v1/scheduled-charges/{id}/postpone Empurre uma cobrança para uma nova data de vencimento ## Visão Geral Move a cobrança para uma nova data e limpa qualquer lembrete pendente, para que o cliente receba um e-mail novo no `dueDate` atualizado. **Permitido a partir de:** `scheduled` / `due_today` / `overdue` / `paused`. ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/scheduled-charges/sch_abc123/postpone \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "newDueDate": "2026-07-01", "reason": "cliente pediu mais prazo" }' ``` ```javascript JavaScript theme={null} import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY }); await garu.scheduledCharges.postpone('sch_abc123', { newDueDate: '2026-07-01', reason: 'cliente pediu mais prazo' }); ``` ```python Python theme={null} import requests import os requests.post( "https://garu.com.br/api/v1/scheduled-charges/sch_abc123/postpone", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, json={"newDueDate": "2026-07-01", "reason": "cliente pediu mais prazo"} ) ``` ## Parâmetros ID da cobrança (`sch_…`). Nova data em `YYYY-MM-DD`, fuso de São Paulo. Deve ser hoje ou futura. Texto livre persistido no log de auditoria. ## Resposta A cobrança atualizada (mesmo formato do POST de criação), agora com o novo `dueDate` e `status` ajustado. Um evento `postponed` é apendado à linha do tempo e o webhook `scheduled_charge.postponed` é disparado. # Cobrar Agora Source: https://docs.garu.com.br/api-reference/cobrancas-agendadas/cobrar-agora POST /api/v1/scheduled-charges/{id}/charge-now Dispare a cobrança e o e-mail ao cliente na hora, sem esperar o vencimento ## Visão Geral Dispara a cobrança e a notificação ao cliente **imediatamente** — o mesmo fluxo que o cron diário rodaria no dia do vencimento, só que antecipado. Use quando você quer cobrar antes do `dueDate`: o cliente já confirmou, você fechou o mês mais cedo, ou está recuperando manualmente uma série que ficou para trás. Não tem corpo de requisição. A cobrança é identificada pelo `id` no path. **Permitido a partir de:** `scheduled` / `due_today`. Exige chave de API (`sk_test_…` / `sk_live_…`) e só enxerga cobranças do próprio seller. **Esta ação é idempotente por ciclo.** Se o ciclo atual da cobrança já foi disparado (pelo cron ou por uma chamada anterior a `charge-now`), a Garu **não cobra de novo** — retorna `201` com `outcome: "already_sent"`. Pode chamar à vontade sem risco de cobrança dupla. ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/scheduled-charges/sch_abc123/charge-now \ -H "Authorization: Bearer sk_test_sua_chave" ``` ```javascript Node SDK theme={null} import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY }); const result = await garu.scheduledCharges.chargeNow('sch_abc123'); console.log(result.outcome); // 'dispatched' | 'already_sent' | 'not_sent' | 'failed' console.log(result.cycleNumber); // 1, 2, ... ou null console.log(result.message); // texto pt-BR pronto para exibir ``` ```bash CLI theme={null} garu scheduled-charges charge-now sch_abc123 ``` Um SDK Flutter com `chargeNow` será documentado aqui assim que essa biblioteca for publicada. ## Parâmetros de Path ID da cobrança (`sch_…`). Deve pertencer ao seller autenticado. Sem corpo de requisição. ## Resposta Sempre `201` quando a cobrança é válida e cobrável (padrão do NestJS para `POST` sem override). O resultado real fica no campo `outcome` — ele indica se a Garu disparou, ignorou (já enviado) ou falhou. **Trate `outcome`, não o status HTTP**, para saber o que aconteceu. ```json theme={null} { "outcome": "dispatched", "cycleNumber": 1, "message": "Cobrança disparada. O cliente já recebeu o e-mail de pagamento." } ``` O texto de `message` neste e nos demais exemplos é **ilustrativo** — o valor exato é gerado pelo servidor e pode mudar. Faça sua lógica em cima de `outcome` e `reason`; use `message` apenas para exibir ao usuário. ### Campos | Campo | Tipo | Descrição | | - | - | - | | `outcome` | string | Resultado do disparo. Um de `dispatched`, `already_sent`, `not_sent`, `failed`. | | `cycleNumber` | number \| null | Número do ciclo cobrado. `null` quando não há ciclo aberto a reportar. | | `reason` | string | Presente em `not_sent` e `failed`; código curto e estável que explica o motivo. Ausente em `dispatched` e `already_sent`. | | `message` | string | Texto pt-BR pronto para exibir ao usuário, já adequado ao `outcome`/`reason`. | ### Valores de `outcome` | `outcome` | O que significa | Você precisa agir? | | - | - | - | | `dispatched` | A cobrança foi disparada agora e o e-mail saiu para o cliente. | Não — fluxo normal. | | `already_sent` | O ciclo atual já tinha sido disparado; **nada foi cobrado de novo** (idempotência). | Não — é o comportamento esperado em re-chamadas. | | `not_sent` | A Garu não chegou a disparar por uma condição de pré-envio (ver `reason`). | Sim — resolva o `reason` e tente de novo. | | `failed` | O disparo foi tentado mas falhou (cartão/cobrança recusada — ver `reason`). | Sim — corrija a causa (ex.: pedir novo cartão). | ### Valores de `reason` Para `not_sent`: | `reason` | Significado da `message` | | - | - | | `no_email` | O cliente não tem e-mail de cobrança cadastrado, então não há para quem enviar o link. | | `no_saved_payment_method` | Não existe método de pagamento salvo para cobrar este ciclo (cobrança recorrente sem cartão tokenizado). | | `lock_lost` | Outro processo (provavelmente o próprio cron) estava cobrando este ciclo no mesmo instante; a Garu evitou a corrida. Tente de novo em alguns segundos. | Para `failed`: | `reason` | Significado da `message` | | - | - | | `card_expired` | O cartão salvo do cliente expirou. Peça um novo método de pagamento. | | `payment_method_missing` | O método de pagamento que seria usado não está mais disponível. | | `customer_missing` | O cliente vinculado à cobrança não foi encontrado. | | `` | A operadora recusou a cobrança; o `reason` carrega o código canônico de falha (ex.: `insufficient_funds`). Veja [Códigos de falha](/api-reference/webhooks/codigos-de-falha). | O campo `message` já vem com o texto pt-BR correspondente a cada combinação de `outcome` + `reason`, pronto para exibir direto na sua interface. ## Erros | Status | Caso | | - | - | | 400 | A cobrança não está em status cobrável (`scheduled` ou `due_today`) — ex.: já está `paid`, `paused` ou `canceled`. Também `400` se for uma recorrência sem ciclo aberto para disparar. | | 404 | A cobrança não existe ou não pertence ao seller que está chamando. | `400` e `404` são erros de requisição — a cobrança não pôde sequer ser avaliada. Já um disparo que **roda mas não cobra** (cartão recusado, sem e-mail) volta como `201` com `outcome: "failed"` ou `"not_sent"`. Não confunda os dois: trate o HTTP para erros de chamada e o `outcome` para o resultado do disparo. ## Como se relaciona com o cron diário `charge-now` roda a mesma rotina de cobrança que o cron dispara no `dueDate`, compartilhando o lock por ciclo. Por isso o disparo manual e o automático não se atropelam: * Se o cron já disparou o ciclo hoje, `charge-now` devolve `already_sent`. * Se você disparar com `charge-now`, o cron não dispara de novo aquele ciclo. `maxRecoveryDays` (definido na [criação](/api-reference/cobrancas-agendadas/criar)) é um mecanismo **separado**: ele controla por quantos dias o cron tenta recuperar automaticamente um ciclo cujo disparo tenha falhado. `charge-now` não altera essa janela — é o gatilho manual para disparar o ciclo na hora. ## Próximos passos Inclui o campo `maxRecoveryDays` para recuperação automática de ciclos perdidos Audite cada tentativa por ciclo, com o `failureCode` canônico de cada recusa # Criar Cobrança Agendada Source: https://docs.garu.com.br/api-reference/cobrancas-agendadas/criar POST /api/v1/scheduled-charges Agende uma cobrança PIX ou Boleto para um cliente cadastrado ## Visão Geral Cria uma cobrança agendada — avulsa (`one_time`) ou recorrente (`recurring`) — para uma data futura. A Garu envia o e-mail ao cliente no dia do vencimento e dispara webhooks no ciclo de vida. Para recorrência debitada automaticamente, combine `type: "recurring"` com `methods: ["pix_automatic"]` (veja [Pix Automático](/guias/pix-automatico)). Cadastre o cliente primeiro via dashboard ou API — veja [Cadastro de clientes](/guias/clientes). O `customerId` é o ID numérico interno do cliente (não o `uuid` público de `/api/v1/customers`) — as duas APIs ainda não compartilham um identificador cruzado. ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/scheduled-charges \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -H "X-Idempotency-Key: 6c4c2a1e-4c2b-4f1c-9a8d-1b2e3f4a5b6c" \ -d '{ "customerId": 42, "amount": 297.50, "type": "one_time", "dueDate": "2026-06-15", "methods": ["pix", "boleto"], "description": "Mensalidade Junho" }' ``` ```javascript JavaScript theme={null} import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY }); const charge = await garu.scheduledCharges.create({ customerId: 42, amount: 297.5, // em Reais; mínimo R$ 5,00 type: 'one_time', dueDate: '2026-06-15', methods: ['pix', 'boleto'], description: 'Mensalidade Junho', idempotencyKey: 'cliente-42-mensalidade-2026-06' }); console.log(charge.id); // sch_abc123 ``` ```python Python theme={null} import requests import os response = requests.post( "https://garu.com.br/api/v1/scheduled-charges", headers={ "Authorization": f"Bearer {os.environ['GARU_API_KEY']}", "X-Idempotency-Key": "cliente-42-mensalidade-2026-06" }, json={ "customerId": 42, "amount": 297.50, "type": "one_time", "dueDate": "2026-06-15", "methods": ["pix", "boleto"], "description": "Mensalidade Junho" } ) print(response.json()["id"]) # sch_abc123 ``` ### Recorrente com Pix Automático ```bash theme={null} curl -X POST https://garu.com.br/api/v1/scheduled-charges \ -H "Authorization: Bearer sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "customerId": 42, "productId": 456, "amount": 297.50, "type": "recurring", "dueDate": "2026-06-15", "methods": ["pix_automatic"], "recurrence": { "interval": "monthly", "intervalCount": 1, "endsAfter": 12 }, "description": "Plano Premium - mensal" }' ``` ## Parâmetros ID do cliente no Garu (já cadastrado neste seller). Valor em BRL decimal (ex: `297.50`). **Não use centavos.** Mínimo de **R\$ 5,00** (`5.00`), o valor mínimo da plataforma por cobrança, na cobrança avulsa e na recorrente. Abaixo disso a API responde `400`. Veja [Erros Comuns](#erros-comuns). Tipo da cobrança: `one_time` (avulsa) ou `recurring` (recorrente). `pix_automatic` exige `recurring`. Data de vencimento em `YYYY-MM-DD`, fuso de São Paulo. Deve ser hoje ou futura. Métodos oferecidos ao cliente. Aceita `["pix"]`, `["boleto"]`, `["pix", "boleto"]` ou `["pix_automatic"]`. Usar `pix_automatic` exige `type: "recurring"` **e** um `productId` cujo produto tenha `pixAutomatic: true`. ID de um produto opcional. Quando informado, aparece vinculado à cobrança no dashboard. **Obrigatório** quando `methods` inclui `pix_automatic`. Configuração da recorrência (use com `type: "recurring"`). Frequência: `weekly`, `biweekly`, `monthly`, `bimonthly`, `quarterly`, `biannual` ou `yearly`. Multiplicador do intervalo (ex: `interval: "monthly"` + `intervalCount: 2` = a cada 2 meses). Encerra após este número de ciclos. Opcional. Data final da recorrência em `YYYY-MM-DD`. Opcional. Dias de teste gratuito antes do primeiro ciclo (`1`–`365`). Apenas para `type: "recurring"`. Texto livre exibido no e-mail do cliente e na página de pagamento. Até 500 caracteres. Janela, em dias (inteiro de `1` a `365`), para a recuperação automática de uma cobrança que o cron diário tenha perdido — se o disparo do `dueDate` falhar, a Garu segue tentando dentro dessa janela. Omitir usa o padrão do sistema (`14`). Também aparece no objeto retornado. Identificador interno seu (até 255 caracteres). Útil para reconciliação. JSON livre. Persistido como JSONB; não interpretado pela Garu. ## Idempotência Envie o header `X-Idempotency-Key`. A mesma chave devolve a série originalmente criada por 24h, em vez de agendar uma cobrança duplicada, então um retry após timeout de rede fica seguro. Derive a chave de algo estável no seu sistema, como o id da assinatura e o mês. Uma chave nova a cada tentativa não protege nada. O SDK Node só envia o header quando você passa `idempotencyKey`: desde a versão 5.0.0 ele não gera uma chave sozinho. ## Resposta ```json theme={null} { "id": "sch_abc123", "sellerId": 10, "customerId": 42, "productId": null, "amount": 297.5, "description": "Mensalidade Junho", "type": "one_time", "dueDate": "2026-06-15", "methods": ["pix", "boleto"], "recurrence": null, "status": "scheduled", "subscriptionId": null, "trialEndsAt": null, "cancelAtPeriodEnd": false, "maxRecoveryDays": 14, "externalReference": null, "metadata": null, "createdAt": "2026-05-01T12:00:00Z", "updatedAt": "2026-05-01T12:00:00Z" } ``` ## Erros Comuns ```json theme={null} { "statusCode": 400, "timestamp": "2026-09-30T23:00:00.000Z", "path": "/api/v1/scheduled-charges", "message": "O valor mínimo de uma cobrança é R$ 5,00." } ``` **Solução:** Envie um `amount` de pelo menos `5.00`. A regra vale para a cobrança avulsa e para a recorrente. As cobranças agendadas criadas antes dessa regra mantêm o valor e continuam sendo cobradas normalmente. # Detalhes da Cobrança Agendada Source: https://docs.garu.com.br/api-reference/cobrancas-agendadas/detalhes GET /api/v1/scheduled-charges/{id} Busque uma cobrança junto com sua linha do tempo e transações geradas ## Visão Geral Retorna a cobrança em pacote com: * a linha do tempo de eventos (`events`, ordem cronológica), * as transações Garu geradas a partir da cobrança (`transactions`). É a chamada que alimenta a página de detalhes do dashboard. ## Exemplo de Requisição ```bash cURL theme={null} curl -X GET https://garu.com.br/api/v1/scheduled-charges/sch_abc123 \ -H "Authorization: Bearer sk_test_sua_chave" ``` ```javascript JavaScript theme={null} import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY }); const { charge, events, transactions } = await garu.scheduledCharges.get('sch_abc123'); console.log(charge.status); events.forEach((e) => console.log(e.eventType, e.createdAt)); ``` ```python Python theme={null} import requests import os response = requests.get( "https://garu.com.br/api/v1/scheduled-charges/sch_abc123", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"} ) detail = response.json() print(detail["charge"]["status"]) ``` ## Parâmetros de Path ID da cobrança (`sch_…`). ## Resposta ```json theme={null} { "charge": { "id": "sch_abc123", "customerId": 42, "amount": 297.5, "type": "one_time", "dueDate": "2026-06-15", "methods": ["pix", "boleto"], "recurrence": null, "status": "paid", "subscriptionId": null, "trialEndsAt": null, "cancelAtPeriodEnd": false, "customer": { "id": 42, "name": "Maria Silva", "email": "maria@exemplo.com.br", "document": "12345678901" }, "product": null, "createdAt": "2026-05-01T12:00:00Z", "updatedAt": "2026-06-15T13:42:00Z" }, "events": [ { "id": 1, "scheduledChargeId": "sch_abc123", "eventType": "created", "actor": { "type": "user", "id": 1 }, "payload": { "amount": 297.5 }, "createdAt": "2026-05-01T12:00:00Z" }, { "id": 2, "scheduledChargeId": "sch_abc123", "eventType": "d_day_reminder_sent", "actor": { "type": "system" }, "payload": null, "createdAt": "2026-06-15T11:00:00Z" }, { "id": 3, "scheduledChargeId": "sch_abc123", "eventType": "paid", "actor": { "type": "system" }, "payload": { "transactionId": 4472 }, "createdAt": "2026-06-15T13:42:00Z" } ], "transactions": [ { "id": 4472, "value": 297.5, "paymentMethod": "pix", "status": "payedPix", "date": "2026-06-15T11:01:00Z", "refundedAt": null } ] } ``` `charge.amount` e `transactions[].value` estão ambos em BRL decimal (`297.50`) — nunca centavos. Nenhuma conversão é necessária entre os dois. ### Tipos de evento | `eventType` | Quando | | - | - | | `created` | Cobrança agendada | | `postponed` | Data alterada | | `paused` / `resumed` | Pausa / retomada manual | | `manually_marked_paid` | Marcada como paga (pagamento off-Garu) | | `paid` | Pagamento confirmado pela Garu | | `d_day_reminder_sent` | E-mail enviado ao cliente no vencimento | | `overdue_reminder_sent` | Lembrete enviado ao time financeiro (D+1, D+2 ou D+3) | `actor.type` é `user` (com `id` numérico do usuário), `api_key` (com `id` da chave) ou `system` para ações automáticas dos crons. # Listar Cobranças Agendadas Source: https://docs.garu.com.br/api-reference/cobrancas-agendadas/listar GET /api/v1/scheduled-charges Liste e filtre cobranças agendadas com paginação e busca por cliente ## Visão Geral Retorna uma lista paginada das cobranças agendadas do seller autenticado. Use os filtros para construir as abas do dashboard (Próximas / Vencidas / Pausadas / Concluídas). ## Exemplo de Requisição ```bash cURL theme={null} # Próximas (scheduled + due_today) curl -X GET "https://garu.com.br/api/v1/scheduled-charges?status=scheduled&status=due_today" \ -H "Authorization: Bearer sk_test_sua_chave" # Em atraso de um cliente específico curl -X GET "https://garu.com.br/api/v1/scheduled-charges?status=overdue&customerId=42" \ -H "Authorization: Bearer sk_test_sua_chave" ``` ```javascript JavaScript theme={null} import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY }); // Múltiplos status como array const upcoming = await garu.scheduledCharges.list({ status: ['scheduled', 'due_today'], limit: 50 }); // Em atraso de um cliente const overdueForCustomer = await garu.scheduledCharges.list({ status: 'overdue', customerId: 42 }); ``` ```python Python theme={null} import requests import os response = requests.get( "https://garu.com.br/api/v1/scheduled-charges", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, params=[("status", "scheduled"), ("status", "due_today"), ("limit", 50)] ) for charge in response.json()["data"]: print(charge["id"], charge["dueDate"], charge["status"]) ``` ## Parâmetros de Query Filtra por status. Pode ser repetido (`?status=scheduled&status=due_today`). Valores: `scheduled`, `due_today`, `overdue`, `paid`, `paused`, `canceled`, `trial`, `pending_tokenization`, `recurrence_canceled`. Restringe a um cliente específico. Filtra por tipo: `one_time` (avulsa) ou `recurring` (recorrente). Limite inferior de `dueDate` (`YYYY-MM-DD`). Limite superior de `dueDate` (`YYYY-MM-DD`). Busca livre no nome / e-mail / CPF do cliente vinculado à cobrança. Página da paginação. Itens por página (máx. 100). ## Resposta ```json theme={null} { "data": [ { "id": "sch_abc123", "customerId": 42, "amount": 297.5, "dueDate": "2026-06-15", "methods": ["pix", "boleto"], "status": "scheduled", "customer": { "id": 42, "name": "Maria Silva", "email": "maria@exemplo.com.br", "document": "12345678901" }, "product": null, "createdAt": "2026-05-01T12:00:00Z" } ], "count": 1, "totalCount": 1, "totalPages": 1 } ``` Os campos `customer` e `product` vêm carregados (com os dados essenciais para listagem) para evitar uma segunda chamada por cobrança. # Marcar como Paga Source: https://docs.garu.com.br/api-reference/cobrancas-agendadas/marcar-como-paga POST /api/v1/scheduled-charges/{id}/mark-paid Registre um pagamento feito fora do Garu (transferência, dinheiro, etc.) ## Visão Geral Use quando o cliente pagou por um canal externo (TED, dinheiro, outro PSP). A cobrança vai para `paid` sem gerar uma transação no gateway, e os lembretes param. * **Avulsa (`one_time`):** omita `cycleNumber`. Permitido a partir de `due_today` / `overdue`. * **Recorrente (`recurring`):** informe `cycleNumber`. Permitido quando o ciclo está em `due_today` / `overdue` / `failed`; os ciclos futuros continuam normalmente. ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/scheduled-charges/sch_abc123/mark-paid \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "paymentDate": "2026-06-20", "externalReference": "TED 4472881" }' ``` ```javascript JavaScript theme={null} import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY }); await garu.scheduledCharges.markPaid('sch_abc123', { paymentDate: '2026-06-20', externalReference: 'TED 4472881' }); ``` ```python Python theme={null} import requests import os requests.post( "https://garu.com.br/api/v1/scheduled-charges/sch_abc123/mark-paid", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, json={ "paymentDate": "2026-06-20", "externalReference": "TED 4472881" } ) ``` ## Parâmetros ID da cobrança (`sch_…`). Data do pagamento em `YYYY-MM-DD`, fuso de São Paulo. Deve ser hoje ou passada. Referência bancária, ID interno ou qualquer string estável para reconciliação. Até 255 caracteres. Ciclo a marcar como pago. **Obrigatório** para cobranças recorrentes; omitido para `one_time`. ## Resposta A cobrança atualizada com `status: "paid"` (avulsa) ou com o ciclo informado atualizado (recorrente — o objeto retornado é sempre a cobrança pai, não o ciclo). Um evento `manually_marked_paid` é apendado e o webhook `scheduled_charge.paid` é disparado. Diferente de `paid` automático, esta ação **não** gera uma `Transaction` no Garu — o pagamento aconteceu fora do gateway. O array `transactions` em [`GET /:id`](/api-reference/cobrancas-agendadas/detalhes) continua vazio. # Pausar Cobrança Source: https://docs.garu.com.br/api-reference/cobrancas-agendadas/pausar POST /api/v1/scheduled-charges/{id}/pause Coloque uma cobrança em espera; nenhum lembrete é enviado enquanto pausada ## Visão Geral Pausa a cobrança. Nenhum e-mail (cliente ou time) é enviado até você retomá-la com [`/resume`](/api-reference/cobrancas-agendadas/retomar). **Permitido a partir de:** `scheduled` / `due_today` / `overdue`. ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/scheduled-charges/sch_abc123/pause \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "reason": "em negociação" }' ``` ```javascript JavaScript theme={null} import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY }); await garu.scheduledCharges.pause('sch_abc123', { reason: 'em negociação' }); ``` ```python Python theme={null} import requests import os requests.post( "https://garu.com.br/api/v1/scheduled-charges/sch_abc123/pause", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, json={"reason": "em negociação"} ) ``` ## Parâmetros ID da cobrança (`sch_…`). Texto livre persistido no log de auditoria. ## Resposta A cobrança atualizada com `status: "paused"`. Um evento `paused` é apendado e o webhook `scheduled_charge.paused` é disparado. # Retomar Cobrança Source: https://docs.garu.com.br/api-reference/cobrancas-agendadas/retomar POST /api/v1/scheduled-charges/{id}/resume Reative uma cobrança pausada ## Visão Geral Retoma uma cobrança pausada — volta para `scheduled` e os lembretes voltam a disparar normalmente na data de vencimento. **Permitido a partir de:** `paused`. ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/scheduled-charges/sch_abc123/resume \ -H "Authorization: Bearer sk_test_sua_chave" ``` ```javascript JavaScript theme={null} import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY }); await garu.scheduledCharges.resume('sch_abc123'); ``` ```python Python theme={null} import requests import os requests.post( "https://garu.com.br/api/v1/scheduled-charges/sch_abc123/resume", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"} ) ``` ## Parâmetros ID da cobrança (`sch_…`). Sem corpo de requisição. ## Resposta A cobrança atualizada com `status: "scheduled"`. Um evento `resumed` é apendado e o webhook `scheduled_charge.resumed` é disparado. # Listar tentativas de cobrança Source: https://docs.garu.com.br/api-reference/cobrancas-agendadas/tentativas GET /api/v1/scheduled-charges/{id}/attempts Log per-attempt para auditoria de billing recorrente Disponível a partir da **v0.8.2**. Retorna **uma linha por tentativa lógica de cobrança** em todos os ciclos da série — ciclo 1 interativo, cada cobrança silenciosa, cada retry, cada `mark-paid` manual. Cada linha carrega o `failureCode` canônico para recusas, possibilitando auditar por que uma série recorrente caiu em atraso. ## Quando usar * Debug de cliente reclamando "minha cobrança falhou e não sei por quê" * Dashboard interno mostrando histórico de tentativas * Auditoria de SLA: quantas tentativas a Garu fez antes de marcar como `overdue` ## Query params | Param | Tipo | Default | | - | - | - | | `cycleNumber` | int | (todos os ciclos) | | `page` | int | 1 | | `limit` | int (1..100) | 20 | ## Resposta ```json theme={null} { "data": [ { "id": 1234, "cycleId": "scc_01HG...", "cycleNumber": 3, "attemptNumber": 4, "attemptedAt": "2026-05-04T19:00:00Z", "source": "card_retry", "paymentMethod": "card", "paymentMethodId": 99, "cardLast4": "4242", "cardBrand": "visa", "status": "declined", "failureCode": "insufficient_funds", "failureReason": "Saldo insuficiente", "gatewayFailureCode": "51", "gatewayChargeId": 887766, "transactionId": 5544 } ], "count": 1, "totalCount": 5, "totalPages": 1 } ``` ## Origem da tentativa (`source`) | Valor | Descrição | | - | - | | `cycle1_interactive` | Cliente pagou no link público do ciclo 1 (tokenização inicial) | | `silent_charge` | Cron de billing cobrou silenciosamente o cartão salvo (ciclo N≥2) | | `card_retry` | Cron de retry chamou o endpoint nativo de retry do gateway | | `manual_mark_paid` | Seller marcou ciclo como pago manualmente | | `fallback_pix` | (reservado; ainda não emitido) | ## Status | Valor | Descrição | | - | - | | `pending` | Requisição feita, aguardando webhook do gateway | | `succeeded` | Convertido em transação paga | | `declined` | Negado pelo emissor (ver `failureCode`) | | `canceled` | Cancelado antes de completar | | `errored` | Erro técnico — distinto de `declined` (rede, 5xx, etc.) | O snapshot de `cardLast4` / `cardBrand` é capturado no momento da tentativa e **sobrevive** à exclusão do PaymentMethod (LGPD removal). `paymentMethodId` pode ficar `null` se a row foi deletada, mas você ainda vê o final do cartão. ## Exemplo Auditoria de uma série em atraso: ```bash theme={null} # Quantas vezes tentamos cobrar o ciclo 3? curl https://garu.com.br/api/v1/scheduled-charges/sch_abc/attempts?cycleNumber=3 \ -H "Authorization: Bearer $GARU_API_KEY" ``` ```typescript theme={null} // Pegando todas as recusas com falha distinta import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY! }); const { data } = await garu.scheduledCharges.listAttempts('sch_abc'); const declines = data.filter((a) => a.status === 'declined'); const codes = new Set(declines.map((a) => a.failureCode)); // → Set { 'insufficient_funds', 'issuer_unavailable' } ``` ## SDK ```typescript theme={null} const result = await garu.scheduledCharges.listAttempts('sch_abc', { cycleNumber: 3, page: 1, limit: 50, }); ``` ## MCP Ferramenta `list_scheduled_charge_attempts` — agentes podem inspecionar histórico de cobrança como parte de fluxos de suporte. # Cancelar Cobrança Source: https://docs.garu.com.br/api-reference/cobrancas/cancelar DELETE /api/v1/charges/{uuid} Cancele uma cobrança que ainda não foi paga ## Visão Geral Cancela uma cobrança **ainda não paga** — um PIX que expirou no seu fluxo, um boleto que não faz mais sentido, um pedido abandonado. O código PIX e o boleto deixam de ser pagáveis. Para uma cobrança **já paga**, o caminho é o [estorno](/api-reference/cobrancas/reembolsar), não o cancelamento. ## Headers Sua chave de API (`Bearer sk_live_...`) ## Path Parameters UUID da cobrança ## Exemplo de Requisição ```bash cURL theme={null} curl -X DELETE https://garu.com.br/api/v1/charges/6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f \ -H "Authorization: Bearer sk_live_sua_chave_api" ``` ```javascript JavaScript theme={null} const response = await fetch( `https://garu.com.br/api/v1/charges/${chargeUuid}`, { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.GARU_API_KEY}` } } ); const { canceled } = await response.json(); ``` ```python Python theme={null} import os import requests response = requests.delete( f"https://garu.com.br/api/v1/charges/{charge_uuid}", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, ) print(response.json()["canceled"]) ``` ## Resposta de Sucesso (200 OK) ```json theme={null} { "canceled": true } ``` ## Erros | Código | Quando acontece | | - | - | | `401` | Chave de API ausente ou inválida | | `404` | A cobrança não existe ou não pertence à conta da chave usada | # Criar Cobrança Source: https://docs.garu.com.br/api-reference/cobrancas/criar POST /api/v1/charges Crie uma cobrança PIX, boleto ou cartão e receba os dados para exibir o pagamento no seu próprio checkout ## Visão Geral Cria uma cobrança e devolve tudo que você precisa para exibir o pagamento **dentro da sua própria interface** — sem redirecionar o cliente. É a base do [checkout transparente](/guias/checkout-transparente). Se você não precisa desse controle, o caminho mais simples continua sendo o [link de pagamento](/api-reference/produtos/link-pagamento) ou a [Checkout Session](/api-reference/checkout/criar), onde a Garu hospeda a página de pagamento. A cobrança é sempre feita **em cima de um produto**. Crie o produto uma vez com [`POST /api/v1/products`](/api-reference/produtos/criar) e use o `uuid` dele aqui. ## Headers Sua chave de API (`Bearer sk_live_...`) `application/json` Chave única por cobrança. Reenviar a mesma requisição com a mesma chave devolve a cobrança já criada em vez de duplicar. Válida por 24h. ## Request Body UUID do produto a ser cobrado Método de pagamento: `pix`, `boleto` ou `creditCard` Dados de quem está pagando Nome completo (3 a 255 caracteres) E-mail válido CPF (11 dígitos) ou CNPJ (14 dígitos), apenas números Telefone com DDD, 10 ou 11 dígitos, apenas números CEP com 8 dígitos, sem hífen Logradouro Número do endereço Complemento Bairro Cidade Sigla do estado, 2 letras maiúsculas (ex: `SP`) Dados do cartão. **Obrigatório apenas quando `paymentMethod` = `creditCard`.** Número do cartão, 13 a 19 dígitos, sem espaços Nome do titular exatamente como impresso no cartão Validade no formato `YYYY-MM` Código de segurança, 3 ou 4 dígitos Número de parcelas, de 1 a 12 Token de uma [Checkout Session](/api-reference/checkout/criar) criada previamente. Use quando quiser aproveitar `metadata` e `client_reference_id` da session mantendo o checkout na sua interface. Texto livre anexado à cobrança **Cartão: servidor-para-servidor apenas.** Este endpoint recebe o número do cartão e o CVV em texto claro. Chame-o exclusivamente do seu backend — nunca do navegador nem de um aplicativo, onde a sua chave de API e os dados do cartão ficariam expostos. Processar dados de cartão no seu servidor coloca a sua operação no escopo do **PCI DSS**. Se você prefere não assumir isso, use `pix` e `boleto` aqui e deixe o cartão para a página hospedada da Garu. ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/charges \ -H "Authorization: Bearer sk_live_sua_chave_api" \ -H "Content-Type: application/json" \ -H "X-Idempotency-Key: pedido-4472" \ -d '{ "productId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "paymentMethod": "pix", "customer": { "name": "Maria Silva", "email": "maria@exemplo.com.br", "document": "12345678909", "phone": "11987654321" } }' ``` ```javascript JavaScript theme={null} const response = await fetch('https://garu.com.br/api/v1/charges', { method: 'POST', headers: { Authorization: `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json', 'X-Idempotency-Key': 'pedido-4472' }, body: JSON.stringify({ productId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', paymentMethod: 'pix', customer: { name: 'Maria Silva', email: 'maria@exemplo.com.br', document: '12345678909', phone: '11987654321' } }) }); const charge = await response.json(); console.log(charge.pix.code); // código PIX copia-e-cola ``` ```python Python theme={null} import os import requests response = requests.post( "https://garu.com.br/api/v1/charges", headers={ "Authorization": f"Bearer {os.environ['GARU_API_KEY']}", "Content-Type": "application/json", "X-Idempotency-Key": "pedido-4472", }, json={ "productId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "paymentMethod": "pix", "customer": { "name": "Maria Silva", "email": "maria@exemplo.com.br", "document": "12345678909", "phone": "11987654321", }, }, ) charge = response.json() print(charge["pix"]["code"]) ``` ## Resposta de Sucesso (201 Created) ```json theme={null} { "uuid": "6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f", "status": "pending", "paymentMethod": "pix", "amount": 349.00, "chargedTotal": 349.00, "installments": 1, "product": { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Curso de Marketing Digital" }, "customer": { "name": "Maria Silva", "email": "maria@exemplo.com.br", "document": "***456789**" }, "pix": { "code": "00020101021226840014br.gov.bcb.pix..." }, "boleto": null, "card": null, "refund": null, "createdAt": "2026-07-22T14:03:11.000Z", "expiresAt": null } ``` ### Blocos por método de pagamento Exatamente um dos três vem preenchido, conforme o `paymentMethod`: ```json PIX theme={null} "pix": { "code": "00020101021226840014br.gov.bcb.pix..." } ``` ```json Boleto theme={null} "boleto": { "barcodeLine": "34191.79001 01043.510047 91020.150008 1 98650000034900", "pdfUrl": "https://garu.com.br/api/v1/charges/6f1c9b2e-.../boleto.pdf" } ``` ```json Cartão theme={null} "card": { "brand": "visa", "last4": "1111", "authorizationCode": "123456" } ``` O `pix.code` é o **copia-e-cola** (padrão EMV). Renderize-o como QR Code com qualquer biblioteca do seu stack — não devolvemos imagem justamente para que nada no seu checkout venha de um domínio de terceiros. O `boleto.pdfUrl` aponta para o domínio da Garu e pode ser entregue direto ao seu cliente: é uma URL pública, que não exige chave de API. ### Sobre o `expiresAt` O `expiresAt` só vem preenchido para **boleto**, com o vencimento (8 dias após a criação). Para PIX e cartão ele vem `null`. Motivo: hoje a Garu não define uma janela de expiração própria para o código PIX. Em vez de devolver um valor que não corresponde a nada, devolvemos `null`. Se o seu fluxo precisa de um prazo para o PIX, controle-o do seu lado. ### Valores: `amount` e `chargedTotal` **`amount`** é o preço base do produto. **`chargedTotal`** é o que o cliente foi efetivamente cobrado. Eles são iguais em PIX, boleto e cartão em 1×. Em parcelamento no cartão o `chargedTotal` é **maior**, porque inclui o acréscimo do parcelamento: um produto de R\$ 349,00 em 2× resulta em `amount: 349.00` e `chargedTotal: 358.52`. Use `chargedTotal` para reconciliar o que foi cobrado e `amount` para casar com o seu catálogo. ## Erros | Código | Quando acontece | | - | - | | `400` | Dados inválidos (documento, telefone, cartão) ou pagamento recusado pela operadora | | `401` | Chave de API ausente ou inválida | | `404` | O `productId` não existe ou não pertence à conta da chave usada | ## Próximos passos Depois de criar a cobrança, acompanhe o pagamento por [webhook](/api-reference/webhooks) ou consultando [`GET /api/v1/charges/{uuid}`](/api-reference/cobrancas/detalhes). # Consultar Cobrança Source: https://docs.garu.com.br/api-reference/cobrancas/detalhes GET /api/v1/charges/{uuid} Recupere uma cobrança pelo uuid, incluindo status e dados de pagamento ## Visão Geral Retorna uma cobrança da sua conta pelo `uuid`. Use para acompanhar o status de um pagamento — especialmente num [checkout transparente](/guias/checkout-transparente), enquanto o cliente ainda não pagou o PIX ou o boleto. Para saber de um pagamento **assim que ele acontece**, prefira [webhooks](/api-reference/webhooks) em vez de ficar consultando este endpoint em loop. ## Headers Sua chave de API (`Bearer sk_live_...`) ## Path Parameters UUID da cobrança, devolvido na criação ## Exemplo de Requisição ```bash cURL theme={null} curl https://garu.com.br/api/v1/charges/6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f \ -H "Authorization: Bearer sk_live_sua_chave_api" ``` ```javascript JavaScript theme={null} const response = await fetch( `https://garu.com.br/api/v1/charges/${chargeUuid}`, { headers: { Authorization: `Bearer ${process.env.GARU_API_KEY}` } } ); const charge = await response.json(); console.log(charge.status); ``` ```python Python theme={null} import os import requests response = requests.get( f"https://garu.com.br/api/v1/charges/{charge_uuid}", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, ) print(response.json()["status"]) ``` ## Resposta de Sucesso (200 OK) ```json theme={null} { "uuid": "6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f", "status": "paid", "paymentMethod": "pix", "amount": 349.00, "chargedTotal": 349.00, "installments": 1, "product": { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Curso de Marketing Digital" }, "customer": { "name": "Maria Silva", "email": "maria@exemplo.com.br", "document": "***456789**" }, "pix": { "code": "00020101021226840014br.gov.bcb.pix..." }, "boleto": null, "card": null, "refund": null, "createdAt": "2026-07-22T14:03:11.000Z", "expiresAt": null } ``` Bandeira e últimos 4 dígitos do cartão só vêm na **resposta da criação**. Numa consulta posterior o bloco `card` volta com os campos nulos. ## Status possíveis O `status` é um valor **estável e amigável** — o mesmo para PIX, boleto e cartão. Você pode ramificar com segurança nele (`if (charge.status === 'paid')`); ele não muda se trocarmos de adquirente por trás. | Status | Descrição | Pago? | | - | - | - | | `pending` | Aguardando o pagamento do cliente (PIX ou boleto em aberto) | Não | | `authorized` | Cartão autorizado, valor ainda não capturado | Não | | `paid` | Pagamento confirmado — pode liberar o produto | **Sim** | | `failed` | Pagamento negado ou indisponível | Não | | `expired` | Boleto venceu sem pagamento | Não | | `canceled` | Cobrança cancelada antes do pagamento | Não | | `refund_pending` | Estorno de PIX solicitado, aguardando a devolução liquidar | Não | | `refunded` | Valor devolvido ao cliente | Não | | `chargeback` | Estorno forçado (contestação junto à bandeira) | Não | **Aja em `paid`.** É o único status que garante que o dinheiro entrou. `authorized` (cartão) ainda não é captura, e `refund_pending` significa que a devolução foi pedida mas ainda não caiu na conta do cliente. ## Erros | Código | Quando acontece | | - | - | | `401` | Chave de API ausente ou inválida | | `404` | A cobrança não existe ou não pertence à conta da chave usada | # Listar Cobranças Source: https://docs.garu.com.br/api-reference/cobrancas/listar GET /api/v1/charges Liste as cobranças da sua conta com filtros de status, método, produto, período e cliente ## Visão Geral Lista as cobranças da conta autenticada, da mais recente para a mais antiga por padrão. Útil para telas de conciliação e relatórios dentro do seu sistema. A lista inclui **todas** as cobranças da conta, inclusive as de pedidos de produto físico — nessas, o campo `product` vem `null`, porque o pedido pode conter mais de um produto. ## Headers Sua chave de API (`Bearer sk_live_...`) ## Query Parameters Página desejada Itens por página, máximo 100 Filtra pelo status amigável: `pending`, `authorized`, `paid`, `failed`, `expired`, `canceled`, `refund_pending`, `refunded`, `chargeback` Filtra por método: `pix`, `boleto` ou `creditCard` Filtra pelo UUID do produto Cobranças criadas a partir desta data (ISO-8601) Cobranças criadas até esta data (ISO-8601) Busca por nome, e-mail ou documento do cliente Ordenação: `createdAt`, `-createdAt`, `amount` ou `-amount`. O prefixo `-` indica ordem decrescente. ## Exemplo de Requisição ```bash cURL theme={null} curl -G https://garu.com.br/api/v1/charges \ -H "Authorization: Bearer sk_live_sua_chave_api" \ -d status=paid \ -d createdAfter=2026-07-01T00:00:00Z \ -d limit=50 ``` ```javascript JavaScript theme={null} const params = new URLSearchParams({ status: 'paid', createdAfter: '2026-07-01T00:00:00Z', limit: '50' }); const response = await fetch(`https://garu.com.br/api/v1/charges?${params}`, { headers: { Authorization: `Bearer ${process.env.GARU_API_KEY}` } }); const { data, totalCount } = await response.json(); console.log(`${data.length} de ${totalCount} cobranças`); ``` ```python Python theme={null} import os import requests response = requests.get( "https://garu.com.br/api/v1/charges", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, params={ "status": "paid", "createdAfter": "2026-07-01T00:00:00Z", "limit": 50, }, ) body = response.json() print(f"{len(body['data'])} de {body['totalCount']} cobranças") ``` ## Resposta de Sucesso (200 OK) ```json theme={null} { "data": [ { "uuid": "6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f", "status": "paid", "paymentMethod": "pix", "amount": 349.00, "chargedTotal": 349.00, "installments": 1, "product": { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Curso de Marketing Digital" }, "customer": { "name": "Maria Silva", "email": "maria@exemplo.com.br", "document": "***456789**" }, "pix": { "code": "00020101021226840014br.gov.bcb.pix..." }, "boleto": null, "card": null, "refund": null, "createdAt": "2026-07-22T14:03:11.000Z", "expiresAt": null } ], "count": 1, "totalCount": 137, "totalPages": 7 } ``` | Campo | Significado | | - | - | | `count` | Itens nesta página | | `totalCount` | Total de cobranças que casam com os filtros | | `totalPages` | Número de páginas com o `limit` informado | ## Erros | Código | Quando acontece | | - | - | | `400` | `paymentMethod`, `sort` ou uma das datas em formato inválido | | `401` | Chave de API ausente ou inválida | # Estornar Cobrança Source: https://docs.garu.com.br/api-reference/cobrancas/reembolsar POST /api/v1/charges/{uuid}/refund Estorne uma cobrança paga, total ou parcialmente ## Visão Geral Estorna uma cobrança já paga. O estorno pode ser **total** (omitindo `amount`) ou **parcial**. Só cobranças com status `paid` podem ser estornadas. Para cancelar uma cobrança que ainda **não** foi paga (`pending`), use [`DELETE /api/v1/charges/{uuid}`](/api-reference/cobrancas/cancelar). ## Headers Sua chave de API (`Bearer sk_live_...`) `application/json` Opcional. Só se aplica a Pix/boleto, que abrem um pedido de estorno em vez de reverter automaticamente — a mesma chave devolve o pedido original por 24h em vez de abrir um segundo. Ignorado para cartão, que reverte na hora e não tem pedido para duplicar. ## Path Parameters UUID da cobrança ## Request Body Valor a estornar em **BRL decimal** (ex: `50.00`, não `5000`). Omita para estornar o total cobrado. Motivo do estorno, até 500 caracteres. Fica registrado na cobrança. O teto do estorno é o **`chargedTotal`** da cobrança, não o `amount`. Numa venda parcelada no cartão o cliente pagou o valor com acréscimo, e é esse valor que pode voltar: um produto de R$ 349,00 vendido em 2× por R$ 358,52 aceita estorno de até R\$ 358,52. ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/charges/6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f/refund \ -H "Authorization: Bearer sk_live_sua_chave_api" \ -H "Content-Type: application/json" \ -d '{ "amount": 100.00, "reason": "Cliente desistiu" }' ``` ```javascript JavaScript theme={null} const response = await fetch( `https://garu.com.br/api/v1/charges/${chargeUuid}/refund`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ amount: 100.0, reason: 'Cliente desistiu' }) } ); const charge = await response.json(); console.log(charge.refund); ``` ```python Python theme={null} import os import requests response = requests.post( f"https://garu.com.br/api/v1/charges/{charge_uuid}/refund", headers={ "Authorization": f"Bearer {os.environ['GARU_API_KEY']}", "Content-Type": "application/json", }, json={"amount": 100.00, "reason": "Cliente desistiu"}, ) print(response.json()["refund"]) ``` ## Resposta de Sucesso (200 OK) A cobrança atualizada, agora com o bloco `refund` preenchido: ```json theme={null} { "uuid": "6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f", "status": "reversed", "paymentMethod": "pix", "amount": 349.00, "chargedTotal": 349.00, "refund": { "amount": 100.00, "reason": "Cliente desistiu", "refundedAt": "2026-07-23T10:00:00.000Z" } } ``` ## Estorno de Pix Automático Cobranças de **Pix Automático** são estornadas via *devolução* — uma transferência Pix de volta, regida por regras do Banco Central. Isso muda o comportamento em um ponto importante: A devolução **não é instantânea**. A resposta confirma que o estorno foi *solicitado*, não concluído. A cobrança fica com `status: "refundPending"` e o bloco `refund` já traz o valor, mas com **`refundedAt: null`**. Quando a transferência liquida, o status vira `reversed`, o `refundedAt` é preenchido e o webhook `transaction.refunded` é disparado. Use `refundedAt` como o sinal de que o dinheiro voltou de fato — não a resposta desta chamada. Estornos de **cartão** e **boleto** (Celcoin) são confirmados na própria resposta. Estorno de **PIX processado pela Celcoin não é suportado** (veja abaixo). ## Estorno de PIX (Celcoin) não é suportado A Celcoin não oferece devolução de PIX: a API dela expõe estorno apenas para cartão e boleto, e o painel não mostra ação de estorno para um PIX pago. Um estorno de PIX processado pela Celcoin **não é possível por aqui** — a API recusa na hora, sem sequer tentar o provedor. A API sinaliza esse caso com **HTTP 422** e o campo **`code: "pix_refund_unsupported"`**. É uma recusa **definitiva** — não adianta tentar de novo. Para devolver um PIX Celcoin, faça a transferência de volta **manualmente** ao cliente (por exemplo, um Pix de volta). ```json 422 Unprocessable Entity theme={null} { "statusCode": 422, "code": "pix_refund_unsupported", "message": "Estorno de Pix não é suportado para cobranças processadas pela Celcoin. Faça a devolução manualmente ao cliente (por exemplo, um Pix de volta).", "path": "/api/v1/charges/{uuid}/refund" } ``` Estorno de **PIX Automático (Woovi)** é outro caminho e **continua funcionando** — veja a seção acima. ## Auditoria Todo estorno registra quem o fez. Um estorno feito pelo painel guarda o usuário responsável; um estorno feito por esta API guarda **qual chave de API** foi usada. Isso vale para a trilha de auditoria exigida pelo PCI DSS — mais um motivo para usar chaves distintas por integração e revogar as que não estiverem em uso. ## Erros | Código | Quando acontece | | - | - | | `400` | Cobrança não está em status estornável, já foi estornada, tem um estorno em processamento, ou o valor excede o `chargedTotal` | | `401` | Chave de API ausente ou inválida | | `404` | A cobrança não existe ou não pertence à conta da chave usada | | `422` | Estorno de PIX processado pela Celcoin — não suportado. Recusa definitiva; o corpo traz `code: "pix_refund_unsupported"` (veja acima) | # Confirmar Estorno Source: https://docs.garu.com.br/api-reference/estornos/confirmar POST /api/v1/refund-requests/{uuid}/confirm Registra que você já devolveu o dinheiro ## Visão Geral Chame isto **depois** de transferir o dinheiro ao comprador. A Garu nunca observa a transferência — ela registra que **você afirma** que o dinheiro voltou. Confirmar dispara tudo de uma vez: * o carnê é encerrado como `refunded` e as parcelas restantes param; * os boletos em aberto são cancelados no banco; * as comissões de afiliado e coprodutor **das parcelas que compensaram** são estornadas; * numa cobrança Pix ou boleto, a cobrança é marcada como estornada e dispara `transaction.refunded`. Idempotente: confirmar duas vezes não estorna comissão duas vezes. ## Exemplo ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/refund-requests/a1b2c3d4-0000-4000-8000-000000000001/confirm \ -H "Authorization: Bearer sk_live_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "note": "Pix devolvido em 14/08, e2e E12345678" }' ``` ```javascript JavaScript theme={null} await garu.refundRequests.confirm(uuid, { note: 'Pix devolvido em 14/08, e2e E12345678' }); ``` ## Parâmetros Registrada na resolução para a trilha de auditoria. Um bom uso é guardar o identificador da transferência. # Detalhes do Pedido de Estorno Source: https://docs.garu.com.br/api-reference/estornos/detalhes GET /api/v1/refund-requests/{uuid} Um pedido de estorno ## Exemplo ```bash cURL theme={null} curl https://garu.com.br/api/v1/refund-requests/a1b2c3d4-0000-4000-8000-000000000001 \ -H "Authorization: Bearer sk_live_sua_chave" ``` ```javascript JavaScript theme={null} const pedido = await garu.refundRequests.get(uuid); ``` ## Resposta ```json theme={null} { "uuid": "a1b2c3d4-0000-4000-8000-000000000001", "status": "pending", "amount": 390, "reason": "Comprador desistiu", "installmentPlanId": "e5d0d8fe-0000-4000-8000-000000000001", "chargeId": null, "requestedBy": { "type": "user", "id": 3 }, "resolvedBy": null, "sellerNote": null, "resolvedAt": null, "createdAt": "2026-09-01T09:00:00.000Z" } ``` Exatamente um entre `installmentPlanId` e `chargeId` vem preenchido, conforme o pedido seja de um carnê ou de uma cobrança avulsa. # Listar Pedidos de Estorno Source: https://docs.garu.com.br/api-reference/estornos/listar GET /api/v1/refund-requests O que você ainda deve devolver a compradores ## Visão Geral Lista os pedidos de estorno da conta. Cobre carnês **e** cobranças Pix/boleto avulsas. Cartão e Pix Woovi não aparecem aqui — eles têm estorno automático de verdade, via [Estornar Cobrança](/api-reference/cobrancas/reembolsar). ## Exemplo ```bash cURL theme={null} curl "https://garu.com.br/api/v1/refund-requests?status=pending" \ -H "Authorization: Bearer sk_live_sua_chave" ``` ```javascript JavaScript theme={null} // Quanto eu ainda devo devolver? const pendentes = await garu.refundRequests.list({ status: 'pending' }); const total = pendentes.data.reduce((s, r) => s + r.amount, 0); ``` ## Parâmetros de Query `pending`, `confirmed` ou `rejected`. Repita para aceitar vários. Filtra pelo UUID do carnê. Filtra pelo UUID da cobrança (Pix e boleto). Página, padrão 1. Itens por página, padrão 20, máximo 100. # Rejeitar Estorno Source: https://docs.garu.com.br/api-reference/estornos/rejeitar POST /api/v1/refund-requests/{uuid}/reject Recusa o pedido; o carnê segue rodando ## Visão Geral Recusa o pedido. O carnê fica **intacto** e continua exatamente como estava. O registro de que um estorno foi pedido não é apagado — ele continua no histórico. Idempotente. ## Exemplo ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/refund-requests/a1b2c3d4-0000-4000-8000-000000000001/reject \ -H "Authorization: Bearer sk_live_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "note": "Produto entregue e retirado na loja em 02/08" }' ``` ```javascript JavaScript theme={null} await garu.refundRequests.reject(uuid, { note: 'Produto entregue e retirado na loja em 02/08' }); ``` ## Parâmetros Motivo da recusa, para a trilha de auditoria. # Exemplos de Código Source: https://docs.garu.com.br/api-reference/exemplos Implementações completas em várias linguagens de programação ## Visão Geral Esta página contém exemplos completos e prontos para produção em várias linguagens de programação. Copie e adapte para sua aplicação. ## Funções de Setup Use estas funções auxiliares como base para suas integrações: ```javascript theme={null} const GARU_API_URL = 'https://garu.com.br/api'; const GARU_PAY_URL = 'https://garu.com.br/pay'; async function garuRequest(endpoint, options = {}) { const response = await fetch(`${GARU_API_URL}${endpoint}`, { ...options, headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json', ...options.headers } }); if (!response.ok) { const error = await response.json(); throw new Error(error.message || 'Erro na API'); } if (response.status === 204) return null; return response.json(); } ``` ```python theme={null} import requests import os GARU_API_URL = 'https://garu.com.br/api' GARU_PAY_URL = 'https://garu.com.br/pay' def garu_request(method, endpoint, data=None, params=None): headers = { 'Authorization': f'Bearer {os.environ["GARU_API_KEY"]}', 'Content-Type': 'application/json' } response = requests.request( method=method, url=f'{GARU_API_URL}{endpoint}', json=data, params=params, headers=headers ) response.raise_for_status() if response.status_code == 204: return None return response.json() ``` ```php theme={null} true, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$apiKey}", 'Content-Type: application/json' ] ]); if ($data !== null) { curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); } $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode >= 400) { throw new Exception("Erro na API: {$response}"); } if ($httpCode === 204) { return null; } return json_decode($response, true); } ``` ## Validação de Webhooks *** ## Classes Cliente Completas Implementações completas com todos os métodos da API. ### JavaScript / Node.js ```javascript theme={null} // garu-client.js class GaruClient { constructor(apiKey) { this.apiKey = apiKey; this.baseUrl = "https://garu.com.br/api"; this.appUrl = "https://garu.com.br/pay"; } async request(endpoint, options = {}) { const url = `${this.baseUrl}${endpoint}`; const response = await fetch(url, { ...options, headers: { Authorization: `Bearer ${this.apiKey}`, "Content-Type": "application/json", ...options.headers, }, }); if (!response.ok) { const error = await response.json(); throw new Error(error.message || "Erro na API"); } // DELETE retorna 204 sem corpo if (response.status === 204) { return null; } return response.json(); } // Produtos async criarProduto(dados) { const produto = await this.request("/v1/products", { method: "POST", body: JSON.stringify(dados), }); return { ...produto, linkPagamento: `${this.appUrl}/${produto.uuid}`, }; } async listarProdutos(page = 1, limit = 10) { return this.request(`/v1/products?page=${page}&limit=${limit}`); } async buscarProduto(uuid) { return this.request(`/v1/products/${uuid}`); } async atualizarProduto(uuid, dados) { return this.request(`/v1/products/${uuid}`, { method: "PATCH", body: JSON.stringify(dados), }); } async excluirProduto(uuid) { return this.request(`/v1/products/${uuid}`, { method: "DELETE", }); } } // Uso const garu = new GaruClient(process.env.GARU_API_KEY); // Criar produto const produto = await garu.criarProduto({ name: "Curso de Marketing Digital", description: "Aprenda marketing digital do zero", image: "https://meusite.com.br/imagens/curso-marketing.jpg", value: 297.0, pix: true, creditCard: true, boleto: true, installments: 12, returnUrl: "https://meusite.com.br/obrigado", }); console.log(`Produto criado: ${produto.name}`); console.log(`Link: ${produto.linkPagamento}`); // Listar produtos const { data: produtos } = await garu.listarProdutos(); produtos.forEach((p) => console.log(`- ${p.name}: R$ ${p.value}`)); ``` ### Express.js com Webhook ```javascript theme={null} // server.js const express = require("express"); const crypto = require("crypto"); const app = express(); app.use(express.json()); // Verificar assinatura do webhook function verificarAssinatura(payload, signature, secret) { const expected = crypto .createHmac("sha256", secret) .update(JSON.stringify(payload)) .digest("hex"); return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); } // Handler de webhook app.post("/webhooks/garu", async (req, res) => { const signature = req.headers["x-garu-signature"]; // Validar assinatura if (!verificarAssinatura(req.body, signature, process.env.WEBHOOK_SECRET)) { return res.status(401).json({ error: "Assinatura inválida" }); } // Responder rapidamente res.status(200).json({ received: true }); // Processar evento const { event, data } = req.body; switch (event) { case "transaction.paid": await processarPagamento(data); break; case "transaction.failed": await notificarFalha(data); break; case "subscription.cancelled": await cancelarAcesso(data); break; } }); async function processarPagamento(data) { console.log(`Pagamento confirmado: ${data.id}`); console.log(`Cliente: ${data.customer.email}`); console.log(`Valor: R$ ${data.value}`); // Liberar acesso, enviar email, etc. } async function notificarFalha(data) { console.log(`Pagamento falhou: ${data.id}`); // Enviar notificação } async function cancelarAcesso(data) { console.log(`Assinatura cancelada: ${data.id}`); // Revogar acesso } app.listen(3000, () => { console.log("Servidor rodando na porta 3000"); }); ``` ## Python ### Classe Cliente Completa ```python theme={null} # garu_client.py import requests import os from typing import Optional, Dict, List, Any class GaruClient: def __init__(self, api_key: str): self.api_key = api_key self.base_url = 'https://garu.com.br/api' self.app_url = 'https://garu.com.br/pay' def _headers(self) -> Dict[str, str]: return { 'Authorization': f'Bearer {self.api_key}', 'Content-Type': 'application/json' } def _request(self, method: str, endpoint: str, data: Optional[Dict] = None, params: Optional[Dict] = None) -> Any: url = f'{self.base_url}{endpoint}' response = requests.request( method=method, url=url, json=data, params=params, headers=self._headers() ) response.raise_for_status() if response.status_code == 204: return None return response.json() # Produtos def criar_produto(self, dados: Dict) -> Dict: produto = self._request('POST', '/v1/products', data=dados) produto['link_pagamento'] = f"{self.app_url}/{produto['uuid']}" return produto def listar_produtos(self, page: int = 1, limit: int = 10) -> Dict: return self._request('GET', '/v1/products', params={ 'page': page, 'limit': limit }) def buscar_produto(self, uuid: str) -> Dict: return self._request('GET', f'/v1/products/{uuid}') def atualizar_produto(self, uuid: str, dados: Dict) -> Dict: return self._request('PATCH', f'/v1/products/{uuid}', data=dados) def excluir_produto(self, uuid: str) -> None: self._request('DELETE', f'/v1/products/{uuid}') # Uso if __name__ == '__main__': garu = GaruClient(os.environ['GARU_API_KEY']) # Criar produto produto = garu.criar_produto({ 'name': 'Curso de Marketing Digital', 'description': 'Aprenda marketing digital do zero', 'image': 'https://meusite.com.br/imagens/curso-marketing.jpg', 'value': 297.00, 'pix': True, 'creditCard': True, 'boleto': True, 'installments': 12, 'returnUrl': 'https://meusite.com.br/obrigado' }) print(f"Produto criado: {produto['name']}") print(f"Link: {produto['link_pagamento']}") # Listar produtos resultado = garu.listar_produtos() for p in resultado['data']: print(f"- {p['name']}: R$ {p['value']}") ``` ### Flask com Webhook ```python theme={null} # app.py import hmac import hashlib import os from flask import Flask, request, jsonify app = Flask(__name__) def verificar_assinatura(payload: str, signature: str, secret: str) -> bool: expected = hmac.new( secret.encode(), payload.encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(signature, expected) @app.route('/webhooks/garu', methods=['POST']) def webhook(): signature = request.headers.get('X-Garu-Signature', '') secret = os.environ['WEBHOOK_SECRET'] payload = request.get_data(as_text=True) if not verificar_assinatura(payload, signature, secret): return jsonify({'error': 'Assinatura inválida'}), 401 data = request.json event = data['event'] event_data = data['data'] if event == 'transaction.paid': processar_pagamento(event_data) elif event == 'transaction.failed': notificar_falha(event_data) elif event == 'subscription.cancelled': cancelar_acesso(event_data) return jsonify({'received': True}), 200 def processar_pagamento(data): print(f"Pagamento confirmado: {data['id']}") print(f"Cliente: {data['customer']['email']}") print(f"Valor: R$ {data['value']}") # Liberar acesso, enviar email, etc. def notificar_falha(data): print(f"Pagamento falhou: {data['id']}") # Enviar notificação def cancelar_acesso(data): print(f"Assinatura cancelada: {data['id']}") # Revogar acesso if __name__ == '__main__': app.run(port=3000) ``` ## PHP ### Classe Cliente Completa ```php theme={null} apiKey = $apiKey; } private function request(string $method, string $endpoint, ?array $data = null): ?array { $url = $this->baseUrl . $endpoint; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => $method, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$this->apiKey}", 'Content-Type: application/json' ] ]); if ($data !== null) { curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); } $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode >= 400) { throw new Exception("Erro na API: {$response}"); } if ($httpCode === 204) { return null; } return json_decode($response, true); } // Produtos public function criarProduto(array $dados): array { $produto = $this->request('POST', '/v1/products', $dados); $produto['linkPagamento'] = "{$this->appUrl}/{$produto['uuid']}"; return $produto; } public function listarProdutos(int $page = 1, int $limit = 10): array { return $this->request('GET', "/v1/products?page={$page}&limit={$limit}"); } public function buscarProduto(string $uuid): array { return $this->request('GET', "/v1/products/{$uuid}"); } public function atualizarProduto(string $uuid, array $dados): array { return $this->request('PATCH', "/v1/products/{$uuid}", $dados); } public function excluirProduto(string $uuid): void { $this->request('DELETE', "/v1/products/{$uuid}"); } } // Uso $garu = new GaruClient(getenv('GARU_API_KEY')); // Criar produto $produto = $garu->criarProduto([ 'name' => 'Curso de Marketing Digital', 'description' => 'Aprenda marketing digital do zero', 'image' => 'https://meusite.com.br/imagens/curso-marketing.jpg', 'value' => 297.00, 'pix' => true, 'creditCard' => true, 'boleto' => true, 'installments' => 12, 'returnUrl' => 'https://meusite.com.br/obrigado' ]); echo "Produto criado: {$produto['name']}\n"; echo "Link: {$produto['linkPagamento']}\n"; // Listar produtos $resultado = $garu->listarProdutos(); foreach ($resultado['data'] as $p) { echo "- {$p['name']}: R$ {$p['value']}\n"; } ``` ### Laravel com Webhook ```php theme={null} header('X-Garu-Signature'); $payload = $request->getContent(); $secret = config('services.garu.webhook_secret'); if (!$this->verificarAssinatura($payload, $signature, $secret)) { return response()->json(['error' => 'Assinatura inválida'], 401); } $event = $request->input('event'); $data = $request->input('data'); match ($event) { 'transaction.paid' => $this->processarPagamento($data), 'transaction.failed' => $this->notificarFalha($data), 'subscription.cancelled' => $this->cancelarAcesso($data), default => Log::info("Evento não tratado: {$event}") }; return response()->json(['received' => true]); } private function verificarAssinatura(string $payload, ?string $signature, string $secret): bool { if (!$signature) { return false; } $expected = hash_hmac('sha256', $payload, $secret); return hash_equals($expected, $signature); } private function processarPagamento(array $data): void { Log::info("Pagamento confirmado: {$data['id']}"); Log::info("Cliente: {$data['customer']['email']}"); // Liberar acesso, enviar email, etc. } private function notificarFalha(array $data): void { Log::warning("Pagamento falhou: {$data['id']}"); // Notificar cliente } private function cancelarAcesso(array $data): void { Log::info("Assinatura cancelada: {$data['id']}"); // Revogar acesso } } ``` ## Próximos Passos Resolva erros comuns da API Configure notificações em tempo real # Introdução Source: https://docs.garu.com.br/api-reference/introduction Comece a integrar com a API da Garu para processar pagamentos ## Visão Geral A API da Garu permite que você integre processamento de pagamentos diretamente em suas aplicações. Com nossa API, você pode: * Criar produtos para vendas avulsas * Processar pagamentos via **PIX**, **Cartão de Crédito** e **Boleto** * Configurar parcelamento em até 12x * Receber notificações em tempo real via webhooks * Gerenciar assinaturas e cobranças recorrentes ## Primeiros Passos Acesse o [Dashboard da Garu](https://garu.com.br/inicio) e navegue até **Configurações** → **Desenvolvedores** para criar sua chave de API. Use sua chave de API no header `Authorization` de todas as requisições. Faça uma requisição POST para `/api/v1/products` com os dados do seu produto. Use o `uuid` retornado para construir a URL de pagamento: `https://garu.com.br/pay/{uuid}` ## Exemplo Rápido Crie um produto com uma única requisição: ```bash theme={null} curl -X POST https://garu.com.br/api/v1/products \ -H "Authorization: Bearer sk_test_sua_chave_api" \ -H "Content-Type: application/json" \ -d '{ "name": "Curso de Marketing Digital", "description": "Aprenda marketing digital do zero", "image": "https://meusite.com/imagens/curso-marketing.jpg", "value": 297.00, "pix": true, "creditCard": true, "boleto": true, "installments": 12 }' ``` Pronto! Use o `uuid` da resposta para gerar o link de pagamento. ## Próximos Passos Aprenda a configurar suas chaves de API Guia completo para criar produtos # Atualizar E-mail de Cobrança Source: https://docs.garu.com.br/api-reference/portal-do-cliente/atualizar-email PATCH /api/public/minha-area/profile Define ou limpa o e-mail de cobrança específico do cliente para um vendedor ## Visão Geral Endpoint autenticado pelo **link mágico**. Permite ao cliente definir um e-mail específico (`billingEmailOverride`) para receber cobranças de **um vendedor específico** — útil quando o cliente prefere que cobranças cheguem em um endereço fixo (ex: `cobrancas@empresa.com.br`) diferente do e-mail pessoal. O override é **fixo**: mesmo que o cliente troque o e-mail principal em uma cobrança futura, o desta loja não muda. Para voltar ao padrão, envie `billingEmailOverride: null`. ## Resolução do e-mail enviado ```text theme={null} billingEmailOverride (definido aqui) ?? CustomerSellerProfile.lastUsedEmail (último visto neste vendedor) ?? Customer.email (e-mail principal global) ``` ## Autenticação Header `Authorization: Bearer `. ## Exemplo de Requisição ```bash cURL theme={null} # Definir um e-mail fixo para o vendedor 10 curl -X PATCH https://garu.com.br/api/public/minha-area/profile \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI..." \ -H "Content-Type: application/json" \ -d '{ "sellerId": 10, "billingEmailOverride": "cobrancas@empresa.com.br" }' # Limpar o override (volta ao padrão) curl -X PATCH https://garu.com.br/api/public/minha-area/profile \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI..." \ -H "Content-Type: application/json" \ -d '{ "sellerId": 10, "billingEmailOverride": null }' ``` ```javascript JavaScript theme={null} await fetch('https://garu.com.br/api/public/minha-area/profile', { method: 'PATCH', headers: { Authorization: `Bearer ${magicLinkToken}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ sellerId: 10, billingEmailOverride: 'cobrancas@empresa.com.br' }) }); ``` ```python Python theme={null} import requests requests.patch( "https://garu.com.br/api/public/minha-area/profile", headers={"Authorization": f"Bearer {magic_link_token}"}, json={"sellerId": 10, "billingEmailOverride": "cobrancas@empresa.com.br"} ) ``` ## Parâmetros ID do vendedor para o qual aplicar o e-mail. Deve ser um vendedor com o qual o cliente já tem relacionamento (uma cobrança ou pagamento existente). E-mail a fixar como override. **Envie `null`** (literal) para limpar o override e voltar ao e-mail padrão do cliente. ## Resposta ```json theme={null} { "success": true } ``` ## Erros * **400 Bad Request** — `billingEmailOverride` não é um e-mail válido (e não é `null`). * **401 Unauthorized** — JWT expirado/inválido. * **403 Forbidden** — cliente não tem relacionamento com o `sellerId` informado. * **404 Not Found** — cliente associado ao token não existe mais (caso raro de exclusão). # Listar Cobranças do Cliente Source: https://docs.garu.com.br/api-reference/portal-do-cliente/listar-cobrancas GET /api/public/minha-area/charges Cross-seller: cobranças do cliente em todos os vendedores ## Visão Geral Endpoint autenticado pelo **link mágico** emitido em [`/request`](/api-reference/portal-do-cliente/solicitar-link). Retorna todas as cobranças agendadas em status acionáveis (`scheduled`, `due_today`, `overdue`, `paused`, `paid`, `pending_tokenization`) para o CPF associado ao token, em **todos** os vendedores Garu com os quais o cliente tem relacionamento. Cada cobrança vem com `customer`, `product` (se associado) e `seller` carregados, prontos para exibir sem chamadas adicionais. ## Autenticação Header `Authorization: Bearer `. O JWT é o mesmo enviado no link `https://garu.com.br/minha-area/`. ## Exemplo de Requisição ```bash cURL theme={null} curl -X GET https://garu.com.br/api/public/minha-area/charges \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI..." ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/public/minha-area/charges', { headers: { Authorization: `Bearer ${magicLinkToken}` } } ); const { data } = await response.json(); data.forEach((charge) => { console.log(charge.seller?.name, charge.amount, charge.status); }); ``` ```python Python theme={null} import requests response = requests.get( "https://garu.com.br/api/public/minha-area/charges", headers={"Authorization": f"Bearer {magic_link_token}"} ) for charge in response.json()["data"]: print(charge["seller"]["name"], charge["amount"], charge["status"]) ``` ## Resposta ```json theme={null} { "data": [ { "id": "sch_abc123", "sellerId": 10, "amount": 297.5, "description": "Mensalidade Junho", "dueDate": "2026-06-15", "methods": ["pix", "boleto"], "status": "scheduled", "customer": { "id": 42, "name": "Maria Silva", "email": "maria@exemplo.com.br", "document": "12345678901" }, "product": null, "seller": { "id": 10, "name": "Loja Garu" } } ] } ``` ## Erros * **401 Unauthorized** — header ausente, malformado ou JWT expirado/inválido. Solicite um novo link em [`/request`](/api-reference/portal-do-cliente/solicitar-link). # Solicitar Link Mágico Source: https://docs.garu.com.br/api-reference/portal-do-cliente/solicitar-link POST /api/public/minha-area/request Envia um link de acesso ao /minha-area para o e-mail registrado do cliente ## Visão Geral Endpoint público (sem autenticação por API key). Sempre responde **200**, com `maskedEmail = null` quando o CPF/CNPJ não corresponde a nenhum cliente — é assim por design, para evitar enumeração de CPFs cadastrados. Quando há cliente correspondente, a Garu envia um e-mail com um link da forma `https://garu.com.br/minha-area/`. O JWT tem TTL de 24h e é reutilizável dentro desse período. ## Throttle **3 requisições por hora por IP.** Excedente retorna **429 Too Many Requests**. ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST https://garu.com.br/api/public/minha-area/request \ -H "Content-Type: application/json" \ -d '{ "document": "12345678901" }' ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/public/minha-area/request', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ document: '12345678901' }) } ); const { maskedEmail } = await response.json(); // maskedEmail: 'm***@gmail.com' ou null ``` ```python Python theme={null} import requests response = requests.post( "https://garu.com.br/api/public/minha-area/request", json={"document": "12345678901"} ) masked = response.json()["maskedEmail"] # 'm***@gmail.com' ou None ``` ## Parâmetros CPF (11 dígitos) ou CNPJ (14 dígitos). Pontuação é removida no servidor. ## Resposta **200 OK** sempre, mesmo sem cliente correspondente: ```json theme={null} { "maskedEmail": "m***@gmail.com" } ``` ou ```json theme={null} { "maskedEmail": null } ``` Não tente inferir a existência do CPF pela resposta — o tempo de resposta é constante e o `maskedEmail = null` cobre tanto "CPF inválido" quanto "CPF não cadastrado". ## Erros * **400 Bad Request** — corpo inválido (ex: `document` ausente). * **429 Too Many Requests** — throttle de 3/hora/IP excedido. # Atualizar Produto Source: https://docs.garu.com.br/api-reference/produtos/atualizar PATCH /api/v1/products/{uuid} Atualize os dados de um produto existente ## Visão Geral Use este endpoint para atualizar as informações de um produto existente. Apenas os campos enviados serão atualizados. ## Parâmetros de Path UUID do produto a ser atualizado. ## Parâmetros do Body Todos os campos são opcionais. Envie apenas os que deseja atualizar. Novo nome do produto. Nova descrição do produto. Novo preço do produto em **Reais** (BRL decimal, ex: `347.00`), nunca em centavos. Envie `0` para deixar o produto sem preço; qualquer outro preço precisa ser de \*\*pelo menos R$ 5,00**. De R$ 0,01 a R\$ 4,99, ou um valor negativo, a API responde `400`. Omita o campo para manter o preço atual. A regra do mínimo só vale quando o pedido define um preço: um produto que já custava menos de R\$ 5,00 continua vendendo, e um pedido que só troca o nome ou liga o PIX passa normalmente. Habilitar/desabilitar pagamento via PIX. Habilitar/desabilitar pagamento via cartão de crédito. Habilitar/desabilitar pagamento via boleto. Habilitar/desabilitar o [Pix Automático](/guias/pix-automatico) (débito recorrente do Pix) no produto. Enviar `false` impede **novas** assinaturas por Pix Automático nesse produto; as assinaturas já ativas continuam sendo cobradas normalmente. Novo número máximo de parcelas (1-12). Nova URL da imagem do produto. Nova URL de retorno após pagamento. Novo texto do botão de retorno. Transforma o produto em assinatura (`true`) ou em venda avulsa (`false`). Transformar um produto de assinatura em venda avulsa conta como definir o preço: o `value` guardado nele passa a ser o preço do link, e precisa ser `0` ou pelo menos R\$ 5,00. ## Exemplo de Requisição ```bash cURL theme={null} curl -X PATCH https://garu.com.br/api/v1/products/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ -H "Authorization: Bearer sk_test_sua_chave_api" \ -H "Content-Type: application/json" \ -d '{ "value": 347.00, "installments": 6 }' ``` ```javascript JavaScript theme={null} const response = await fetch('https://garu.com.br/api/v1/products/a1b2c3d4-e5f6-7890-abcd-ef1234567890', { method: 'PATCH', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ value: 347.00, installments: 6 }) }); const product = await response.json(); console.log(`Produto atualizado: ${product.name} - R$ ${product.value}`); ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/v1/products/a1b2c3d4-e5f6-7890-abcd-ef1234567890" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}", "Content-Type": "application/json" } payload = { "value": 347.00, "installments": 6 } response = requests.patch(url, json=payload, headers=headers) product = response.json() print(f"Produto atualizado: {product['name']} - R$ {product['value']}") ``` ## Resposta de Sucesso (200 OK) ```json theme={null} { "name": "Curso de Marketing Digital", "description": "Aprenda marketing digital do zero ao avançado", "value": 347.00, "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "pix": true, "creditCard": true, "boleto": true, "installments": [ { "quantity": 1, "value": 347.00 }, { "quantity": 2, "value": 179.32 }, { "quantity": 3, "value": 122.61 }, { "quantity": 4, "value": 94.28 }, { "quantity": 5, "value": 77.30 }, { "quantity": 6, "value": 66.05 } ], "isActive": true, "createdAt": "2024-12-24T10:30:00.000Z", "updatedAt": "2024-12-24T14:45:00.000Z" } ``` O `uuid` do produto **não muda** ao atualizar. O link de pagamento permanece o mesmo. ## Erros Comuns ```json theme={null} { "statusCode": 400, "timestamp": "2026-09-30T21:00:00.000Z", "path": "/api/v1/products/a1b2c3d4-e5f6-7890-abcd-ef1234567890", "message": "O preço mínimo de um produto é R$ 5,00. Use R$ 0,00 para um produto sem preço." } ``` **Solução:** Envie um `value` de pelo menos `5.00`, ou `0` para deixar o produto sem preço. Se você não quer mudar o preço, não envie `value`. ```json theme={null} { "statusCode": 404, "message": "Product not found" } ``` **Solução:** Verifique se o UUID do produto está correto. ```json theme={null} { "statusCode": 403, "message": "Insufficient permissions" } ``` **Solução:** Você só pode atualizar produtos criados com sua conta. ## Próximos Passos Desative um produto Receba notificações de eventos # Criar Produto Source: https://docs.garu.com.br/api-reference/produtos/criar POST /api/v1/products Aprenda a criar produtos para vendas avulsas ou assinaturas via API ## Visão Geral O endpoint de criação de produtos permite que você crie produtos para vendas avulsas (one-time purchases) ou assinaturas (recurring billing). Cada produto criado recebe um `uuid` único que é usado para gerar o link de pagamento. ## Requisição ### Campos Obrigatórios Nome do produto. Será exibido na página de pagamento. URL da imagem do produto. Preço do produto em **Reais** (BRL decimal, ex: `297.00`), nunca em centavos. * `0` cria um produto **sem preço**. É aceito, mas o produto não vende pelo link de pagamento: a cobrança de R\$ 0,00 é recusada. Use para um produto que você cobra de outro jeito, como por [cobranças agendadas](/api-reference/cobrancas-agendadas/criar). * Qualquer outro preço precisa ser de \*\*pelo menos R$ 5,00**, o preço mínimo da plataforma. De R$ 0,01 a R\$ 4,99, ou um valor negativo, a API responde `400`. * Em um produto de assinatura (`isSubscription: true`), o preço fica nos [preços de assinatura](/api-reference/assinaturas/precos/criar) e este campo não é verificado. Envie `0`. ### Campos Opcionais Descrição do produto. Ajuda o cliente a entender o que está comprando. Habilita pagamento via PIX. Habilita pagamento via cartão de crédito. Habilita pagamento via boleto bancário. Habilita o [Pix Automático](/guias/pix-automatico) (débito recorrente do Pix) no checkout de assinatura do produto. **Vem ligado por padrão** — envie `false` para não oferecer. Produtos que não são assinatura ignoram o campo. Número máximo de parcelas permitidas (1-12). Tags para categorizar o produto. URL para redirecionar o cliente após o pagamento. Texto do botão de redirecionamento (máximo 100 caracteres). ID do Facebook Pixel para rastreamento. Define se o produto é de assinatura (cobrança recorrente). Para produtos de assinatura, o preço é definido nos [preços de assinatura](/api-reference/assinaturas/precos/criar), não no campo `value`. `name`, `image` e `value` são obrigatórios. Os métodos de pagamento vêm **desligados** por padrão: envie `pix`, `creditCard` e `boleto` com `true` para os que você quer aceitar. ## Exemplo de Requisição ### Exemplo Mínimo Crie um produto à venda com os campos obrigatórios e o PIX ligado: ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/products \ -H "Authorization: Bearer sk_test_sua_chave_api" \ -H "Content-Type: application/json" \ -d '{ "name": "Meu Produto", "image": "https://meusite.com/imagens/meu-produto.jpg", "value": 49.90, "pix": true }' ``` ```javascript JavaScript theme={null} const response = await fetch('https://garu.com.br/api/v1/products', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Meu Produto', image: 'https://meusite.com/imagens/meu-produto.jpg', value: 49.90, pix: true }) }); const product = await response.json(); console.log(`Link de pagamento: https://garu.com.br/pay/${product.uuid}`); ``` ```python Python theme={null} import requests import os response = requests.post( "https://garu.com.br/api/v1/products", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, json={ "name": "Meu Produto", "image": "https://meusite.com/imagens/meu-produto.jpg", "value": 49.90, "pix": True } ) product = response.json() print(f"Link de pagamento: https://garu.com.br/pay/{product['uuid']}") ``` ### Exemplo Completo Personalize todas as opções do produto: ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/products \ -H "Authorization: Bearer sk_test_sua_chave_api" \ -H "Content-Type: application/json" \ -d '{ "name": "Curso de Marketing Digital", "description": "Aprenda marketing digital do zero ao avançado", "image": "https://meusite.com/imagens/curso-marketing.jpg", "value": 297.00, "pix": true, "creditCard": true, "boleto": true, "installments": 12 }' ``` ```javascript JavaScript theme={null} const response = await fetch('https://garu.com.br/api/v1/products', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Curso de Marketing Digital', description: 'Aprenda marketing digital do zero ao avançado', image: 'https://meusite.com/imagens/curso-marketing.jpg', value: 297.00, pix: true, creditCard: true, boleto: true, installments: 12 }) }); const product = await response.json(); console.log(`Link de pagamento: https://garu.com.br/pay/${product.uuid}`); ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/v1/products" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}", "Content-Type": "application/json" } payload = { "name": "Curso de Marketing Digital", "description": "Aprenda marketing digital do zero ao avançado", "image": "https://meusite.com/imagens/curso-marketing.jpg", "value": 297.00, "pix": True, "creditCard": True, "boleto": True, "installments": 12 } response = requests.post(url, json=payload, headers=headers) product = response.json() print(f"Link de pagamento: https://garu.com.br/pay/{product['uuid']}") ``` ```php PHP theme={null} 'Curso de Marketing Digital', 'description' => 'Aprenda marketing digital do zero ao avançado', 'image' => 'https://meusite.com/imagens/curso-marketing.jpg', 'value' => 297.00, 'pix' => true, 'creditCard' => true, 'boleto' => true, 'installments' => 12 ]; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => [ "Authorization: Bearer {$apiKey}", 'Content-Type: application/json' ], CURLOPT_POSTFIELDS => json_encode($payload) ]); $response = curl_exec($ch); $product = json_decode($response, true); echo "Link de pagamento: https://garu.com.br/pay/{$product['uuid']}\n"; ``` ### Exemplo para Assinatura Para produtos com cobrança recorrente, defina `isSubscription: true` e envie `value: 0`. O preço será configurado depois, nos [preços de assinatura](/api-reference/assinaturas/precos/criar), que também seguem o mínimo de R\$ 5,00: ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/products \ -H "Authorization: Bearer sk_test_sua_chave_api" \ -H "Content-Type: application/json" \ -d '{ "name": "Plano Premium", "description": "Acesso completo a todos os recursos", "image": "https://meusite.com/imagens/plano-premium.jpg", "value": 0, "isSubscription": true }' ``` ```javascript JavaScript theme={null} const response = await fetch('https://garu.com.br/api/v1/products', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Plano Premium', description: 'Acesso completo a todos os recursos', image: 'https://meusite.com/imagens/plano-premium.jpg', value: 0, isSubscription: true }) }); const product = await response.json(); // Próximo passo: criar preços de assinatura com o UUID do produto console.log(`Produto criado: ${product.uuid}`); ``` ```python Python theme={null} import requests import os response = requests.post( "https://garu.com.br/api/v1/products", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, json={ "name": "Plano Premium", "description": "Acesso completo a todos os recursos", "image": "https://meusite.com/imagens/plano-premium.jpg", "value": 0, "isSubscription": True } ) product = response.json() # Próximo passo: criar preços de assinatura com o UUID do produto print(f"Produto criado: {product['uuid']}") ``` Após criar um produto de assinatura, você precisa [criar preços de assinatura](/api-reference/assinaturas/precos/criar) para definir os planos (mensal, anual, etc.) e seus valores. ## Resposta de Sucesso Identificador único usado para o link de pagamento. Nome do produto. Descrição do produto. Preço do produto em Reais. `0` indica um produto sem preço. Se PIX está habilitado. Se cartão de crédito está habilitado. Se boleto está habilitado. Se o Pix Automático (débito recorrente) está habilitado. Lista de parcelas com valores calculados. Status do produto. Se o produto é de assinatura (cobrança recorrente). Data de criação (ISO 8601). Data da última atualização (ISO 8601). ### Exemplo de Resposta (201 Created) ```json theme={null} { "name": "Curso de Marketing Digital", "description": "Aprenda marketing digital do zero ao avançado", "value": 297.00, "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "pix": true, "creditCard": true, "boleto": true, "installments": [ { "quantity": 1, "value": 297.00 }, { "quantity": 2, "value": 153.45 }, { "quantity": 3, "value": 104.94 }, { "quantity": 4, "value": 80.69 }, { "quantity": 5, "value": 66.15 }, { "quantity": 6, "value": 56.52 }, { "quantity": 7, "value": 49.68 }, { "quantity": 8, "value": 44.55 }, { "quantity": 9, "value": 40.59 }, { "quantity": 10, "value": 37.42 }, { "quantity": 11, "value": 34.85 }, { "quantity": 12, "value": 32.72 } ], "isActive": true, "isSubscription": false, "createdAt": "2024-12-24T10:30:00.000Z", "updatedAt": "2024-12-24T10:30:00.000Z" } ``` ## Erros Comuns ```json theme={null} { "statusCode": 400, "timestamp": "2026-09-30T21:00:00.000Z", "path": "/api/v1/products", "message": "O preço mínimo de um produto é R$ 5,00. Use R$ 0,00 para um produto sem preço." } ``` **Solução:** Envie um `value` de pelo menos `5.00`, ou `0` para um produto sem preço. De R$ 0,01 a R$ 4,99 o produto é recusado, e um valor negativo também. **Solução:** `name`, `image` e `value` são obrigatórios. A mensagem de erro indica qual campo faltou. ```json theme={null} { "statusCode": 401, "message": "API key is required" } ``` **Solução:** Verifique se o header `Authorization` está correto. ## Próximos Passos Aprenda a usar e compartilhar o link de pagamento Consulte seus produtos existentes Configure planos de preço para produtos de assinatura Aprenda a configurar cobrança recorrente # Excluir Produto Source: https://docs.garu.com.br/api-reference/produtos/excluir DELETE /api/v1/products/{uuid} Desative um produto existente ## Visão Geral Este endpoint realiza uma **exclusão suave** (soft delete) do produto, definindo `isActive: false`. Os dados do produto são preservados para histórico de transações. Após excluir um produto, o link de pagamento deixará de funcionar. Clientes que tentarem acessar receberão uma mensagem de produto indisponível. ## Parâmetros de Path UUID do produto a ser excluído. ## Exemplo de Requisição ```bash cURL theme={null} curl -X DELETE https://garu.com.br/api/v1/products/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ -H "Authorization: Bearer sk_test_sua_chave_api" ``` ```javascript JavaScript theme={null} const response = await fetch('https://garu.com.br/api/v1/products/a1b2c3d4-e5f6-7890-abcd-ef1234567890', { method: 'DELETE', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } }); const produto = await response.json(); console.log(`Produto ${produto.uuid} desativado (isActive: ${produto.isActive})`); ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/v1/products/a1b2c3d4-e5f6-7890-abcd-ef1234567890" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}" } response = requests.delete(url, headers=headers) produto = response.json() print(f"Produto {produto['uuid']} desativado") ``` ## Resposta de Sucesso **200 OK** - Retorna o produto já desativado, com `isActive: false`. ```json theme={null} { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Curso de Marketing Digital", "value": 297.00, "isActive": false, "updatedAt": "2026-01-15T10:30:00.000Z" } ``` ## Erros Comuns ```json theme={null} { "statusCode": 404, "message": "Product not found" } ``` **Solução:** Verifique se o UUID do produto está correto. ```json theme={null} { "statusCode": 403, "message": "Insufficient permissions" } ``` **Solução:** Você só pode excluir produtos criados com sua conta. ## Comportamento * O produto é marcado como `isActive: false` * O link de pagamento para de funcionar * Os dados permanecem no sistema para histórico * Transações anteriores não são afetadas A desativação é definitiva pela API: não há endpoint de reativação. `isActive` é somente leitura na resposta e **não** pode ser alterado via `PATCH`. Se precisar vender o produto novamente, crie um novo produto. ## Próximos Passos Crie um novo produto Veja todos os seus produtos # Link de Pagamento Source: https://docs.garu.com.br/api-reference/produtos/link-pagamento Obtenha e utilize o link de pagamento para receber de seus clientes ## Visão Geral Após criar um produto, você recebe um `uuid` que é usado para construir o link de pagamento. Este link pode ser compartilhado diretamente com seus clientes para que eles realizem o pagamento. ## Formato do Link ``` https://garu.com.br/pay/{uuid} ``` **Exemplo:** `https://garu.com.br/pay/a1b2c3d4-e5f6-7890-abcd-ef1234567890` O link só vende um produto com preço. Um produto criado com `value: 0` (sem preço) não vende pelo link: a cobrança de R$ 0,00 é recusada. Para vender pelo link, dê a ele um preço de pelo menos R$ 5,00 com [Atualizar Produto](/api-reference/produtos/atualizar). ## Obtendo o Link de Pagamento ### Método 1: Na Criação do Produto Quando você cria um produto via API, a resposta inclui o campo `uuid`: ```javascript theme={null} const response = await fetch('https://garu.com.br/api/v1/products', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Meu Produto', description: 'Descrição do produto', image: 'https://meusite.com/imagens/meu-produto.jpg', value: 99.90, pix: true, creditCard: true, boleto: true, installments: 6 }) }); const product = await response.json(); const paymentLink = `https://garu.com.br/pay/${product.uuid}`; console.log(paymentLink); // https://garu.com.br/pay/a1b2c3d4-e5f6-7890-abcd-ef1234567890 ``` ### Método 2: Consultando um Produto Existente Se você já tem um produto criado, consulte-o pelo UUID: ```bash theme={null} curl -X GET https://garu.com.br/api/v1/products/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ -H "Authorization: Bearer sk_test_sua_chave_api" ``` **Resposta:** ```json theme={null} { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Meu Produto", "value": 99.90, ... } ``` ### Método 3: Listando Todos os Produtos Recupere todos os seus produtos e seus UUIDs: ```bash theme={null} curl -X GET https://garu.com.br/api/v1/products \ -H "Authorization: Bearer sk_test_sua_chave_api" ``` **Resposta:** ```json theme={null} { "data": [ { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Curso de Marketing Digital", "value": 297.00 }, { "uuid": "b2c3d4e5-f6a7-8901-bcde-f23456789012", "name": "E-book Python", "value": 47.00 } ], "count": 2, "totalCount": 2, "totalPages": 1 } ``` ## Utilizando o Link de Pagamento ### Compartilhamento Direto Envie o link diretamente para seus clientes via: * Email * WhatsApp * Redes sociais * SMS ### Incorporando no seu Site ```html Botão HTML theme={null} Comprar Agora - R$ 297,00 ``` ```javascript React theme={null} function BotaoComprar({ productUuid, preco }) { const handleClick = () => { window.location.href = `https://garu.com.br/pay/${productUuid}`; }; return ( ); } // Uso ``` ```javascript Vue theme={null} ``` ### Abrindo em Popup Para uma experiência de checkout mais integrada: ```javascript theme={null} function abrirCheckout(uuid) { const width = 500; const height = 700; const left = (window.innerWidth - width) / 2; const top = (window.innerHeight - height) / 2; window.open( `https://app.garu.com.br/${uuid}`, 'GaruCheckout', `width=${width},height=${height},left=${left},top=${top}` ); } ``` ## URL de Retorno Personalizada Configure para onde o cliente será redirecionado após o pagamento: ```json theme={null} { "name": "Curso de Marketing Digital", "value": 297.00, "pix": true, "creditCard": true, "boleto": true, "installments": 12, "returnUrl": "https://meusite.com.br/obrigado", "returnUrlButtonText": "Acessar Curso" } ``` Após o pagamento, o cliente verá um botão com o texto "Acessar Curso" que redireciona para `https://meusite.com.br/obrigado`. Use a URL de retorno para direcionar o cliente para uma página de agradecimento ou para entregar o acesso ao produto. ## Fluxo Completo ```javascript theme={null} const GARU_BASE_URL = 'https://garu.com.br/pay'; async function criarProdutoEObterLink(dadosProduto) { // 1. Criar o produto const response = await fetch('https://garu.com.br/api/v1/products', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify(dadosProduto) }); if (!response.ok) { throw new Error('Falha ao criar produto'); } const product = await response.json(); // 2. Construir o link de pagamento const paymentLink = `${GARU_BASE_URL}/${product.uuid}`; // 3. Retornar produto e link return { produto: product, linkPagamento: paymentLink, uuid: product.uuid }; } // Uso const resultado = await criarProdutoEObterLink({ name: 'Curso de Marketing Digital', description: 'Aprenda marketing digital', value: 297.00, pix: true, creditCard: true, boleto: true, installments: 12, returnUrl: 'https://meusite.com.br/obrigado', returnUrlButtonText: 'Acessar Curso' }); console.log(`Produto criado: ${resultado.produto.name}`); console.log(`Link de pagamento: ${resultado.linkPagamento}`); ``` ## Boas Práticas Sempre armazene o `uuid` do produto para referência futura. Você precisará dele para construir o link de pagamento. Configure URLs de retorno que entreguem valor ao cliente, como páginas de acesso ao produto ou instruções de próximos passos. Use chaves `sk_test_` para testar todo o fluxo antes de usar chaves de produção. Não dependa apenas do retorno do cliente. Configure webhooks para receber confirmação de pagamento do servidor. ## Próximos Passos Receba notificações de pagamento em tempo real Veja implementações completas em várias linguagens # Listar Produtos Source: https://docs.garu.com.br/api-reference/produtos/listar GET /api/v1/products Consulte todos os seus produtos cadastrados ## Visão Geral Este endpoint retorna uma lista paginada de todos os seus produtos. Você pode filtrar por nome usando o parâmetro `search`. ## Parâmetros de Query Número da página para paginação. Quantidade de itens por página. Filtra os produtos pelo nome. Opcional. ## Exemplo de Requisição ```bash cURL theme={null} curl -X GET "https://garu.com.br/api/v1/products?page=1&limit=10" \ -H "Authorization: Bearer sk_test_sua_chave_api" ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/v1/products?page=1&limit=10', { headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } } ); const { data, count, totalCount, totalPages } = await response.json(); data.forEach(product => { console.log(`${product.name}: https://garu.com.br/pay/${product.uuid}`); }); ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/v1/products" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}" } params = { "page": 1, "limit": 10 } response = requests.get(url, headers=headers, params=params) result = response.json() for product in result['data']: print(f"{product['name']}: https://garu.com.br/pay/{product['uuid']}") ``` ## Resposta de Sucesso Lista de produtos. Total de produtos retornados nesta página. Total de produtos que correspondem ao filtro, somando todas as páginas. Total de páginas disponíveis. ### Exemplo de Resposta (200 OK) ```json theme={null} { "data": [ { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Curso de Marketing Digital", "description": "Aprenda marketing digital do zero", "value": 297.00, "pix": true, "creditCard": true, "boleto": true, "installments": [ { "quantity": 1, "value": 297.00 }, { "quantity": 2, "value": 153.45 } ], "isActive": true, "createdAt": "2024-12-24T10:30:00.000Z" }, { "uuid": "b2c3d4e5-f6a7-8901-bcde-f23456789012", "name": "E-book Python", "description": "Guia completo de Python", "value": 47.00, "pix": true, "creditCard": true, "boleto": false, "installments": [ { "quantity": 1, "value": 47.00 }, { "quantity": 2, "value": 24.30 } ], "isActive": true, "createdAt": "2024-12-23T15:00:00.000Z" } ], "count": 2, "totalCount": 2, "totalPages": 1 } ``` ## Paginação Para percorrer todas as páginas de produtos: ```javascript theme={null} async function listarTodosProdutos() { const produtos = []; let page = 1; let totalPages = 1; do { const response = await fetch( `https://garu.com.br/api/v1/products?page=${page}&limit=50`, { headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } } ); const result = await response.json(); produtos.push(...result.data); totalPages = result.totalPages; page++; } while (page <= totalPages); return produtos; } ``` ## Próximos Passos Atualize os dados de um produto existente Desative um produto # Portal customizado por produto (B2B2C) Source: https://docs.garu.com.br/api-reference/produtos/portal-config Customize a página de pagamento e o portal /minha-area por produto, não só por seller Disponível a partir da **v0.8.0**. Plataformas B2B2C que modelam **professores / coaches / prestadores como Products** sob um único Seller podem ter logo, cor e mensagens próprias por profissional, sem fragmentar a contabilidade do seller. ## Quando usar * SaaS de coaching que cobra por professor * Plataforma de cursos onde cada instrutor tem branding próprio * Marketplace administrativo onde vendedores são representados como Products Para casos B2C simples (uma única marca por seller), use a configuração de portal a nível de seller. ## Endpoints Base: `/api/v1/products/{uuid}/portal-config` `{uuid}` é o identificador do produto — estável e não-enumerável, retornado por `GET /api/v1/products` e por `list_products` no MCP. A API v1 é indexada por UUID. ### `GET` — ler config atual Retorna a config persistida ou `null` se o produto não tem customização (cai no fallback do seller). ### `POST` / `PATCH` — upsert com merge **Mesma semântica em ambos**: só os campos enviados são gravados; os omitidos preservam o valor anterior. Mande `null` num campo para herdar do seller. ```json theme={null} { "businessName": "Coach Maria — Corrida & Trilha", "primaryColor": "#257264", "logoUrl": "https://cdn.exemplo.com/coaches/maria.png", "customWelcomeText": "Bem-vinda à equipe da Maria!" } ``` ### `DELETE` — limpar tudo Remove a row inteira; o produto volta a usar a config do seller. ## Campos ### Branding | Campo | Tipo | Descrição | | - | - | - | | `businessName` | string \| null | Nome exibido no header da página de pagamento | | `logoUrl` | string \| null | URL HTTPS de logo (max 500 chars) | | `primaryColor` | string \| null | Cor primária em hex (`#257264` ou `#abc`) | ### Políticas do portal `/minha-area` | Campo | Tipo | Default herdado | | - | - | - | | `allowCancelSubscription` | boolean \| null | seller | | `allowUpdatePaymentMethod` | boolean \| null | seller | | `allowUpdateBillingInfo` | boolean \| null | seller | | `allowViewInvoices` | boolean \| null | seller | | `allowApplyCoupons` | boolean \| null | seller | | `requireCancelReason` | boolean \| null | seller | | `cancelAtPeriodEndOnly` | boolean \| null | seller | ### E-mails transacionais | Campo | Tipo | | - | - | | `sendCancellationEmail` | boolean \| null | | `sendPaymentMethodUpdatedEmail` | boolean \| null | ### Mensagens customizadas | Campo | Tipo | Limite | | - | - | - | | `customSuccessMessage` | string \| null | 500 chars | | `customCancellationMessage` | string \| null | 500 chars | | `customWelcomeText` | string \| null | 500 chars | ## Exemplo: SaaS de coaching ```bash theme={null} # Coach 1 (Maria) — verde, com mensagem própria curl -X POST https://garu.com.br/api/v1/products/a1b2c3d4-e5f6-7890-abcd-ef1234567890/portal-config \ -H "Authorization: Bearer $GARU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "businessName": "Coach Maria — Corrida & Trilha", "primaryColor": "#257264", "logoUrl": "https://cdn.atletia.com.br/coaches/maria.png", "customWelcomeText": "Bem-vinda ao plano de corrida!" }' # Coach 2 (Jefter) — laranja, sem custom message (herda do seller) curl -X POST https://garu.com.br/api/v1/products/b2c3d4e5-f6a7-8901-bcde-f23456789012/portal-config \ -H "Authorization: Bearer $GARU_API_KEY" \ -d '{ "businessName": "Jefter — Instrutor de Corridas", "primaryColor": "#CB9454", "logoUrl": "https://cdn.atletia.com.br/coaches/jefter.png" }' ``` Quando o cliente paga um produto do Jefter, ele vê laranja + nome dele. Pagando um da Maria, vê verde + nome dela. Mas a transação aparece para o seller "Atletia" no dashboard — uma única conta, uma única contabilidade. ## SDK ```typescript theme={null} import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY! }); // O SDK ainda assina como `number`; chame com o id numérico ou faça a chamada // direta via REST se preferir o UUID. Wrapper UUID-first vem num release próximo. await garu.products.portalConfig.set(57, { businessName: 'Coach Maria — Corrida & Trilha', primaryColor: '#257264' }); ``` ## MCP Agentes podem customizar o portal por produto direto pela ferramenta: * `set_product_portal_config` — upsert com merge * `get_product_portal_config` — ler config atual * `clear_product_portal_config` — limpar tudo # Consultar Taxas Negociadas Source: https://docs.garu.com.br/api-reference/seller-config/taxas GET /api/seller-config/me Retorna as taxas efetivas do seu cadastro ## Visão Geral Consulte as taxas negociadas para a sua conta — PIX, cartão de crédito, boleto, crédito internacional, chargeback e saque. Este endpoint é **somente leitura**. Para renegociar taxas, fale com o seu account. As taxas exibidas em `effective` são o que de fato é cobrado em cada transação. ## Exemplo de Requisição ```bash cURL theme={null} curl -X GET "https://garu.com.br/api/seller-config/me" \ -H "Authorization: Bearer sk_test_sua_chave_api" ``` ```javascript JavaScript theme={null} const response = await fetch('https://garu.com.br/api/seller-config/me', { headers: { Authorization: `Bearer ${process.env.GARU_API_KEY}`, }, }); const fees = await response.json(); console.log(`PIX: ${(fees.effective.pix.feePercent * 100).toFixed(2)}% + R$ ${fees.effective.pix.feeFixed}`); console.log(`Crédito: ${(fees.effective.credit.feePercent * 100).toFixed(2)}% + R$ ${fees.effective.credit.feeFixed}`); ``` ```python Python theme={null} import requests import os response = requests.get( "https://garu.com.br/api/seller-config/me", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, ) fees = response.json() pix = fees["effective"]["pix"] print(f"PIX: {pix['feePercent'] * 100:.2f}% + R$ {pix['feeFixed']}") ``` ## Resposta Taxas efetivas aplicadas a cada método de pagamento. PIX doméstico: `feePercent` (decimal), `feeFixed` (BRL), `dplusX`, `reservePercent`, `reserveDays`. Cartão de crédito doméstico — mesma estrutura. Boleto doméstico — mesma estrutura. Cartão de crédito internacional. `feeFixed` em USD. Taxa de chargeback em BRL. Taxa de saque em BRL. Quando suas taxas foram atualizadas pela última vez (ISO 8601). `null` se você está nas taxas padrão da plataforma. ## Exemplo de Resposta ```json theme={null} { "effective": { "pix": { "feePercent": 0.0099, "feeFixed": 0.10, "dplusX": 1, "reservePercent": 0, "reserveDays": 0 }, "credit": { "feePercent": 0.0399, "feeFixed": 0.49, "dplusX": 15, "reservePercent": 0.04, "reserveDays": 30 }, "boleto": { "feePercent": 0.0199, "feeFixed": 2.49, "dplusX": 2, "reservePercent": 0, "reserveDays": 0 }, "intlCredit": { "feePercent": 0.0699, "feeFixed": 0.30, "dplusX": 30, "reservePercent": 0.10, "reserveDays": 45 }, "chargebackFee": 50.0, "withdrawalFee": 3.5 }, "updatedAt": "2026-03-12T14:00:00.000Z" } ``` ## Respostas de Erro Token JWT ausente ou inválido. Sem contexto de vendedor selecionado, ou sem permissão `billing:view`. # Cancelar Transação Source: https://docs.garu.com.br/api-reference/transacoes/cancelar DELETE /api/transactions/{id} Rota interna do painel. Para cancelar uma cobrança pela API, use DELETE /api/v1/charges/{uuid} **Esta é uma rota interna do painel, não da API pública.** Ela aceita apenas a sessão do painel, não uma chave de API (`sk_test_` / `sk_live_`), e pode mudar sem aviso. Para cancelar uma cobrança pela API, use [`DELETE /api/v1/charges/{uuid}`](/api-reference/cobrancas/cancelar). Ela identifica a cobrança pelo `uuid` e só cancela cobranças da sua conta. ## Visão Geral Cancela uma transação que ainda não foi paga (`pendingBoleto` ou `pendingPix`). É a rota que o painel usa no botão de cancelar. O `{id}` da rota é o `galaxPayId` da transação, o identificador dela no processador de pagamento. Não é o `id` interno da transação nem o `uuid` da cobrança. A rota só cancela transações da sua conta. Uma transação que não existe ou que é de outra conta responde `404` com a mensagem "Transação não encontrada", sem cancelar nada. Para transações já pagas, use o [estorno](/api-reference/cobrancas/reembolsar), não o cancelamento. ## Cancelar pela API pública O equivalente na API pública é [Cancelar Cobrança](/api-reference/cobrancas/cancelar): ```bash cURL theme={null} curl -X DELETE https://garu.com.br/api/v1/charges/6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f \ -H "Authorization: Bearer sk_test_sua_chave_api" ``` ```javascript JavaScript theme={null} const response = await fetch( `https://garu.com.br/api/v1/charges/${chargeUuid}`, { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.GARU_API_KEY}` } } ); const { canceled } = await response.json(); ``` ```python Python theme={null} import os import requests response = requests.delete( f"https://garu.com.br/api/v1/charges/{charge_uuid}", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, ) print(response.json()["canceled"]) ``` **Resposta (200 OK):** ```json theme={null} { "canceled": true } ``` ## Resposta da rota interna (200 OK) ```json theme={null} true ``` ## Erros Comuns **Solução:** Esta rota não aceita chave de API. Use [`DELETE /api/v1/charges/{uuid}`](/api-reference/cobrancas/cancelar). ```json theme={null} { "statusCode": 404, "timestamp": "2026-09-30T21:00:00.000Z", "path": "/api/transactions/12345", "message": "Transação não encontrada" } ``` **Solução:** Confira se o `{id}` é o `galaxPayId` de uma transação **da sua conta**. Uma transação de outra conta responde exatamente como uma que não existe. **Problema:** A transação foi processada por outro provedor de pagamento, e o cancelamento por esta rota ainda não está disponível para ele. **Solução:** Cancele a cobrança direto no painel do provedor. ## Exemplo: Cancelar Boletos Pendentes Antigos Pela API pública, liste as cobranças pendentes e cancele cada uma pelo `uuid`: ```javascript theme={null} async function cancelarBoletosPendentesAntigos(diasAntigos = 7) { const limite = new Date(Date.now() - diasAntigos * 24 * 60 * 60 * 1000); const params = new URLSearchParams({ status: 'pending', paymentMethod: 'boleto', createdBefore: limite.toISOString(), limit: '100' }); const response = await fetch(`https://garu.com.br/api/v1/charges?${params}`, { headers: { Authorization: `Bearer ${process.env.GARU_API_KEY}` } }); const { data: cobrancas } = await response.json(); const canceladas = []; for (const cobranca of cobrancas) { await fetch(`https://garu.com.br/api/v1/charges/${cobranca.uuid}`, { method: 'DELETE', headers: { Authorization: `Bearer ${process.env.GARU_API_KEY}` } }); canceladas.push(cobranca.uuid); } console.log(`${canceladas.length} cobranças canceladas`); return canceladas; } ``` ## Próximos Passos Cancele uma cobrança pela API pública Consulte as cobranças da sua conta # Detalhes da Transação Source: https://docs.garu.com.br/api-reference/transacoes/detalhes GET /api/transactions/{id} Consulte os detalhes completos de uma transação específica ## Visão Geral Este endpoint retorna os detalhes completos de uma transação específica, incluindo informações do cliente, produto, método de pagamento e status. ## Exemplo de Requisição ```bash cURL theme={null} curl -X GET https://garu.com.br/api/transactions/12345 \ -H "Authorization: Bearer sk_test_sua_chave_api" ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/transactions/12345', { headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } } ); const transaction = await response.json(); console.log(`Status: ${transaction.status}`); console.log(`Cliente: ${transaction.customer.name}`); ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/transactions/12345" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}" } response = requests.get(url, headers=headers) transaction = response.json() print(f"Status: {transaction['status']}") print(f"Cliente: {transaction['customer']['name']}") ``` ## Resposta de Sucesso ID interno da transação na Garu. ID da transação no gateway de pagamento. Status atual da transação. Valor total da transação em Reais. Valor líquido que o vendedor receberá. Método de pagamento: `creditcard`, `pix`, `boleto`. Número de parcelas (apenas para cartão de crédito). Dados do cliente que realizou a compra. Dados do produto comprado. Se a transação faz parte de uma assinatura recorrente. ID da assinatura (se `isRecurring` for `true`). ### Exemplo de Resposta (200 OK) ```json theme={null} { "id": 12345, "galaxPayId": 987654, "status": "captured", "value": 297.00, "valueSeller": 297.00, "paymentMethod": "creditcard", "installments": 3, "date": "2024-12-24T14:30:00.000Z", "deadline": "2024-12-24T14:30:00.000Z", "customer": { "id": 456, "name": "João Silva", "email": "joao@example.com", "document": "12345678901", "phone": "11999887766" }, "product": { "id": 123, "name": "Curso de Marketing Digital", "value": 297.00, "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }, "isRecurring": false, "subscriptionId": null, "billingCycle": null, "createdAt": "2024-12-24T14:30:00.000Z", "updatedAt": "2024-12-24T14:35:00.000Z" } ``` ## Verificando se o Pagamento foi Confirmado ```javascript theme={null} async function verificarPagamento(transactionId) { const response = await fetch( `https://garu.com.br/api/transactions/${transactionId}`, { headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } } ); const transaction = await response.json(); // Status que indicam pagamento confirmado const paidStatuses = ['captured', 'payedBoleto', 'payedPix']; const isPaid = paidStatuses.includes(transaction.status); return { isPaid, status: transaction.status, value: transaction.value, customer: transaction.customer, product: transaction.product }; } // Uso const result = await verificarPagamento(12345); if (result.isPaid) { console.log(`Pagamento confirmado para ${result.customer.name}`); // Liberar acesso ao produto } ``` ## Próximos Passos Endpoint público para verificar status Emita reembolsos totais ou parciais # Listar Transações Source: https://docs.garu.com.br/api-reference/transacoes/listar GET /api/transactions Consulte todas as transações com filtros e paginação ## Visão Geral Este endpoint retorna uma lista paginada de todas as suas transações. Você pode filtrar por status, produto, cliente e buscar por nome ou email. ## Exemplo de Requisição ```bash cURL theme={null} curl -X GET "https://garu.com.br/api/transactions?page=1&limit=20" \ -H "Authorization: Bearer sk_test_sua_chave_api" ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/transactions?page=1&limit=20', { headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } } ); const { data, count, totalPages } = await response.json(); data.forEach(tx => { console.log(`${tx.id}: ${tx.status} - R$ ${tx.value}`); }); ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/transactions" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}" } params = { "page": 1, "limit": 20 } response = requests.get(url, headers=headers, params=params) result = response.json() for tx in result['data']: print(f"{tx['id']}: {tx['status']} - R$ {tx['value']}") ``` ## Parâmetros de Query Número da página para paginação. Quantidade de itens por página (máximo 100). Filtrar por status: `pendingBoleto`, `pendingPix`, `captured`, `payedBoleto`, `payedPix`, `denied`, `reversed`, `cancel`. Filtrar por ID do produto. Filtrar por ID do cliente. Buscar por nome ou email do cliente. ## Resposta de Sucesso Lista de transações. Total de transações retornadas nesta página. Página atual. Total de páginas disponíveis. ### Exemplo de Resposta (200 OK) ```json theme={null} { "data": [ { "id": 12345, "galaxPayId": 987654, "status": "captured", "value": 297.00, "paymentMethod": "creditcard", "customer": { "id": 456, "name": "João Silva", "email": "joao@example.com" }, "product": { "id": 123, "name": "Curso de Marketing Digital" }, "createdAt": "2024-12-24T14:30:00.000Z" }, { "id": 12344, "galaxPayId": 987653, "status": "payedPix", "value": 47.00, "paymentMethod": "pix", "customer": { "id": 457, "name": "Maria Santos", "email": "maria@example.com" }, "product": { "id": 124, "name": "E-book Python" }, "createdAt": "2024-12-23T10:15:00.000Z" } ], "count": 2, "page": 1, "totalPages": 5 } ``` ## Status de Transação | Status | Descrição | | - | - | | `pendingBoleto` | Boleto gerado, aguardando pagamento | | `pendingPix` | Código PIX gerado, aguardando pagamento | | `authorized` | Cartão de crédito autorizado | | `captured` | Pagamento com cartão capturado | | `payedBoleto` | Pagamento via boleto confirmado | | `payedPix` | Pagamento via PIX confirmado | | `denied` | Pagamento negado | | `reversed` | Pagamento estornado/reembolsado | | `cancel` | Transação cancelada | ## Paginação Para percorrer todas as transações: ```javascript theme={null} async function listarTodasTransacoes(status) { const transacoes = []; let page = 1; let totalPages = 1; do { const url = new URL('https://garu.com.br/api/transactions'); url.searchParams.set('page', page); url.searchParams.set('limit', 50); if (status) url.searchParams.set('status', status); const response = await fetch(url, { headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}` } }); const result = await response.json(); transacoes.push(...result.data); totalPages = result.totalPages; page++; } while (page <= totalPages); return transacoes; } // Listar todas as transações pagas const pagas = await listarTodasTransacoes('captured'); ``` ## Próximos Passos Consulte os detalhes completos de uma transação Emita reembolsos totais ou parciais # Reembolsar Transação Source: https://docs.garu.com.br/api-reference/transacoes/reembolsar POST /api/transactions/{id}/refund Emita reembolsos totais ou parciais para transações pagas ## Visão Geral Emita um reembolso total ou parcial para uma transação que já foi paga. O valor será devolvido ao cliente através do mesmo método de pagamento utilizado na compra. ## Exemplo de Requisição ### Reembolso Total ```bash cURL theme={null} curl -X POST https://garu.com.br/api/transactions/12345/refund \ -H "Authorization: Bearer sk_test_sua_chave_api" \ -H "Content-Type: application/json" \ -d '{ "reason": "Cliente solicitou reembolso" }' ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/transactions/12345/refund', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ reason: 'Cliente solicitou reembolso' }) } ); const result = await response.json(); console.log(`Status: ${result.status}`); // "reversed" ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/transactions/12345/refund" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}", "Content-Type": "application/json" } payload = { "reason": "Cliente solicitou reembolso" } response = requests.post(url, json=payload, headers=headers) result = response.json() print(f"Status: {result['status']}") # "reversed" ``` ### Reembolso Parcial ```bash cURL theme={null} curl -X POST https://garu.com.br/api/transactions/12345/refund \ -H "Authorization: Bearer sk_test_sua_chave_api" \ -H "Content-Type: application/json" \ -d '{ "amount": 100.00, "reason": "Desconto por insatisfação parcial" }' ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/transactions/12345/refund', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ amount: 100.00, reason: 'Desconto por insatisfação parcial' }) } ); const result = await response.json(); console.log(`Valor reembolsado: R$ ${result.refundedAmount}`); ``` ```python Python theme={null} import requests import os url = "https://garu.com.br/api/transactions/12345/refund" headers = { "Authorization": f"Bearer {os.environ['GARU_API_KEY']}", "Content-Type": "application/json" } payload = { "amount": 100.00, "reason": "Desconto por insatisfação parcial" } response = requests.post(url, json=payload, headers=headers) result = response.json() print(f"Valor reembolsado: R$ {result['refundedAmount']}") ``` ## Parâmetros Valor do reembolso em Reais. Se não especificado, o valor total da transação será reembolsado. Motivo do reembolso (opcional, para registro interno). ## Resposta de Sucesso (200 OK) ```json theme={null} { "id": 12345, "status": "reversed", "value": 297.00, "refundedAmount": 100.00 } ``` ID da transação reembolsada. Novo status da transação (`reversed`). Valor original da transação. Valor que foi reembolsado. ## Considerações Reembolsos podem levar alguns dias úteis para serem processados pelo gateway de pagamento e aparecerem na conta do cliente. * Apenas transações com status `captured`, `payedBoleto` ou `payedPix` podem ser reembolsadas * Reembolsos parciais podem ser feitos múltiplas vezes até atingir o valor total * O valor do reembolso não pode exceder o valor original da transação ## Erros Comuns ```json theme={null} { "statusCode": 400, "message": "Refund amount exceeds transaction value" } ``` **Solução:** O valor do reembolso não pode ser maior que o valor original da transação. ```json theme={null} { "statusCode": 400, "message": "Transaction cannot be refunded" } ``` **Solução:** Apenas transações pagas podem ser reembolsadas. Verifique o status da transação. ## Próximos Passos Cancele transações pendentes Receba notificações de reembolso # Consultar Status Source: https://docs.garu.com.br/api-reference/transacoes/status GET /api/transactions/status/{galaxPayId} Endpoint público para verificar o status de uma transação ## Visão Geral Este é um endpoint **público** (não requer autenticação) que retorna apenas o status de uma transação. Use quando precisar apenas verificar se um pagamento foi confirmado, sem os detalhes completos. Este endpoint usa o `galaxPayId` (ID do gateway de pagamento), não o ID interno da transação. ## Exemplo de Requisição ```bash cURL theme={null} curl -X GET https://garu.com.br/api/transactions/status/987654 ``` ```javascript JavaScript theme={null} const response = await fetch( 'https://garu.com.br/api/transactions/status/987654' ); const status = await response.text(); console.log(`Status: ${status}`); // "captured" ``` ```python Python theme={null} import requests url = "https://garu.com.br/api/transactions/status/987654" response = requests.get(url) status = response.text print(f"Status: {status}") # "captured" ``` ## Resposta de Sucesso (200 OK) ``` "captured" ``` A resposta é uma string simples com o status da transação. ## Status Possíveis | Status | Descrição | Pagamento Confirmado? | | - | - | - | | `pendingBoleto` | Boleto gerado, aguardando pagamento | Não | | `pendingPix` | Código PIX gerado, aguardando pagamento | Não | | `authorized` | Cartão de crédito autorizado | Não | | `captured` | Pagamento com cartão capturado | **Sim** | | `payedBoleto` | Pagamento via boleto confirmado | **Sim** | | `payedPix` | Pagamento via PIX confirmado | **Sim** | | `denied` | Pagamento negado | Não | | `reversed` | Pagamento estornado/reembolsado | Não | | `cancel` | Transação cancelada | Não | ## Exemplo: Verificação de Pagamento ```javascript theme={null} async function isPaid(galaxPayId) { const response = await fetch( `https://garu.com.br/api/transactions/status/${galaxPayId}` ); const status = await response.text(); const paidStatuses = ['captured', 'payedBoleto', 'payedPix']; return paidStatuses.includes(status.replace(/"/g, '')); } // Uso if (await isPaid(987654)) { console.log('Pagamento confirmado!'); // Liberar acesso } else { console.log('Pagamento ainda não confirmado'); } ``` ## Quando Usar Este Endpoint * **Página de confirmação**: Verificar se o pagamento foi confirmado após redirecionamento * **Polling**: Verificar periodicamente o status de um pagamento pendente * **Integrações simples**: Quando não precisa de detalhes, apenas do status Para obter informações completas da transação (cliente, produto, valor), use o endpoint [Detalhes da Transação](/api-reference/transacoes/detalhes). ## Próximos Passos Obtenha informações completas da transação Receba notificações em tempo real # Solução de Problemas Source: https://docs.garu.com.br/api-reference/troubleshooting Resolva erros comuns e problemas de integração ## Erros de Autenticação ### 401 Unauthorized ```json theme={null} { "statusCode": 401, "message": "API key is required" } ``` **Problema:** A requisição não inclui o header `Authorization`. **Solução:** Adicione o header com sua chave de API: ```bash theme={null} -H "Authorization: Bearer sk_test_sua_chave_api" ``` **Problema:** A chave está sendo enviada sem o prefixo `Bearer`. **Errado:** ```bash theme={null} -H "Authorization: sk_test_sua_chave_api" ``` **Correto:** ```bash theme={null} -H "Authorization: Bearer sk_test_sua_chave_api" ``` **Problema:** A chave não existe ou foi revogada. **Solução:** 1. Acesse o [Dashboard](https://garu.com.br/inicio) 2. Vá para **Configurações** → **Desenvolvedores** 3. Verifique se a chave está ativa ou crie uma nova ### 403 Forbidden ```json theme={null} { "statusCode": 403, "message": "Insufficient permissions" } ``` **Problema:** Sua chave de API não tem permissão para acessar este recurso. **Solução:** Verifique as permissões da chave no Dashboard ou crie uma nova chave com as permissões necessárias. **Problema:** Você está tentando acessar ou modificar um produto que não pertence à sua conta. **Solução:** Verifique se o ID ou UUID do produto está correto e pertence à sua conta. ## Erros de Validação ### 400 Bad Request ```json theme={null} { "statusCode": 400, "timestamp": "2026-09-30T21:00:00.000Z", "path": "/api/v1/products", "message": "O preço mínimo de um produto é R$ 5,00. Use R$ 0,00 para um produto sem preço." } ``` **Problema:** O preço está entre R$ 0,01 e R$ 4,99. O preço mínimo da plataforma é R\$ 5,00, e ele vale para o `value` de um produto, o `value` de uma oferta e o valor de um preço de assinatura. **Solução:** Defina um valor de pelo menos 5.00, em **Reais** (não em centavos): ```json theme={null} { "value": 5.00 } ``` Um **produto** também aceita `"value": 0`, que o deixa sem preço (ele não vende pelo link de pagamento). Uma oferta ou um preço de assinatura não aceitam `0`. Um produto que já custava menos de R\$ 5,00 continua vendendo. Ao atualizá-lo, o erro só aparece se o pedido enviar `value`. **Problema:** Campos obrigatórios não foram enviados. **Campos obrigatórios para criar produto** (`POST /api/v1/products`): * `name` (string) * `image` (string, URL da imagem) * `value` (number, em Reais: `0` ou pelo menos `5.00`) **Problema:** O número de parcelas está fora do intervalo permitido. **Solução:** Use um valor entre 1 e 12: ```json theme={null} { "installments": 12 } ``` **Problema:** Pelo menos um método de pagamento deve estar ativo. **Solução:** Habilite pelo menos um: ```json theme={null} { "pix": true, "creditCard": false, "boleto": false } ``` ## Erros de Limite ### 429 Too Many Requests ```json theme={null} { "statusCode": 429, "message": "Muitas tentativas. Por favor, tente novamente mais tarde." } ``` **Problema:** Você excedeu o limite de 100 requisições por minuto. **Solução:** Implemente retry com backoff exponencial: ```javascript theme={null} async function requisicaoComRetry(fn, tentativas = 3) { for (let i = 0; i < tentativas; i++) { try { return await fn(); } catch (error) { if (error.status === 429 && i < tentativas - 1) { // Espera exponencial: 1s, 2s, 4s... const espera = Math.pow(2, i) * 1000; await new Promise(r => setTimeout(r, espera)); continue; } throw error; } } } ``` ## Erros de Recurso ### 404 Not Found ```json theme={null} { "statusCode": 404, "message": "Product not found" } ``` **Problema:** O ID ou UUID do produto não existe. **Soluções:** 1. Verifique se o ID/UUID está correto 2. O produto pode ter sido excluído 3. Liste seus produtos para confirmar os IDs disponíveis ## Problemas com Webhooks ### Webhook não está sendo recebido **Problema:** Seu servidor não está acessível pela internet. **Soluções:** 1. Verifique se o endpoint está online 2. Use HTTPS (HTTP não é suportado) 3. Para testes locais, use [ngrok](https://ngrok.com/) **Problema:** Seu endpoint demora mais de 5 segundos para responder. **Solução:** Responda imediatamente com 200 e processe assincronamente: ```javascript theme={null} app.post('/webhook', (req, res) => { // Responde imediatamente res.status(200).json({ received: true }); // Processa em background processarAsync(req.body); }); ``` **Problema:** Seu endpoint retorna erro (4xx ou 5xx). **Solução:** Verifique os logs do servidor e corrija os erros. A Garu tentará reenviar até 5 vezes. ### Assinatura inválida **Problema:** O segredo usado para validar não corresponde. **Solução:** 1. Acesse **Configurações** → **Webhooks** no Dashboard 2. Copie o segredo correto 3. Atualize a variável de ambiente `WEBHOOK_SECRET` **Problema:** O corpo da requisição foi alterado antes da validação. **Solução:** Use o payload raw, não o parseado: ```javascript theme={null} // Express.js app.use('/webhook', express.raw({ type: 'application/json' })); app.post('/webhook', (req, res) => { const rawBody = req.body.toString(); // Valida com rawBody, não com JSON.parse }); ``` ## Problemas com Pagamento ### Link de pagamento não funciona **Problema:** O produto foi excluído (soft delete). **Solução:** Reative o produto ou crie um novo. **Problema:** O UUID na URL está errado. **Solução:** Verifique o UUID correto listando seus produtos: ```bash theme={null} curl -X GET https://garu.com.br/api/v1/products \ -H "Authorization: Bearer sk_test_xxx" ``` ## Checklist de Integração Use este checklist antes de ir para produção: * [ ] Chave de API armazenada em variável de ambiente * [ ] Usando `sk_test_` em desenvolvimento * [ ] Testou com `sk_live_` em staging * [ ] Todos os campos obrigatórios preenchidos * [ ] Preço de pelo menos R\$ 5,00, em Reais (ou `0` só para produto que não vende pelo link) * [ ] Pelo menos um método de pagamento ativo * [ ] UUID sendo salvo no banco de dados * [ ] Endpoint configurado com HTTPS * [ ] Validação de assinatura implementada * [ ] Resposta rápida (\< 5 segundos) * [ ] Processamento assíncrono para tarefas longas * [ ] Idempotência para evitar duplicações * [ ] Retry com backoff para erros 429 * [ ] Logs para debugging * [ ] Alertas para erros críticos ## Precisa de Ajuda? Se você não conseguiu resolver seu problema: 1. Verifique a [especificação OpenAPI](https://garu.com.br/api/openapi.json) 2. Acesse o suporte no Dashboard 3. Envie um email para **[contato@garu.com.br](mailto:contato@garu.com.br)** Entre em contato com nossa equipe de suporte técnico # Webhooks Source: https://docs.garu.com.br/api-reference/webhooks Receba notificações em tempo real sobre eventos de pagamento ## Visão Geral Webhooks permitem que sua aplicação receba notificações automáticas quando eventos importantes acontecem, como quando um pagamento é confirmado ou um produto é criado. ## Configurando Webhooks Faça login no [Dashboard da Garu](https://garu.com.br/inicio) Vá para **Configurações** → **Webhooks** Clique em **Adicionar webhook** e insira a URL do seu endpoint Escolha quais eventos deseja receber Salve o segredo do webhook para validar as assinaturas ## Eventos Disponíveis ### Eventos de Produto | Evento | Descrição | | - | - | | `product.created` | Um novo produto foi criado | | `product.updated` | Um produto foi atualizado | | `product.deleted` | Um produto foi desativado | ### Eventos de Transação | Evento | Descrição | | - | - | | `transaction.created` | Uma nova transação foi iniciada | | `transaction.updated` | Uma transação teve seus dados atualizados | | `transaction.payment.succeeded` | Pagamento confirmado | | `transaction.payment.failed` | Pagamento recusado (com `failureCode` normalizado — ver [Códigos de falha](/api-reference/webhooks/codigos-de-falha)) | | `transaction.canceled` | Transação cancelada (pelo seller, contrato ou operadora) | | `transaction.chargeback` | Chargeback registrado pelo emissor | | `transaction.refunded` | Pagamento estornado | O evento legado `transaction.failed` continua sendo entregue para endpoints que já o assinam, mas a partir da v0.8.0 prefira `transaction.payment.failed` / `transaction.canceled` / `transaction.chargeback` — eles separam falha técnica, cancelamento e chargeback. ### Eventos de Cobrança Agendada (Scheduled Charge) | Evento | Descrição | | - | - | | `scheduled_charge.created` | Cobrança agendada criada | | `scheduled_charge.updated` | Dados da série atualizados | | `scheduled_charge.canceled` | Cobrança avulsa cancelada | | `scheduled_charge.paid` | Ciclo da série pago (carrega `seriesId` + `cycleNumber`) | | `scheduled_charge.overdue` | Ciclo entrou em atraso | | `scheduled_charge.cycle_failed` | Retries de cartão esgotados (carrega `failureCode`, `failureReason`, `gatewayFailureCode`) | | `scheduled_charge.recurrence_canceled` | Recorrência cancelada (cancela ciclos futuros) | | `scheduled_charge.cancel_at_period_end_set` | Soft-cancel ativado | | `scheduled_charge.cancel_at_period_end_cleared` | Soft-cancel revertido | | `scheduled_charge.payment_method_attached` | Cartão salvo no ciclo 1 | | `scheduled_charge.payment_method_changed` | Cartão trocado | | `scheduled_charge.payment_method_cleared` | Cartão removido (DELETE /:id/payment-method) | ### Eventos de Customer (Trial) | Evento | Descrição | | - | - | | `customer.trial_started` | Trial iniciado | | `customer.trial_converted` | Trial converteu em pagamento | | `customer.trial_lapsed` | Trial expirou sem pagamento | ### Eventos de Payment Method (Cartão Salvo) | Evento | Descrição | | - | - | | `payment_method.expiring_soon` | Cartão vence em D-30, D-14 ou D-7 (um disparo por estágio) | | `payment_method.expired` | Cartão venceu — Garu flippou o status para `expired` automaticamente | ### Eventos de Assinatura | Evento | Descrição | | - | - | | `subscription.created` | Nova assinatura criada | | `subscription.activated` | Assinatura ativada | | `subscription.paused` | Assinatura pausada | | `subscription.cancelled` | Assinatura cancelada | | `subscription.renewed` | Assinatura renovada | **[Pix Automático](/guias/pix-automatico) não cria eventos novos.** Assinaturas e cobranças no Pix Automático disparam exatamente os mesmos eventos de assinatura e transação que o cartão. Para diferenciar a origem, verifique `paymentMethod === 'pix_automatic'` no payload — o campo `paymentMethod` aceita `pix`, `boleto`, `card` e `pix_automatic`. ### Eventos de Checkout Session | Evento | Descrição | | - | - | | `checkout.session.completed` | Pagamento da checkout session foi concluído | | `checkout.session.expired` | Checkout session expirou sem pagamento | ## Formato do Payload Todos os webhooks seguem este formato: ```json theme={null} { "event": "transaction.paid", "timestamp": "2024-12-24T10:30:00.000Z", "data": { // Dados específicos do evento } } ``` ### Exemplo: Pagamento Confirmado ```json theme={null} { "event": "transaction.paid", "timestamp": "2024-12-24T10:30:00.000Z", "data": { "id": 456, "productId": 123, "productName": "Curso de Marketing Digital", "value": 297.00, "paymentMethod": "pix", "customer": { "name": "João Silva", "email": "joao@email.com", "document": "123.456.789-00" }, "paidAt": "2024-12-24T10:30:00.000Z" } } ``` ### Exemplo: Produto Criado ```json theme={null} { "event": "product.created", "timestamp": "2024-12-24T10:30:00.000Z", "data": { "id": 123, "name": "Curso de Marketing Digital", "value": 297.00, "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "sellerId": 456 } } ``` ### Exemplo: Checkout Session Concluída ```json theme={null} { "event": "checkout.session.completed", "timestamp": "2024-12-24T14:30:00.000Z", "data": { "id": "cs_ABC123xyz", "status": "complete", "product_id": 123, "transaction_id": 789, "customer_email": "joao@email.com", "metadata": { "order_id": "12345" }, "client_reference_id": "order_abc123", "completed_at": "2024-12-24T14:30:00.000Z" } } ``` ### Exemplo: Checkout Session Expirada ```json theme={null} { "event": "checkout.session.expired", "timestamp": "2024-12-25T12:00:00.000Z", "data": { "id": "cs_DEF456uvw", "status": "expired", "product_id": 123, "metadata": { "order_id": "67890" }, "client_reference_id": "order_def456", "expires_at": "2024-12-25T12:00:00.000Z" } } ``` ## Validando Assinaturas Para garantir que o webhook veio da Garu, valide a assinatura: ## Boas Práticas Retorne HTTP 200 o mais rápido possível (em até 5 segundos). Processe a lógica de negócio de forma assíncrona. Webhooks podem ser enviados mais de uma vez. Use o ID do evento para evitar processamento duplicado. Configure seu endpoint com HTTPS para garantir a segurança dos dados. Nunca processe webhooks sem validar a assinatura. Isso protege contra ataques. Configure alertas para quando seu endpoint falhar em receber webhooks. ## Retry Policy Se seu endpoint não responder com 2xx, a Garu tentará reenviar com backoff: | Tentativa | Intervalo desde a anterior | | - | - | | 1 | 1 minuto | | 2 | 5 minutos | | 3 | 30 minutos | | 4 | 2 horas | | 5 | 8 horas | | 6 | 16 horas | Após 6 tentativas sem sucesso (\~26h36min de janela total), o webhook é marcado como falho. ## Testando Webhooks Para testar localmente, use ferramentas como: * [ngrok](https://ngrok.com/) - Cria um túnel para seu localhost * [webhook.site](https://webhook.site/) - Inspeciona webhooks recebidos ```bash theme={null} # Usando ngrok ngrok http 3000 # Seu endpoint público será algo como: # https://abc123.ngrok.io/webhooks/garu ``` ## Próximos Passos Implementações completas em várias linguagens Resolva erros comuns # Códigos de falha Source: https://docs.garu.com.br/api-reference/webhooks/codigos-de-falha Enum normalizado GaruFailureCode para rotear falhas de pagamento A partir da **v0.8.0**, toda falha de pagamento traz um código canônico estável (`failureCode`), uma mensagem em português (`failureReason`) e o código bruto do gateway (`gatewayFailureCode`). Você roteia em cima do enum normalizado e ainda tem o código original para auditoria / abertura de chamado com Celcoin. ## Onde aparece | Webhook | Campo | | - | - | | `transaction.payment.failed` | `data.failureCode`, `data.failureReason`, `data.gatewayFailureCode` | | `scheduled_charge.cycle_failed` | `data.failureCode`, `data.failureReason`, `data.gatewayFailureCode` | | `GET /api/v1/scheduled-charges/:id/attempts` | em cada item: `failureCode`, `failureReason`, `gatewayFailureCode` | ## Valores | `failureCode` | Significado | Ação típica | | - | - | - | | `insufficient_funds` | Saldo insuficiente | Aguardar; tentar novamente em horizonte de dias | | `card_declined` | Recusa genérica do emissor | Avisar cliente; sugerir outro cartão | | `card_expired` | Cartão vencido | Coletar novo cartão (a Garu já avisa via `payment_method.expiring_soon`) | | `card_canceled` | Cartão cancelado pelo emissor | Coletar novo cartão; permanente | | `processing_error` | Erro no processamento (gateway / rede / token) | Retry automático cuidará | | `issuer_unavailable` | Emissor indisponível | Retry automático | | `fraud_suspected` | Suspeita de fraude | Não tentar de novo; pedir contato com emissor | | `invalid_cvv` | CVV inválido | Pedir cliente para reentrar dados | | `do_not_honor_repeated` | "Do not honor" persistente | Sugerir outro cartão; emissor recusando | | `unknown` | Não mapeado pela Garu | Inspecionar `gatewayFailureCode` | ## Mapeamento ABECS → Garu A Garu normaliza os códigos ABECS retornados pelo Celcoin. O código original fica em `gatewayFailureCode`: | ABECS | `failureCode` | | - | - | | `00` | (sucesso, não há failureCode) | | `51`, `61`, `65` | `insufficient_funds` | | `54` | `card_expired` | | `41`, `43`, `93` | `fraud_suspected` | | `82`, `N7` | `invalid_cvv` | | `91`, `92`, `96` | `issuer_unavailable` | | `05` (repetido) | `do_not_honor_repeated` | | outros 0x / 1x | `card_declined` | Códigos não-ABECS (mensagem livre do Celcoin) caem em **keyword matching**: a Garu varre a `reasonDenied` por palavras-chave em PT/EN ("vencido", "expired", "insufficient", etc.) e mapeia para o melhor enum. Se nada bate: `unknown`. ## Exemplo ```json theme={null} { "event": "transaction.payment.failed", "data": { "id": 999, "value": 49.9, "failureCode": "insufficient_funds", "failureReason": "Saldo insuficiente", "gatewayFailureCode": "51" } } ``` ## Boas práticas de roteamento `card_expired`, `card_canceled`, `fraud_suspected` → coletar dados novos. Não vale a pena automatizar retry. `insufficient_funds`, `issuer_unavailable`, `processing_error` → a Garu já tenta automaticamente; relaxe. `gatewayFailureCode` muda quando trocamos de gateway por baixo. `failureCode` é estável. Use o enum para automação; logue o gateway code para forensics. Para `scheduled_charge.cycle_failed`, a Garu já tentou em `+6h`, `+24h`, `+48h` antes de emitir o evento. Não tente cobrar de novo programaticamente — peça novo cartão ou aguarde o cliente clicar no e-mail de fallback. # Disparar webhook manualmente Source: https://docs.garu.com.br/api-reference/webhooks/disparar-manualmente POST /api/webhook-endpoints/{id}/trigger Substituto leve de sandbox: dispare um payload de exemplo de qualquer evento já assinado Disponível a partir da **v0.8.1**. Permite disparar um payload de exemplo de qualquer evento Garu para um endpoint específico — útil para validar sua integração ponta-a-ponta antes de tráfego real. ## Pré-condições * O endpoint precisa estar **inscrito no evento** que você quer disparar (configure em **Dashboard → Configurações → Webhooks**). * O `event` precisa pertencer à whitelist (qualquer evento que a Garu emita, exceto `webhook.test` — esse continua atendido pelo endpoint legado `/test`). ## Body ```json theme={null} { "event": "transaction.payment.failed", "payloadOverrides": { "failureCode": "card_expired" } } ``` | Campo | Tipo | Descrição | | - | - | - | | `event` | string | Tipo do evento (ex: `transaction.payment.failed`, `scheduled_charge.cycle_failed`) | | `payloadOverrides` | object | (opcional) Mesclado raso no `data.object` do payload de exemplo | ## Resposta Retorna o `OutgoingWebhookEvent` já entregue, com status, número de tentativas e código HTTP da resposta do seu endpoint. ```json theme={null} { "id": 12345, "endpointId": 7, "eventType": "transaction.payment.failed", "payload": { "id": "evt_test_1730000000_abc", "type": "transaction.payment.failed", "data": { "object": { "id": 999, "value": 49.9, "failureCode": "card_expired", "failureReason": "Cartão vencido", "gatewayFailureCode": "54" } }, "metadata": { "test": true }, "seller_id": 42 }, "status": "delivered", "attempts": 1 } ``` Todo payload disparado por essa rota carrega `metadata.test: true` para que seu receiver possa diferenciar evento real de teste. ## Erros | Status | Caso | | - | - | | 400 | `event` fora da whitelist | | 404 | Endpoint não existe ou não pertence ao seller chamando | | 404 | Endpoint não inscrito no evento informado | ## Exemplo: validar handler de cycle\_failed ```bash theme={null} curl -X POST https://garu.com.br/api/webhook-endpoints/7/trigger \ -H "Authorization: Bearer $GARU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "event": "scheduled_charge.cycle_failed", "payloadOverrides": { "failureCode": "card_expired", "failureReason": "Cartão vencido" } }' ``` # Detalhes do Evento de Webhook Source: https://docs.garu.com.br/api-reference/webhooks/eventos/detalhes GET /api/v1/webhook-events/{uuid} Busque um evento de webhook pelo uuid com payload, tentativas e última resposta do endpoint ## Visão Geral Retorna o evento completo — payload entregue, contador de tentativas e código HTTP da última resposta do seu endpoint. É a chamada que alimenta a página de detalhes do evento no dashboard. ## Exemplo de Requisição ```bash cURL theme={null} curl https://garu.com.br/api/v1/webhook-events/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ -H "Authorization: Bearer sk_live_sua_chave_api" ``` ```javascript JavaScript theme={null} import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY }); const event = await garu.webhookEvents.get('a1b2c3d4-e5f6-7890-abcd-ef1234567890'); console.log(event.status, event.attempts); console.log(event.payload); // o objeto completo enviado ao seu endpoint ``` ```python Python theme={null} import os import requests response = requests.get( "https://garu.com.br/api/v1/webhook-events/a1b2c3d4-e5f6-7890-abcd-ef1234567890", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, ) event = response.json() print(event["status"], event["responseStatus"]) ``` ## Parâmetros de Path UUID do evento, devolvido por `GET /api/v1/webhook-events`. ## Resposta ```json theme={null} { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "webhookEndpoint": { "id": 7, "url": "https://example.com/webhooks/garu", "description": "Produção", "enabled": true, "events": ["transaction.payment.succeeded"] }, "eventType": "transaction.payment.succeeded", "status": "failed", "attempts": 6, "lastAttemptAt": "2026-05-18T14:22:11.000Z", "nextRetryAt": null, "responseStatus": 503, "responseBody": "Service Unavailable", "manualResendOf": null, "createdAt": "2026-05-18T14:22:00.000Z", "payload": { "id": "evt_1a2b3c", "type": "transaction.payment.succeeded", "timestamp": "2026-05-18T14:22:10Z", "data": { "object": { "id": 999, "value": 49.9, "paymentMethod": "pix", "customer": { "email": "joao@email.com" } } } } } ``` ### Campos | Campo | Tipo | Descrição | | - | - | - | | `status` | string | `pending`, `success` ou `failed` | | `attempts` | number | Quantas tentativas foram feitas até agora | | `lastAttemptAt` | string\|null | ISO timestamp da última tentativa (sucesso ou falha) | | `responseStatus` | number\|null | HTTP da última resposta (ex: `200`, `503`, `404`) | | `responseBody` | string\|null | Body da última resposta, truncado | | `manualResendOf` | string\|null | `uuid` do evento original, quando este registro for um clone de reenvio manual (`POST /resend`). `null` em eventos originais. | | `nextRetryAt` | string\|null | Próximo retry agendado, se status `pending` e ainda em janela de retry | | `payload` | object | Payload completo — o mesmo JSON que foi enviado ao seu endpoint | Para reenviar este evento, prefira `POST /api/v1/webhook-events/:uuid/resend` — clona o evento e preserva o original. Veja [Reenviar (clonando)](/api-reference/webhooks/eventos/resend). O endpoint `/retry` (legado) segue disponível. # Listar Eventos de Webhook Source: https://docs.garu.com.br/api-reference/webhooks/eventos/listar GET /api/v1/webhook-events Liste eventos de webhook com filtros de status, tipo e endpoint ## Visão Geral Retorna uma lista paginada dos eventos de webhook emitidos pelo seller autenticado — entregues, pendentes e com falha. É a chamada por trás da aba **Eventos** em **Configurações → Webhooks** do dashboard. ## Exemplo de Requisição ```bash cURL theme={null} # Últimos eventos que falharam curl -X GET "https://garu.com.br/api/v1/webhook-events?status=failed&limit=5" \ -H "Authorization: Bearer sk_live_sua_chave_api" # Eventos de pagamento confirmado de um endpoint específico curl -X GET "https://garu.com.br/api/v1/webhook-events?eventType=transaction.payment.succeeded&endpointId=7" \ -H "Authorization: Bearer sk_live_sua_chave_api" ``` ```javascript JavaScript theme={null} import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY }); // Últimos eventos que falharam const failed = await garu.webhookEvents.list({ status: 'failed', limit: 5 }); // Eventos de um endpoint específico const fromEndpoint = await garu.webhookEvents.list({ endpointId: 7, eventType: 'transaction.payment.succeeded' }); ``` ```python Python theme={null} import os import requests response = requests.get( "https://garu.com.br/api/v1/webhook-events", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, params={"status": "failed", "limit": 5}, ) for event in response.json()["data"]: print(event["uuid"], event["eventType"], event["status"]) ``` ## Parâmetros de Query Filtra por status de entrega. Valores: `pending`, `success`, `failed`. Filtra por tipo de evento (ex: `transaction.payment.succeeded`, `scheduled_charge.cycle_failed`). Use os mesmos identificadores documentados em [Webhooks → Eventos Disponíveis](/api-reference/webhooks). Restringe a eventos entregues a um endpoint específico. Endpoints continuam identificados por ID numérico — a configuração de endpoint (URL, eventos assinados, secret) segue exclusiva do dashboard, não migrou para `/api/v1`. Página da paginação. Itens por página (máx. 100). ## Resposta ```json theme={null} { "data": [ { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "webhookEndpoint": { "id": 7, "url": "https://example.com/webhooks/garu", "description": "Produção", "enabled": true, "events": ["transaction.payment.succeeded"] }, "eventType": "transaction.payment.succeeded", "status": "failed", "attempts": 6, "lastAttemptAt": "2026-05-18T14:22:11.000Z", "nextRetryAt": null, "responseStatus": 503, "responseBody": "Service Unavailable", "manualResendOf": null, "createdAt": "2026-05-18T14:22:00.000Z", "payload": { "id": "evt_1a2b3c", "type": "transaction.payment.succeeded", "data": { "object": { "id": 999, "value": 49.9 } } } } ], "count": 1, "totalCount": 1, "totalPages": 1 } ``` ### Campos | Campo | Tipo | Descrição | | - | - | - | | `uuid` | string | Identificador público do evento (use em `get`, `resend` e `retry`) | | `webhookEndpoint` | object | Snapshot do endpoint de destino — `id` continua numérico | | `eventType` | string | Tipo do evento Garu (ex: `transaction.payment.succeeded`) | | `status` | string | `pending`, `success` ou `failed` | | `attempts` | number | Quantas tentativas de entrega foram feitas | | `lastAttemptAt` | string\|null | ISO timestamp da última tentativa (sucesso ou falha) | | `nextRetryAt` | string\|null | Próximo retry agendado, se `pending` e ainda em janela de retry | | `responseStatus` | number\|null | Código HTTP da última resposta do seu endpoint | | `responseBody` | string\|null | Body da última resposta, truncado | | `manualResendOf` | string\|null | `uuid` do evento original, quando este registro for um clone de reenvio manual | | `payload` | object | Payload completo enviado ao seu endpoint | Os eventos ficam retidos por **30 dias**. Para auditoria de longo prazo, persista os webhooks no seu lado conforme os recebe. # Reenviar Evento de Webhook Source: https://docs.garu.com.br/api-reference/webhooks/eventos/reenviar POST /api/v1/webhook-events/{uuid}/retry Reseta um evento para pending e dispara a entrega imediatamente ## Visão Geral Reseta o evento para `pending`, zera o contador de tentativas falhas e dispara a entrega imediatamente. Funciona em qualquer status (`success`, `failed`, `pending`). **Prefira [`POST /webhook-events/:uuid/resend`](/api-reference/webhooks/eventos/resend).** `/retry` muta o evento original — perde o histórico de tentativas e respostas — e manda o mesmo `Idempotency-Key` (`payload.id`) da entrega original, o que pode causar **drop silencioso** em receivers que deduplicam por header. `/resend` clona, preserva o original e usa `Idempotency-Key: resend_`, sinalizando ao receiver que é entrega nova. Veja o guia [Reenviar webhook](/guias/reenviar-webhook) para a comparação completa. `/retry` segue funcionando para compatibilidade com automações existentes; ele não vai ser removido sem aviso. **Rate limit: 20 requisições por minuto por IP.** Para reprocessar lotes maiores, dê espaço entre as chamadas (≥ 3s entre retries) ou processe em batches de 20/min. ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/webhook-events/a1b2c3d4-e5f6-7890-abcd-ef1234567890/retry \ -H "Authorization: Bearer sk_live_sua_chave_api" ``` ```javascript JavaScript theme={null} import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY }); const reloaded = await garu.webhookEvents.retry('a1b2c3d4-e5f6-7890-abcd-ef1234567890'); console.log(reloaded.status); // → 'pending' ``` ```python Python theme={null} import os import requests response = requests.post( "https://garu.com.br/api/v1/webhook-events/a1b2c3d4-e5f6-7890-abcd-ef1234567890/retry", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, ) print(response.json()["status"]) # → "pending" ``` ## Parâmetros de Path UUID do evento a reenviar. Sem corpo de requisição. ## Resposta Retorna o `WebhookEvent` atualizado, em `201 Created`, já em `pending`: ```json theme={null} { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "webhookEndpoint": { "id": 7, "url": "https://example.com/webhooks/garu", "description": "Produção", "enabled": true, "events": ["transaction.payment.succeeded"] }, "eventType": "transaction.payment.succeeded", "status": "pending", "attempts": 0, "lastAttemptAt": null, "nextRetryAt": "2026-05-18T14:23:11.000Z", "responseStatus": null, "responseBody": null, "manualResendOf": null, "createdAt": "2026-05-18T14:22:00.000Z", "payload": { "id": "evt_1a2b3c", "type": "transaction.payment.succeeded", "data": { "object": { "id": 999, "value": 49.9 } } } } ``` ## Erros | Status | Caso | | - | - | | 404 | Evento não existe ou não pertence ao seller chamando | | 429 | Rate limit excedido (mais de 20 retries/min do mesmo IP) | ## O que o reenvio faz 1. **Reseta o status para `pending`** e zera tentativas falhas. 2. **Reenvia o mesmo payload** — o `id` do evento (`evt_…`) e o conteúdo do payload são preservados. Seu receiver recebe exatamente o que receberia na primeira tentativa. 3. **Aplica a retry policy padrão** caso a entrega falhe de novo (1min, 5min, 30min, 2h, 8h, 16h até `failed` permanente). Como o `id` do evento (`evt_…`) é o mesmo, sua lógica de idempotência no receiver deve tratar o reenvio como duplicata — é exatamente o que ela deveria fazer com qualquer redelivery automática da Garu. ## SDK, CLI e MCP * **Node SDK**: `garu.webhookEvents.retry(uuid)` — o parâmetro é `uuid` a partir do `@garuhq/node` **3.0.0** (era `id` numérico antes). * **CLI**: `garu webhooks events retry ` — a partir do `@garuhq/cli` **0.11.0**. * **MCP**: ferramenta `retry_webhook_event` (parâmetro `uuid`) — a partir do `@garuhq/mcp` **0.21.0**. Veja o guia [Reenviar webhook](/guias/reenviar-webhook) para o fluxo completo (listar → escolher → reenviar) nas três superfícies. # Reenviar (clonando) Evento de Webhook Source: https://docs.garu.com.br/api-reference/webhooks/eventos/resend POST /api/v1/webhook-events/{uuid}/resend Clona um evento e dispara a entrega imediatamente, preservando o original intacto ## Visão Geral Clona um evento de webhook e dispara a entrega imediatamente. O evento original **não é tocado** — fica no status terminal (`success` ou `failed`) com todo o histórico de tentativas e respostas preservado para auditoria. O clone é uma linha nova na tabela de eventos, carregando `manualResendOf: `. Disponível a partir da **v0.11.0** (então em `/api/webhook-events`; migrado para `/api/v1/webhook-events` com identificador `uuid`). **Mudança de contrato no header `Idempotency-Key`.** Reenvios manuais (este endpoint e o botão "Reenviar" do dashboard) mandam `Idempotency-Key: resend_` no POST para o seu endpoint, em vez do `payload.id` original. * Handlers que deduplicam pelo valor **bruto** do header já tratam isso como entrega fresca — nenhuma mudança de código. Mas vão começar a receber reenvios manuais que antes eram silenciosamente descartados; idempotência de negócio (orderId, transactionId) precisa absorver. * Handlers que deduplicam pelo `payload.id` (idêntico entre original e clone) ou normalizam o header **precisam ser atualizados** — caso contrário, o reenvio é descartado como duplicata. Veja o guia [Reenviar webhook](/guias/reenviar-webhook#mudanca-no-idempotency-key) para o código antes/depois. ## `/resend` vs `/retry` | | `/resend` | `/retry` | | - | - | - | | Semântica | **Clona** o evento | **Muta** o evento original | | Evento original | Intacto, status terminal preservado | Volta para `pending`, contador zerado | | Histórico de respostas | Mantido no original | **Sobrescrito** pela nova tentativa | | `Idempotency-Key` outbound | `resend_` | `event.payload.id` (mesmo do original) | | Indicado para | Tudo | Legado — preserve apenas se já automatizou em cima dele | ## Exemplo de Requisição ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/webhook-events/a1b2c3d4-e5f6-7890-abcd-ef1234567890/resend \ -H "Authorization: Bearer sk_live_sua_chave_api" ``` ```javascript JavaScript theme={null} import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY }); const clone = await garu.webhookEvents.resend('a1b2c3d4-e5f6-7890-abcd-ef1234567890'); console.log(clone.uuid, clone.manualResendOf); // novo uuid, uuid do original ``` ```python Python theme={null} import os import requests response = requests.post( "https://garu.com.br/api/v1/webhook-events/a1b2c3d4-e5f6-7890-abcd-ef1234567890/resend", headers={"Authorization": f"Bearer {os.environ['GARU_API_KEY']}"}, ) clone = response.json() print(clone["uuid"], clone["manualResendOf"]) # novo uuid, uuid do original ``` ## Parâmetros de Path UUID do evento a clonar e reenviar. Funciona em qualquer status do evento original — `success`, `failed` ou `pending`. Sem corpo de requisição. ## Resposta Retorna o **clone recém-criado** (não o original), em `201 Created`, já em `pending` e com a entrega disparada: ```json theme={null} { "uuid": "f9e8d7c6-b5a4-3210-9876-543210fedcba", "webhookEndpoint": { "id": 7, "url": "https://example.com/webhooks/garu", "description": "Produção", "enabled": true, "events": ["transaction.payment.succeeded"] }, "eventType": "transaction.payment.succeeded", "status": "pending", "attempts": 0, "lastAttemptAt": null, "nextRetryAt": "2026-05-19T18:05:33.000Z", "responseStatus": null, "responseBody": null, "manualResendOf": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "createdAt": "2026-05-19T18:04:33.000Z", "payload": { "id": "evt_1a2b3c", "type": "transaction.payment.succeeded", "data": { "object": { "id": 999, "value": 49.9 } } } } ``` ### Campos relevantes | Campo | Tipo | Descrição | | - | - | - | | `uuid` | string | Identificador do **clone** (novo). Use este `uuid` se quiser reenviar de novo. | | `manualResendOf` | string\|null | `uuid` do evento original. Sempre presente em clones; `null` em eventos originais. | | `status` | string | Sempre `pending` na criação. Vira `success` ou `failed` conforme a entrega. | | `payload` | object | Cópia exata do payload do original — `payload.id` (`evt_…`) é preservado. | O `payload.id` (`evt_…`) do clone é **idêntico** ao do original. Quem muda é o `Idempotency-Key` HTTP outbound: o clone manda `resend_`, o original mandou `evt_…`. ## Rate Limit **20 requisições por minuto por IP.** Mesmo limite do `/retry`, mesma motivação: cada chamada dispara uma entrega HTTP síncrona para o endpoint do seller. ## Erros | Status | Caso | | - | - | | 404 | Evento não existe ou não pertence ao seller chamando | | 429 | Rate limit excedido (mais de 20 resends/min do mesmo IP) | ## Onde aparece no dashboard Toda linha de evento em **Configurações → Webhooks → Eventos** mostra o botão **Reenviar** (visível em `success`, `failed` e `pending`). Reenviar um evento já `success` abre uma confirmação ("este evento já foi entregue com sucesso — reenviar mesmo assim?"). Clones recebem um badge 🔁 na tabela e uma linha **"Reenviado a partir de #\"** no drawer de detalhes. ## Próximos passos Fluxo completo (listar → escolher → reenviar) e o passo-a-passo do `Idempotency-Key` Inspecione `manualResendOf`, tentativas e payload antes de reenviar # Assinaturas Source: https://docs.garu.com.br/guias/assinaturas Guia completo para criar e gerenciar produtos com cobrança recorrente ## Visão Geral O sistema de assinaturas da Garu permite criar produtos com cobrança recorrente. Com ele você pode: * Criar planos com intervalos flexíveis (diário, semanal, mensal, anual) * Oferecer períodos de teste gratuito * Processar pagamentos automaticamente via cartão de crédito ou [Pix Automático](/guias/pix-automatico) * Gerenciar retry automático em caso de falha * Permitir que clientes gerenciem suas assinaturas via Portal Cobrança recorrente **automática** funciona com **cartão de crédito** e com **[Pix Automático](/guias/pix-automatico)**. PIX tradicional e Boleto exigem pagamento manual a cada ciclo. ## Conceitos Principais | Termo | Descrição | | - | - | | **Produto** | O item ou serviço que você vende (ex: "Plano Premium") | | **Preço de Assinatura** | Um plano de preço com intervalo e valor (ex: "Mensal R\$ 49,90") | | **Assinatura** | O acordo de cobrança ativo entre você e um cliente | | **Período de Teste** | Dias gratuitos antes da primeira cobrança | | **Ciclo de Cobrança** | Intervalo recorrente das cobranças | ## Intervalos de Cobrança | Intervalo | Valor | Descrição | | - | - | - | | Diário | `daily` | Cobrança a cada dia | | Semanal | `weekly` | Cobrança a cada 7 dias | | Mensal | `monthly` | Cobrança no mesmo dia todo mês | | Anual | `annually` | Cobrança uma vez por ano | ## Ciclo de Vida da Assinatura | Status | Descrição | | - | - | | `pending` | Assinatura criada, aguardando primeiro pagamento | | `active` | Assinatura ativa e cobrando normalmente | | `pending_cancellation` | Cancelamento agendado para fim do período | | `canceled` | Assinatura encerrada | | `failed` | Pagamento falhou após todas as tentativas | ## Passo a Passo: Criar uma Assinatura ### 1. Criar o Produto Primeiro, crie um produto de assinatura com `isSubscription: true`. Na API v1, `name`, `image` e `value` são obrigatórios. Veja a [API de Criação de Produtos](/api-reference/produtos/criar) para mais detalhes. ```bash theme={null} curl -X POST https://garu.com.br/api/v1/products \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "name": "Plano Premium", "image": "https://meusite.com/imagens/plano-premium.jpg", "value": 0, "isSubscription": true }' ``` Para produtos de assinatura, o preço é definido nos **preços de assinatura**, não no produto. Por isso, envie `value: 0`: o preço mínimo de R\$ 5,00 não é verificado no produto de assinatura, e sim em cada preço de assinatura. **Resposta:** ```json theme={null} { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Plano Premium", "isSubscription": true, "isActive": true } ``` ### 2. Criar Preços de Assinatura Crie um ou mais planos de preço para o produto. O `amount` é em **Reais** (`49.90` = R$ 49,90), nunca em centavos, e precisa ser de **pelo menos R$ 5,00\*\*: um valor menor, inclusive `0`, é recusado com `400`. Consulte a [API de Criação de Preços](/api-reference/assinaturas/precos/criar) para todos os campos disponíveis. ```bash theme={null} # Plano Mensal curl -X POST https://garu.com.br/api/subscription-prices \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "productId": "prod_abc123xyz", "name": "Plano Mensal", "amount": 49.90, "billingInterval": "monthly", "trialDays": 7 }' ``` **Resposta:** ```json theme={null} { "id": "price_def456uvw", "productId": "prod_abc123xyz", "name": "Plano Mensal", "amount": 49.90, "currency": "BRL", "billingInterval": "monthly", "trialDays": 7, "isActive": true } ``` ### 3. Obter o Link de Pagamento Combine o `uuid` do produto com o `id` do preço para gerar o link de pagamento: ``` https://garu.com.br/pay/{product_uuid}?priceId={price_id} ``` | Parâmetro | Origem | Exemplo | | - | - | - | | `product_uuid` | Campo `uuid` do produto | `c69c63d2-4207-4613-ad69-28e7e902544b` | | `price_id` | Campo `id` do preço | `price_gNgQVRrG5goab0Ql` | **Link completo:** ``` https://garu.com.br/pay/c69c63d2-4207-4613-ad69-28e7e902544b?priceId=price_gNgQVRrG5goab0Ql ``` Salve o `uuid` do produto e o `id` do preço no seu banco de dados para gerar links de pagamento a qualquer momento. ### 4. Cliente Realiza o Checkout Quando o cliente acessa o link: 1. Preenche informações pessoais (nome, email, CPF/CNPJ) 2. Insere dados do cartão de crédito 3. Sistema valida e cria a assinatura 4. Se houver período de teste, primeira cobrança é agendada 5. Cliente recebe confirmação ## Pix Automático na Assinatura Quer oferecer recorrência sem depender de cartão? Ligue o campo `pixAutomatic` no produto e o checkout passa a exibir a aba **Pix Automático** ao lado do cartão. ```bash theme={null} curl -X PATCH https://garu.com.br/api/v1/products/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ -H "Authorization: Bearer sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "pixAutomatic": true }' ``` Com o toggle ligado, o comportamento no checkout muda assim: 1. O cliente acessa o mesmo link de pagamento (`/pay/{uuid}?priceId=...`). 2. Escolhe a aba **Pix Automático** em vez de cartão. 3. Autoriza a recorrência **uma vez** no app do banco (seção "Pix Automático" / "Recorrência Pix"). 4. A assinatura sai de `waitingPayment` e vira `active` — os próximos ciclos são debitados sozinhos. A partir daí, os webhooks (`subscription.activated`, `transaction.payment.succeeded`, `subscription.renewed`) são os mesmos do cartão. Para diferenciar a origem, cheque `paymentMethod === 'pix_automatic'` no payload. Autorização, ciclos silenciosos, cancelamento pelos dois lados e modelo de falha. O campo `pixAutomatic` vem desligado por padrão (`false`). Ligar é aditivo: o cartão de crédito continua funcionando normalmente como método de assinatura. ## Período de Teste (Trial) O período de teste permite que clientes experimentem antes de pagar. ### Como Funciona 1. **Assinatura Criada**: Status `active` imediatamente 2. **Durante o Trial**: Cliente tem acesso, sem cobranças 3. **Trial Termina**: Primeira cobrança processada automaticamente 4. **Cobrança Continua**: Ciclo normal de cobrança inicia ### Exemplo de Timeline Para um trial de 14 dias iniciando em 1º de janeiro: | Data | Evento | | - | - | | 1 Jan | Assinatura criada, trial inicia | | 1-14 Jan | Período de teste (sem cobranças) | | 15 Jan | Primeira cobrança de R\$ 49,90 | | 15 Fev | Segunda cobrança de R\$ 49,90 | | ... | Continua mensalmente | Clientes podem cancelar durante o trial sem serem cobrados. ## Lógica de Retry de Pagamento Quando um pagamento falha, o sistema tenta novamente automaticamente. ### Estratégia de Retry | Tentativa | Timing | Ação | | - | - | - | | 1 | Imediatamente | Primeira tentativa | | 2 | +3 dias | Retry automático | | 3 | +3 dias | Retry automático | | 4 | +3 dias | Tentativa final | | Falha | Após 4ª | Assinatura marcada como `failed` | ### Motivos Comuns de Falha | Motivo | Descrição | | - | - | | `insufficient_funds` | Saldo insuficiente | | `card_expired` | Cartão expirado | | `card_declined` | Transação recusada pelo banco | | `invalid_card` | Número ou CVV inválido | | `fraud_suspected` | Detecção de fraude | ## Cancelamento Existem duas formas de cancelar uma assinatura. Veja a [API de Cancelamento](/api-reference/assinaturas/cancelar) para mais detalhes, ou [Pausar](/api-reference/assinaturas/pausar) e [Retomar](/api-reference/assinaturas/retomar) para opções de pausa temporária. ### Cancelamento Imediato Cliente perde acesso imediatamente: ```bash theme={null} curl -X POST https://garu.com.br/api/subscriptions/100/cancel \ -H "Authorization: Bearer sk_test_sua_chave" \ -d '{"cancelImmediately": true}' ``` ### Cancelamento Agendado Cliente mantém acesso até o fim do período: ```bash theme={null} curl -X POST https://garu.com.br/api/subscriptions/100/cancel \ -H "Authorization: Bearer sk_test_sua_chave" \ -d '{"cancelImmediately": false}' ``` **Resultado:** * Status muda para `pending_cancellation` * Cliente mantém acesso até o período terminar * Nenhuma cobrança adicional * No fim do período, status muda para `canceled` ## Portal do Cliente O Portal permite que assinantes gerenciem suas assinaturas de forma autônoma. Veja a [API do Portal do Cliente](/api-reference/assinaturas/portal) para configurações avançadas e todos os endpoints disponíveis. ### Criando uma Sessão do Portal ```bash theme={null} curl -X POST https://garu.com.br/api/portal/sessions \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "customerId": 50, "returnUrl": "https://seusite.com/conta" }' ``` **Resposta:** ```json theme={null} { "token": "a1b2c3d4e5f6...", "expiresAt": "2024-01-15T11:30:00.000Z", "portalUrl": "/portal?token=a1b2c3d4e5f6..." } ``` ### URL do Portal ``` https://garu.com.br/portal?token={session_token} ``` ### O que Clientes Podem Fazer | Ação | Descrição | | - | - | | Cancelar assinatura | Encerrar a assinatura | | Atualizar pagamento | Trocar cartão de crédito | | Atualizar dados | Alterar informações de cobrança | | Ver histórico | Consultar pagamentos anteriores | | Pausar/Retomar | Pausar temporariamente a cobrança | Configure o Portal em **Configurações** → **Portal do Cliente** no Dashboard. ## Webhooks de Assinatura Configure webhooks para receber notificações em tempo real. Consulte a [documentação de Webhooks](/api-reference/webhooks) para implementação completa e validação de assinatura. ### Eventos Disponíveis | Evento | Descrição | | - | - | | `subscription.created` | Nova assinatura criada | | `subscription.activated` | Assinatura ativada | | `subscription.payment_success` | Pagamento recorrente confirmado | | `subscription.payment_failed` | Pagamento falhou (pode haver retry) | | `subscription.canceled` | Assinatura cancelada | | `subscription.trial_ending` | Trial termina em 3 dias | ### Exemplo de Payload ```json theme={null} { "event": "subscription.payment_success", "timestamp": "2024-02-15T10:00:00.000Z", "data": { "subscription": { "id": 100, "status": "active", "customerId": 50 }, "payment": { "amount": 49.90, "transactionId": "tx_abc123", "paidAt": "2024-02-15T10:00:00.000Z" } } } ``` ## Exemplo Completo ```javascript theme={null} const GARU_API = 'https://garu.com.br/api'; class GaruAssinaturas { constructor(apiKey) { this.apiKey = apiKey; } async request(method, endpoint, data) { const response = await fetch(`${GARU_API}${endpoint}`, { method, headers: { 'Authorization': `Bearer ${this.apiKey}`, 'Content-Type': 'application/json' }, body: data ? JSON.stringify(data) : undefined }); return response.json(); } // Criar produto de assinatura (apenas nome é obrigatório) async criarProduto(nome) { return this.request('POST', '/products', { name: nome, isSubscription: true }); } // Criar preço de assinatura (productId é o UUID do produto) async criarPreco(productId, nome, valor, intervalo, trialDias = 0) { return this.request('POST', '/subscription-prices', { productId, // UUID do produto (ex: 'prod_abc123xyz') name: nome, amount: valor, // Valor em Reais (ex: 49.90), mínimo R$ 5,00 billingInterval: intervalo, // 'daily', 'weekly', 'monthly', 'annually' trialDays: trialDias }); } // Gerar link de checkout getCheckoutUrl(productUuid, priceId) { return `https://garu.com.br/pay/${productUuid}?priceId=${priceId}`; } // Cancelar assinatura async cancelar(subscriptionId, imediatamente = false) { return this.request('POST', `/subscriptions/${subscriptionId}/cancel`, { cancelImmediately: imediatamente }); } // Criar sessão do portal async criarSessaoPortal(customerId, returnUrl) { return this.request('POST', '/portal/sessions', { customerId, returnUrl }); } } // Uso const garu = new GaruAssinaturas('sk_test_sua_chave'); // Criar produto (apenas nome é obrigatório) const produto = await garu.criarProduto('SaaS Premium'); // produto.uuid = 'prod_abc123xyz' // Criar planos de preço (usando o UUID do produto) const planoMensal = await garu.criarPreco(produto.uuid, 'Mensal', 49.90, 'monthly', 7); const planoAnual = await garu.criarPreco(produto.uuid, 'Anual', 479.00, 'annually', 14); // Links de checkout console.log('Mensal:', garu.getCheckoutUrl(produto.uuid, planoMensal.id)); console.log('Anual:', garu.getCheckoutUrl(produto.uuid, planoAnual.id)); ``` ## Próximos Passos Crie planos de preço para assinaturas Consulte todas as assinaturas Consulte os detalhes de uma assinatura Cancele uma assinatura Pause temporariamente as cobranças Histórico de eventos e pagamentos Configurar e usar o portal de autoatendimento Receber notificações de eventos # Boleto Parcelado (Carnê) Source: https://docs.garu.com.br/guias/carne Venda um produto em até 12 boletos mensais — e entenda o risco que você assume ## O que é Um carnê é **um produto pago em N boletos mensais**. O comprador leva o produto agora e paga ao longo de até 12 meses. **Isso é crédito que você concede, não parcelamento de cartão.** Ninguém garante um boleto. Se o comprador parar de pagar na parcela 4, você ficou com 4 parcelas e perdeu as outras 8 — a Garu emite os boletos, cobra e informa, mas **não assume o risco de inadimplência**. No cartão parcelado a adquirente antecipa o valor; aqui, não existe antecipação. Só ative carnê em produtos cuja margem suporta perder parte do valor. ## Como funciona Só o **primeiro boleto** é registrado no banco. As outras parcelas ainda não existem como boleto pagável. Só aí a venda existe. O plano sai de `pending_activation` para `active` e as parcelas 2..N passam a ser emitidas mês a mês. Cada parcela é emitida alguns dias antes do vencimento, com lembretes por e-mail. Segunda via fica disponível quando o boleto vence. Depois da janela de carência sobre a parcela mais antiga em aberto, o plano vira `defaulted`. Emissão e lembretes param. O dinheiro já recebido é seu. ## O preço que o comprador vê O valor de cada parcela **não** é o preço à vista dividido por N. Ele inclui o acréscimo do parcelamento (o `fator` configurado para a sua conta): | | | | - | - | | Preço à vista | R\$ 1.200,00 | | Fator | 1,30 | | Total do carnê | **R\$ 1.560,00** | | 12 parcelas de | **R\$ 130,00** | O checkout mostra ao comprador a divulgação completa exigida pelo **art. 52 do CDC**: preço à vista, número e valor das prestações, acréscimo total, soma total a pagar e a taxa de juros ao mês e ao ano. `totalScheduled` é o que o carnê cobra. `totalCollected` é o que **realmente entrou**. Os dois divergem quando o banco soma multa ou mora — por isso `totalCollected` pode legitimamente ficar **acima** de `totalScheduled`. Nunca use um no lugar do outro para conciliação. ## Criando pela API ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/installment-plans \ -H "Authorization: Bearer sk_live_sua_chave" \ -H "Content-Type: application/json" \ -H "X-Idempotency-Key: 6c4c2a1e-4c2b-4f1c-9a8d-1b2e3f4a5b6c" \ -d '{ "productId": "40381e8e-6ee7-4b8e-9393-766a6e2109d2", "customerId": 4821, "installments": 12 }' ``` ```javascript JavaScript theme={null} import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY }); const carne = await garu.installmentPlans.create({ productId: '40381e8e-6ee7-4b8e-9393-766a6e2109d2', customerId: 4821, installments: 12 }); console.log(carne.totalScheduled); // 1560 console.log(carne.installmentsDetail[0].boleto.barcodeLine); ``` **Sempre envie `X-Idempotency-Key`.** Esta chamada registra um boleto de verdade no banco. Uma retentativa sem a chave coloca **dois códigos de barras pagáveis** na mão do mesmo comprador. O SDK anexa a chave sozinho. ## Estornos A Garu **não devolve esse dinheiro por você**. Um boleto não tem estorno, e o valor já foi liquidado para a sua conta — a devolução é uma transferência que só você pode fazer. Por isso o fluxo é em duas etapas: `POST /api/v1/installment-plans/{uuid}/refund-requests` registra o pedido e avisa o seu time. **O carnê continua rodando** enquanto o pedido está pendente — as próximas parcelas ainda são emitidas. Por fora da Garu: um Pix de volta, uma transferência, o que combinar com o comprador. `POST /api/v1/refund-requests/{uuid}/confirm`. Aí sim o carnê é encerrado como `refunded`, as parcelas em aberto param, os boletos abertos são cancelados no banco e as comissões de afiliado e coprodutor das parcelas pagas são estornadas. Use `GET /api/v1/refund-requests?status=pending` para responder “quanto eu ainda devo devolver a compradores”. Pix e boleto avulsos também aparecem nessa lista. ## Ciclo de vida | Status | Significado | | - | - | | `pending_activation` | Criado. Parcela 1 emitida, ainda não paga. Não é venda. | | `active` | Parcela 1 compensou. O carnê está rodando. | | `completed` | Todas as parcelas pagas. | | `defaulted` | A parcela mais antiga em aberto passou da carência. | | `canceled` | Encerrado pelo vendedor ou pela expiração da ativação. | | `refunded` | Estorno confirmado pelo vendedor. | ## Webhooks Um carnê dura um ano, então acompanhe parcela a parcela — não só os extremos: * `installment_plan.created` · `.activated` · `.completed` * `installment_plan.installment_paid` · `.installment_overdue` · `.installment_reissued` * `installment_plan.defaulted` · `.canceled` * `installment_plan.refund_requested` · `.refund_rejected` · `.refunded` Um carnê **nunca** emite eventos `scheduled_charge.*`, e não pode ser manipulado por `/api/v1/scheduled-charges`. Ele tem um único caminho de escrita: `/api/v1/installment-plans`. ## Limitações conhecidas O art. 52 §2 do CDC dá ao comprador o direito de quitar antecipadamente com redução proporcional dos juros. Isso ainda **não** está implementado — hoje o comprador paga as parcelas restantes pelo valor cheio. Se receber esse pedido, trate manualmente. Ainda não há lembrete por WhatsApp. A taxa de recuperação de um carnê depende muito do alcance do lembrete. Não é possível re-parcelar o saldo restante de um carnê em andamento. Você pode adiar uma parcela por vez ou cancelar o plano. # Checkout Sessions Source: https://docs.garu.com.br/guias/checkout-sessions Guia completo para criar links de pagamento personalizados via API ## Visão Geral Checkout Sessions permitem criar links de pagamento programaticamente com controle total sobre a experiência do cliente. É a forma mais flexível de integrar pagamentos da Garu ao seu sistema. **Quando usar Checkout Sessions:** | Cenário | Solução | | - | - | | E-commerce com carrinho | Checkout Sessions | | Pré-preencher dados do cliente | Checkout Sessions | | Correlacionar com pedidos internos | Checkout Sessions | | Link simples para compartilhar | Link de Pagamento direto | | Venda rápida sem integração | Link de Pagamento direto | **Pré-requisito:** Você precisa de uma chave de API. Veja como obter em [Autenticação](/api-reference/autenticacao). ## Por que usar Checkout Sessions? ### 1. Pré-preenchimento de Dados Com Checkout Sessions, você pode enviar dados do cliente que já possui, reduzindo o atrito no checkout: ```json theme={null} { "customer": { "name": "João Silva", "email": "joao@email.com", "document": "12345678900", "phone": "11999999999" } } ``` O cliente verá o formulário já preenchido, precisando apenas confirmar e pagar. ### 2. Correlação com seu Sistema Use `metadata` e `client_reference_id` para vincular o pagamento aos seus pedidos: ```json theme={null} { "metadata": { "order_id": "12345", "campaign": "black-friday", "user_id": "usr_abc123" }, "client_reference_id": "order_12345" } ``` Esses dados são retornados nos webhooks, facilitando a automação. ### 3. Controle de Expiração Sessions expiram automaticamente após 24 horas, mas você pode: * Definir expiração personalizada com `expires_at` * Expirar manualmente uma session aberta * Receber webhook quando uma session expira ### 4. Idempotência Evite sessions duplicadas em caso de retry usando `X-Idempotency-Key`: ```bash theme={null} curl -X POST https://api.garu.com.br/api/checkout/sessions \ -H "X-Idempotency-Key: order_12345_v1" \ ... ``` ## Fluxo de Integração ``` SEU SISTEMA GARU CLIENTE | | | | 1. Criar Checkout Session | | |------------------------------->| | | | | | 2. Retorna URL da session | | |<-------------------------------| | | | | | 3. Redireciona cliente | | |-------------------------------------------------------> | | | | | | 4. Cliente paga | | |<------------------------------| | | | | 5. Webhook: session.completed | | |<-------------------------------| | | | | | 6. Libera acesso | | |-------------------------------------------------------> | ``` ## Ciclo de Vida da Session | Status | Descrição | | - | - | | `open` | Session criada, aguardando pagamento | | `complete` | Pagamento confirmado, transação criada | | `expired` | Expirou sem pagamento (após 24h ou manualmente) | ``` CRIAR -----> OPEN -----> COMPLETE | | | v | Transaction ID | é preenchido | v EXPIRED ``` ## Passo a Passo: Integração Completa ### 1. Criar o Produto Primeiro, crie um produto que será vendido. Veja o [guia de Criação de Produtos](/guias/criando-produtos). ```bash theme={null} curl -X POST https://garu.com.br/api/v1/products \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "name": "Curso de Python", "image": "https://exemplo.com/curso.png", "value": 297.00 }' ``` Anote o `uuid` do produto retornado — é ele que você envia no campo `product_id` da checkout session. (O campo se chama `product_id` por histórico, mas espera o **UUID** do produto, não um id numérico.) ### 2. Criar Checkout Session Quando o cliente iniciar o checkout no seu sistema: ```bash theme={null} curl -X POST https://api.garu.com.br/api/checkout/sessions \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -H "X-Idempotency-Key: order_12345_v1" \ -d '{ "product_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "customer": { "name": "João Silva", "email": "joao@email.com" }, "success_url": "https://seusite.com/sucesso?session_id={SESSION_ID}", "cancel_url": "https://seusite.com/carrinho", "metadata": { "order_id": "12345" }, "client_reference_id": "order_12345" }' ``` **Resposta:** ```json theme={null} { "data": { "id": "cs_ABC123xyz", "status": "open", "url": "https://pay.garu.com.br/pay/session/abc123...", "expires_at": "2025-01-20T12:00:00.000Z" } } ``` ### 3. Redirecionar o Cliente Envie o cliente para a URL retornada: ```javascript theme={null} // No frontend window.location.href = session.url; // Ou em um botão Finalizar Compra ``` ### 4. Receber Webhook Configure seu endpoint para receber notificações. Veja a [documentação de Webhooks](/api-reference/webhooks). ```javascript theme={null} app.post('/webhooks/garu', (req, res) => { const { event, data } = req.body; if (event === 'checkout.session.completed') { const orderId = data.client_reference_id; const transactionId = data.transaction_id; // Atualizar pedido no seu sistema await db.orders.update(orderId, { status: 'paid', transactionId: transactionId }); // Liberar acesso ao produto await liberarAcesso(data.customer_email, data.product_id); } res.status(200).json({ received: true }); }); ``` ### 5. Verificar Status (Opcional) Se precisar verificar o status de uma session: ```bash theme={null} curl -X GET "https://api.garu.com.br/api/checkout/sessions/cs_ABC123xyz" \ -H "Authorization: Bearer sk_test_sua_chave" ``` ## Checkout Sessions para Assinaturas Para criar uma session de assinatura, inclua o `price_id`: ```bash theme={null} curl -X POST https://api.garu.com.br/api/checkout/sessions \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "product_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "price_id": "price_abc123", "customer": { "email": "joao@email.com" }, "success_url": "https://seusite.com/assinatura-ativa" }' ``` O `price_id` é obtido ao [criar um preço de assinatura](/api-reference/assinaturas/precos/criar). ## Restringindo Métodos de Pagamento Por padrão, todos os métodos de pagamento do produto são exibidos. Para restringir: ```json theme={null} { "product_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "payment_methods": ["pix", "creditCard"] } ``` Valores aceitos: `pix`, `creditCard`, `boleto` ## Boas Práticas ### Sempre Use Idempotency Keys Evite sessions duplicadas em caso de falha de rede: ```javascript theme={null} const idempotencyKey = `order_${orderId}_checkout_${Date.now()}`; const response = await fetch('https://api.garu.com.br/api/checkout/sessions', { method: 'POST', headers: { 'Authorization': 'Bearer ' + apiKey, 'X-Idempotency-Key': idempotencyKey, 'Content-Type': 'application/json' }, body: JSON.stringify({ product_id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890' }) }); ``` ### Armazene o Session ID Salve o `id` da session para consultas futuras: ```javascript theme={null} const session = await criarCheckoutSession(dados); await db.checkoutSessions.create({ sessionId: session.id, orderId: orderId, createdAt: new Date() }); ``` ### Use Metadata para Correlação Inclua todos os dados necessários para processar o pedido: ```json theme={null} { "metadata": { "order_id": "12345", "user_id": "usr_abc", "plan": "premium", "coupon": "DESCONTO10" } } ``` ### Trate Expirações Sessions expiram após 24 horas. Trate isso na sua aplicação: ```javascript theme={null} app.get('/checkout/:orderId', async (req, res) => { const order = await db.orders.find(req.params.orderId); if (order.sessionExpiresAt < new Date()) { // Criar nova session const newSession = await criarCheckoutSession(order); return res.redirect(newSession.url); } res.redirect(order.sessionUrl); }); ``` ## Exemplo Completo ```javascript theme={null} const GARU_API = 'https://api.garu.com.br/api'; class GaruCheckout { constructor(apiKey) { this.apiKey = apiKey; } async criarSession(dados) { const response = await fetch(GARU_API + '/checkout/sessions', { method: 'POST', headers: { 'Authorization': 'Bearer ' + this.apiKey, 'Content-Type': 'application/json', 'X-Idempotency-Key': dados.idempotencyKey || ('session_' + Date.now()) }, body: JSON.stringify({ product_id: dados.productId, price_id: dados.priceId, customer: dados.customer, payment_methods: dados.paymentMethods, metadata: dados.metadata, success_url: dados.successUrl, cancel_url: dados.cancelUrl, client_reference_id: dados.clientReferenceId }) }); return response.json(); } async buscarSession(sessionId) { const response = await fetch(GARU_API + '/checkout/sessions/' + sessionId, { headers: { 'Authorization': 'Bearer ' + this.apiKey } }); return response.json(); } async expirarSession(sessionId) { const response = await fetch(GARU_API + '/checkout/sessions/' + sessionId + '/expire', { method: 'POST', headers: { 'Authorization': 'Bearer ' + this.apiKey } }); return response.json(); } } // Uso const garu = new GaruCheckout('sk_test_sua_chave'); // Criar session para um pedido const session = await garu.criarSession({ productId: 123, customer: { name: 'João Silva', email: 'joao@email.com' }, successUrl: 'https://seusite.com/sucesso?session_id={SESSION_ID}', cancelUrl: 'https://seusite.com/carrinho', metadata: { order_id: '12345' }, clientReferenceId: 'order_12345' }); console.log('Redirecionar para:', session.data.url); ``` ## Migrando de Links Diretos Se você usa links diretos (`/product-slug`), migrar para Checkout Sessions oferece mais controle: **Antes (Link Direto):** ``` https://pay.garu.com.br/meu-produto?email=joao@email.com ``` **Depois (Checkout Session):** ```javascript theme={null} // 1. Criar session no seu servidor const session = await garu.criarSession({ productId: 123, customer: { email: 'joao@email.com' } }); // 2. Redirecionar para session redirect(session.data.url); ``` **Benefícios:** * Pré-preenchimento completo de dados * Metadados para correlação * Webhooks estruturados * Idempotência * Controle de expiração ## Próximos Passos Referência da API de criação Consulte suas sessions Configure notificações Cobrança recorrente # Checkout Transparente Source: https://docs.garu.com.br/guias/checkout-transparente Receba PIX, boleto e cartão dentro da sua própria interface, sem redirecionar o cliente ## Visão Geral No **checkout transparente** o cliente paga sem sair da sua tela. Você chama a API, recebe o código PIX (ou a linha do boleto, ou o resultado da autorização do cartão) e exibe tudo com a sua marca, no seu layout. É o caminho para quem constrói produto whitelabel, marketplace, ou simplesmente não quer que o cliente veja uma página de terceiro no meio da compra. ### Qual caminho usar? | Cenário | Solução | | - | - | | Quero receber rápido, sem programar | [Link de pagamento](/api-reference/produtos/link-pagamento) | | Quero pré-preencher dados e rastrear pedidos | [Checkout Session](/guias/checkout-sessions) | | Quero o pagamento acontecendo na minha interface | **Checkout transparente** | | Vendo com a marca do meu cliente (whitelabel) | **Checkout transparente** | As três opções coexistem. Nada do que você já integrou deixa de funcionar. ## Como funciona A cobrança sempre aponta para um produto — é dele que saem preço, métodos aceitos e parcelamento. Crie com [`POST /api/v1/products`](/api-reference/produtos/criar) e guarde o `uuid`. Nome, e-mail, CPF/CNPJ e telefone são obrigatórios. Endereço é opcional. [`POST /api/v1/charges`](/api-reference/cobrancas/criar) com o `productId`, o `paymentMethod` e o `customer`. A resposta traz os dados de pagamento. Renderize o QR Code a partir do `pix.code`, ou mostre a linha digitável e o link do PDF do boleto. Receba o [webhook](/api-reference/webhooks) de pagamento confirmado, ou consulte [`GET /api/v1/charges/{uuid}`](/api-reference/cobrancas/detalhes). ## PIX na prática ```javascript theme={null} const response = await fetch('https://garu.com.br/api/v1/charges', { method: 'POST', headers: { Authorization: `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json', 'X-Idempotency-Key': `pedido-${orderId}` }, body: JSON.stringify({ productId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', paymentMethod: 'pix', customer: { name: 'Maria Silva', email: 'maria@exemplo.com.br', document: '12345678909', phone: '11987654321' } }) }); const charge = await response.json(); // charge.uuid → guarde para consultar o status depois // charge.pix.code → o copia-e-cola, pronto para virar QR Code ``` ### Renderizando o QR Code A Garu devolve o **código EMV** (copia-e-cola), não uma imagem. Isso é de propósito: uma imagem viria hospedada num domínio de terceiro, e num checkout whitelabel isso apareceria para o seu cliente. Gere o QR no seu próprio front, com a biblioteca que já usa: ```jsx React theme={null} import QRCode from 'react-qr-code'; ; ``` ```html HTML + JS theme={null}
``` ```python Python theme={null} import qrcode img = qrcode.make(charge["pix"]["code"]) img.save("pix.png") ```
Ofereça também o botão de **copiar código** — boa parte das pessoas paga colando no app do banco, sem escanear nada. ## Boleto ```javascript theme={null} const charge = await createCharge({ paymentMethod: 'boleto', /* ... */ }); // charge.boleto.barcodeLine → linha digitável // charge.boleto.pdfUrl → PDF servido pelo domínio da Garu ``` O `pdfUrl` é uma URL pública, que **não exige chave de API** — pode ser entregue direto ao seu cliente, por e-mail ou como botão de download. Ela aponta para `garu.com.br`, nunca para o domínio do adquirente. ## Cartão de crédito **Servidor-para-servidor, sempre.** Este endpoint recebe o número do cartão e o CVV em texto claro. Chame-o exclusivamente do seu backend — nunca do navegador nem de um app, onde a sua chave de API e os dados do cartão ficariam expostos. Processar dados de cartão no seu servidor coloca a sua operação no escopo do **PCI DSS** (provavelmente SAQ D). Isso é uma decisão de negócio, não um detalhe técnico. Se você prefere não assumir esse escopo, use PIX e boleto no checkout transparente e mande o cartão para a página hospedada da Garu, que já é PCI-compliant. ```javascript theme={null} const charge = await createCharge({ paymentMethod: 'creditCard', productId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', customer: { /* ... */ }, card: { number: '4111111111111111', holderName: 'MARIA SILVA', expirationDate: '2030-12', cvv: '123', installments: 2 } }); // charge.status → "captured" quando aprovado // charge.card.last4 → "1111" // charge.amount → 349.00 (preço do produto) // charge.chargedTotal → 358.52 (o que o cliente pagou, com acréscimo do parcelamento) ``` A Garu nunca armazena o número do cartão nem o CVV. A resposta devolve só bandeira, últimos 4 dígitos e código de autorização. ### Parcelamento: `amount` não é `chargedTotal` Em venda parcelada os dois valores **divergem, e isso é esperado**: `amount` é o preço do produto, `chargedTotal` é o que foi efetivamente cobrado, já com o acréscimo. Um produto de R$ 349,00 vendido em 2× tem `chargedTotal: 358.52` — cerca de R$ 179,26 por parcela. Se você exibir R\$ 179,26 como o total da compra, vai mostrar um número errado ao seu cliente e não vai conseguir reconciliar o recebimento depois. ## Idempotência Mande sempre um `X-Idempotency-Key` único por cobrança. Se a requisição sofrer timeout e você reenviar, a chave garante que volta a **mesma cobrança** em vez de nascer uma segunda. Vale por 24 horas. ```javascript theme={null} headers: { 'X-Idempotency-Key': `pedido-${orderId}` } ``` ## Juntando com Checkout Sessions Dá para combinar os dois: crie a [Checkout Session](/guias/checkout-sessions) para levar `metadata`, `client_reference_id` e atribuição de afiliado, e depois passe o token dela na criação da cobrança. O cliente nunca sai da sua interface e você mantém o rastreamento. ```javascript theme={null} const charge = await createCharge({ productId, paymentMethod: 'pix', customer, checkoutSessionToken: session.id }); ``` A session é marcada como concluída e fica amarrada à cobrança. ## Confirmando o pagamento PIX e boleto são assíncronos: a cobrança nasce pendente e só depois vira paga. Duas formas de saber: A Garu chama o seu endpoint no instante em que o pagamento é confirmado. É a opção mais rápida e a que menos consome recurso dos dois lados. `GET /api/v1/charges/{uuid}` devolve o status atual. Útil para reconciliação, ou para atualizar a tela enquanto o cliente ainda está nela. ## Referência completa # CLI Source: https://docs.garu.com.br/guias/cli Gerencie cobranças, carnês e reembolsos direto no terminal com a CLI da Garu ## Visão Geral A CLI da Garu permite criar cobranças, vender em carnê (boleto parcelado), gerenciar reembolsos e diagnosticar sua integração sem sair do terminal. Ideal para automações, scripts e agentes de IA que preferem trabalhar com comandos. **Pré-requisito:** Você precisa de uma chave de API. Veja como obter em [Autenticação](/api-reference/autenticacao). ## Instalação ```bash npm theme={null} npm install -g @garuhq/cli ``` ```bash curl theme={null} curl -fsSL https://raw.githubusercontent.com/Garu-Pagamentos/garu-cli/main/install.sh | bash ``` Verifique a instalação: ```bash theme={null} garu --version ``` ## Autenticação Existem três formas de autenticar: ```bash theme={null} garu login ``` Abre o navegador para autenticação. O token é salvo em `~/.config/garu/credentials.json`. ```bash theme={null} export GARU_API_KEY=YOUR_API_KEY ``` A CLI usa automaticamente a variável `GARU_API_KEY` se presente. ```bash theme={null} garu charges list --api-key YOUR_API_KEY ``` Útil para scripts e CI/CD. A flag tem prioridade sobre as outras formas. Para encerrar a sessão: ```bash theme={null} garu logout ``` ## Referência de Comandos ### Autenticação | Comando | Descrição | | - | - | | `garu login` | Autentica via navegador | | `garu logout` | Remove credenciais salvas | ### Cobranças | Comando | Descrição | | - | - | | `garu charges create` | Cria uma nova cobrança | | `garu charges list` | Lista cobranças | | `garu charges get ` | Consulta detalhes de uma cobrança | | `garu charges refund ` | Reembolsa uma cobrança | ### Clientes A partir da CLI 0.10.0 você gerencia clientes sem sair do terminal. | Comando | Descrição | | - | - | | `garu customers create` | Cadastra um cliente para o seller atual | | `garu customers list` | Lista clientes, com busca e filtro `--status overdue` | | `garu customers get ` | Consulta um cliente | | `garu customers update ` | Atualiza campos do cliente (parcial) | | `garu customers set-billing-email ` | Define o e-mail de cobrança fixo | | `garu customers clear-billing-email ` | Limpa o e-mail de cobrança fixo | | `garu customers delete ` | Desvincula o cliente do seller atual | ### Produtos A partir da CLI 0.7.0 você cria e atualiza produtos sem sair do terminal — incluindo o [Pix Automático](/guias/pix-automatico). | Comando | Descrição | | - | - | | `garu products create` | Cria um novo produto | | `garu products update ` | Atualiza um produto existente | ### Carnê (Boleto Parcelado) A partir da CLI 0.9.0, venda em [carnê](/guias/carne) — um produto dividido em 2 a 12 boletos mensais — direto do terminal. | Comando | Descrição | | - | - | | `garu installment-plans create` | Vende um produto em carnê para um cliente | | `garu installment-plans list` | Lista carnês, com filtros de status, cliente e produto | | `garu installment-plans get ` | Consulta um carnê e todas as suas parcelas | | `garu installment-plans reissue ` | Emite segunda via de uma parcela vencida | | `garu installment-plans postpone ` | Adia o vencimento de uma parcela | | `garu installment-plans mark-paid ` | Marca uma parcela como paga manualmente | | `garu installment-plans cancel ` | Cancela o carnê e os boletos ainda em aberto | | `garu installment-plans request-refund ` | Solicita o estorno de um carnê | ### Reembolsos A CLI não reverte boleto nem Pix automaticamente — esse fluxo é registrar o pedido, você devolver o valor por fora, e depois confirmar. Cartão continua com estorno automático via `garu charges refund`. | Comando | Descrição | | - | - | | `garu refund-requests list` | Lista pedidos de estorno (carnê e Pix/boleto) | | `garu refund-requests get ` | Consulta um pedido de estorno | | `garu refund-requests confirm ` | Confirma que o valor já foi devolvido (chame **depois** de devolver) | | `garu refund-requests reject ` | Recusa o pedido — o carnê ou a cobrança seguem intactos | ### Cobranças Agendadas | Comando | Descrição | | - | - | | `garu scheduled-charges create` | Agenda uma cobrança (avulsa ou recorrente) | ### Diagnóstico | Comando | Descrição | | - | - | | `garu doctor` | Verifica configuração e conectividade | ### Flags Globais | Flag | Descrição | | - | - | | `--api-key` | Chave de API inline (prioridade sobre env e login) | | `--json` | Saída em JSON (ideal para scripts e agentes de IA) | ## Modos de Saída A CLI detecta automaticamente se está rodando em um terminal interativo (TTY) ou em um script: Saída formatada com cores e tabelas: ```bash theme={null} garu charges list ``` ``` ┌────────┬──────────────────┬──────────┬─────────┐ │ ID │ Cliente │ Valor │ Status │ ├────────┼──────────────────┼──────────┼─────────┤ │ 12345 │ joao@email.com │ R$ 49,90 │ paid │ │ 12346 │ maria@email.com │ R$ 99,00 │ pending │ └────────┴──────────────────┴──────────┴─────────┘ ``` Saída em JSON para scripts e automações: ```bash theme={null} garu charges list --json ``` ```json theme={null} [ { "id": 12345, "customer": "joao@email.com", "amount": 49.90, "status": "paid" } ] ``` Use `--json` quando estiver integrando a CLI com outros scripts ou quando um agente de IA estiver usando a CLI. ## Diagnóstico com `garu doctor` O comando `garu doctor` verifica se tudo está configurado corretamente: ```bash theme={null} garu doctor ``` ``` ✔ Versão da CLI: 1.0.0 ✔ Node.js: v20.11.0 ✔ Credenciais: configuradas ✔ Conectividade: API acessível ✔ Chave de API: válida (sk_test_***abc) ``` **O que ele verifica:** * Versão da CLI e do Node.js * Se as credenciais estão configuradas * Se a API está acessível * Se a chave de API é válida ## Exemplos de Uso ### Criar uma cobrança PIX ```bash theme={null} garu charges create \ --type pix \ --product-id a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ --customer-name "João Silva" \ --customer-email joao@email.com \ --customer-document 12345678900 \ --customer-phone 11987654321 ``` ### Listar cobranças pagas ```bash theme={null} garu charges list --status paid ``` ### Cadastrar um cliente ```bash theme={null} garu customers create \ --name "Maria Silva" --email maria@exemplo.com.br \ --document 12345678909 --phone 11987654321 \ --person-type fisica ``` ### Vender um produto em carnê ```bash theme={null} garu installment-plans create \ --product-id a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ --customer-id 4821 \ --installments 12 ``` ### Confirmar um reembolso após devolver o valor ```bash theme={null} garu refund-requests confirm a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ --note "Pix devolvido em 14/08, e2e E12345678" ``` ## Produtos pela CLI Com `garu products create` e `garu products update ` você cria e ajusta produtos direto no terminal — perfeito para versionar seu catálogo em scripts ou deixar um agente de IA cuidar disso. ### Flags disponíveis As mesmas flags valem para `create` e `update`: | Flag | Descrição | | - | - | | `--name` | Nome do produto (exibido ao cliente) | | `--value` | Preço **em Reais** (ex: `49.90` = R\$ 49,90) | | `--description` | Descrição do produto | | `--image` | URL da imagem do produto | | `--tags` | Tags para categorização (separadas por vírgula) | | `--pix` | Habilita pagamento via PIX | | `--boleto` | Habilita boleto bancário | | `--credit-card` | Habilita cartão de crédito | | `--pix-automatic` | Liga o [Pix Automático](/guias/pix-automatico) (débito recorrente) | | `--no-pix-automatic` | Desliga o Pix Automático (padrão) | | `--installments` | Parcelas máximas no cartão (1–12) | | `--subscription` | Marca o produto como assinatura | | `--subscription-type` | Tipo da assinatura (`monthly`, `yearly`, etc.) | | `--unit-label` | Rótulo da unidade (ex: "licença", "assento") | | `--return-url` | URL de redirecionamento após o pagamento | | `--return-url-button-text` | Texto do botão exibido na tela de sucesso | `--value` (produtos) é informado **em Reais** — o mesmo que o dashboard e a API REST (ex: `49.90` = R$ 49,90). Não use centavos: `--value 4990` cadastraria um produto de R$ 4.990,00. Ao criar um produto, `--name`, `--image` e `--value` são obrigatórios na API. O preço é `0` (produto **sem preço**, que não vende pelo link de pagamento) ou **pelo menos R\$ 5,00**: de `0.01` a `4.99` a API recusa com `400`. No `update`, a regra só vale quando você passa `--value`; sem ele, o preço atual fica como está. ### Criar um produto simples ```bash theme={null} garu products create \ --name "E-book de Marketing" \ --image "https://meusite.com/imagens/ebook-marketing.jpg" \ --value 47.00 \ --pix --credit-card --boleto \ --installments 3 ``` ### Ligar e desligar o Pix Automático O par `--pix-automatic` / `--no-pix-automatic` controla o débito recorrente do Pix. Ele vem **desligado** por padrão e só faz sentido em produtos de assinatura. ```bash Criar com Pix Automático theme={null} garu products create \ --name "Plano Mensal" \ --image "https://meusite.com/imagens/plano-mensal.jpg" \ --value 49.90 \ --pix --credit-card --pix-automatic \ --subscription --subscription-type monthly ``` ```bash Ligar em um produto existente theme={null} garu products update 456 --pix-automatic ``` ```bash Desligar theme={null} garu products update 456 --no-pix-automatic ``` Entenda como o cliente autoriza uma vez no banco e os próximos ciclos caem sozinhos. ## Cobranças agendadas com Pix Automático `garu scheduled-charges create` agenda uma cobrança para um cliente já cadastrado. A partir da CLI 0.7.0, o `--methods` aceita o valor **`pix_automatic`** para débito recorrente. O `--amount` é em Reais e precisa ser de pelo menos `5.00` (R\$ 5,00), na cobrança avulsa e na recorrente. Abaixo disso a API responde `400`. `--methods pix_automatic` **exige** `--type recurring` **e** um `--product-id`, e o produto precisa ter sido criado com `--pix-automatic`. Sem isso, a cobrança volta `400`. ### Receita completa: produto + cobrança recorrente Crie o produto com Pix Automático ligado e depois agende a série recorrente apontando para ele: ```bash theme={null} garu products create \ --name "Plano Mensal" \ --image "https://meusite.com/imagens/plano-mensal.jpg" \ --value 49.90 \ --pix --credit-card --pix-automatic \ --subscription --subscription-type monthly ``` Guarde o `id` retornado — você vai usá-lo no `--product-id` do próximo passo. ```bash theme={null} garu scheduled-charges create --customer-id 42 --product-id 456 \ --amount 49.90 --type recurring --due-date 2026-06-15 \ --methods pix_automatic --recurrence-interval monthly ``` O cliente autoriza a recorrência uma vez no app do banco e os ciclos seguintes são debitados sozinhos. Tanto `--amount` (em `scheduled-charges`) quanto `--value` (em `products`) são informados em **Reais** (`49.90`). Mesma unidade nos dois comandos. Como o cliente autoriza, os próximos ciclos e o modelo de falha. Receita de ponta a ponta via API, SDK e MCP. ## Recursos Código-fonte e documentação técnica @garuhq/cli no npm ## Próximos Passos Conecte agentes de IA à API Garu Use a SDK para integrar no seu código # Cadastro de clientes Source: https://docs.garu.com.br/guias/clientes Mantenha sua agenda de clientes no Garu, com dedup automática por CPF/CNPJ e e-mail de cobrança configurável por cliente. ## Visão Geral A partir da v0.4.0, você pode manter uma agenda de **clientes** no seu account do Garu. É a base para os próximos recursos de **cobranças agendadas** e **portal do cliente** — mas já é útil sozinho: * Cadastro explícito (sem precisar criar uma cobrança). * E-mail de cobrança configurável por cliente, fixo mesmo se o cliente trocar de e-mail. **Onde fica:** Acesse `/clientes` no menu lateral do dashboard. Pela API, use os endpoints abaixo. ## Modelo de dados ```text theme={null} Customer (registro do cliente) document, name, email, phone, ... ← último valor visto CustomerSellerProfile (por seller × cliente) email, phone, ... ← último valor visto neste seller billingEmailOverride ← e-mail fixo (opcional) Resolução do e-mail de cobrança: billingEmailOverride ?? CustomerSellerProfile.email ?? Customer.email ``` Cada vendedor enxerga apenas os clientes vinculados ao próprio account. ## Cadastrando via dashboard No menu lateral, clique em **Clientes**. Botão Vesúvio no canto superior direito da página. Nome completo, CPF ou CNPJ (apenas dígitos), e-mail e telefone. O cliente passa a aparecer na sua agenda imediatamente. ## Cadastrando via API A partir da CLI/SDK `1.x` (v1 pública), use `/api/v1/customers` — camelCase, chaveado por `uuid`. Veja a referência completa em [Clientes](/api-reference/clientes/criar). ### Registrar cliente ```bash theme={null} curl -X POST https://garu.com.br/api/v1/customers \ -H "Authorization: Bearer sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "name": "Maria Silva", "document": "12345678901", "email": "maria@exemplo.com", "phone": "11999999999", "personType": "fisica" }' ``` Resposta: ```json theme={null} { "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "name": "Maria Silva", "document": "12345678901", "email": "maria@exemplo.com", "billingEmail": "maria@exemplo.com", "hasBillingEmailOverride": false } ``` ### Definir um e-mail de cobrança fixo Útil quando o cliente quer que cobranças venham para um e-mail específico (ex: [financeiro@empresa.com.br](mailto:financeiro@empresa.com.br)). Veja [Definir e-mail de cobrança](/api-reference/clientes/definir-email-cobranca): ```bash theme={null} curl -X PATCH https://garu.com.br/api/v1/customers/a1b2c3d4-e5f6-7890-abcd-ef1234567890/billing-email-override \ -H "Authorization: Bearer sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "billingEmailOverride": "financeiro@empresa.com.br" }' ``` Para limpar o override e voltar ao e-mail padrão: ```bash theme={null} curl -X PATCH https://garu.com.br/api/v1/customers/a1b2c3d4-e5f6-7890-abcd-ef1234567890/billing-email-override \ -H "Authorization: Bearer sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "billingEmailOverride": null }' ``` ### Listar clientes ```bash theme={null} curl "https://garu.com.br/api/v1/customers?page=1&limit=20&search=maria" \ -H "Authorization: Bearer sk_live_xxx" ``` ### Buscar um cliente ```bash theme={null} curl https://garu.com.br/api/v1/customers/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ -H "Authorization: Bearer sk_live_xxx" ``` ## Permissões | Ação | Permissão | | - | - | | Listar / ver clientes | `customer:view` | | Cadastrar cliente | `customer:create` | | Atualizar cliente / e-mail de cobrança | `customer:edit` | | Desvincular cliente do seller | `customer:delete` | Ajuste em **Configurações → Equipe** se precisar de papéis personalizados. ## Próximos passos * [**Cobranças agendadas**](/guias/cobrancas-agendadas) — agende PIX/Boleto para uma data futura para um cliente cadastrado. A Garu envia o e-mail no vencimento e alerta seu time financeiro se atrasar. * Em breve: portal do cliente em `/minha-area` para o próprio cliente acompanhar e pagar cobranças sem login. # Cobranças agendadas Source: https://docs.garu.com.br/guias/cobrancas-agendadas Agende cobranças PIX e Boleto para datas futuras. A Garu envia o e-mail no vencimento, alerta seu time se atrasar e expõe ações para adiar, pausar, retomar ou marcar como paga. ## Visão Geral A partir da v0.5.0, você pode **agendar cobranças** para um cliente já cadastrado. A Garu cuida do resto: * E-mail para o cliente no dia do vencimento, com um link de pagamento já com PIX ou Boleto pronto. * Alertas para o time financeiro a partir de D+1, D+2 e D+3 se a cobrança ficar em atraso. * Estado da cobrança visível no dashboard (`Próximas`, `Vencidas`, `Pausadas`, `Concluídas`). * API completa para automatizar o fluxo via SDK ou MCP. **Onde fica:** Acesse `/cobrancas-agendadas` no menu lateral do dashboard. Pela API, use os endpoints abaixo (ou o SDK Node `@garuhq/node` 0.5+). ## O que está suportado nesta versão | Recurso | Status | | - | - | | Cobranças avulsas (`one_time`) | ✓ | | PIX e Boleto | ✓ | | Cobranças recorrentes (`recurring`) com [Pix Automático](/guias/pix-automatico) | ✓ | | Cartão | Em uma versão futura (precisa de tokenização do cliente) | ## Modelo de dados ```text theme={null} ScheduledCharge id (sch_…) customerId → Customer já cadastrado neste seller productId? → opcional; valor pode ser pré-preenchido a partir do produto amount → BRL decimal (ex: 297.50) type → 'one_time' | 'recurring' dueDate → YYYY-MM-DD em horário de São Paulo methods[] → ['pix'] | ['boleto'] | ['pix', 'boleto'] | ['pix_automatic'] status → scheduled | due_today | overdue | paid | paused | canceled description? externalReference? metadata? ScheduledChargeEvent (timeline append-only por cobrança) created, postponed, paused, resumed, manually_marked_paid, paid, overdue_reminder_sent, ... ``` ## Ciclo de vida Cobrança nasce com status `scheduled`. A Garu envia e-mail ao cliente com o link de pagamento. Status vira `due_today`. Pagou pelo link → `paid` automaticamente. Não pagou → `overdue` em D+1. A Garu manda lembretes para o time financeiro em D+1, D+2 e D+3, e dispara o webhook `scheduled_charge.overdue` em cada estágio. Adiar, pausar, retomar ou marcar como paga (caso o cliente tenha pago fora do Garu). ## Agendando via dashboard No menu lateral, clique em **Cobranças**. Botão Vesúvio no canto superior direito. Cliente → Produto (opcional) → Valor + descrição + vencimento → Métodos (PIX/Boleto) → Revisão. A cobrança aparece na aba **Próximas**. No dia do vencimento o cliente recebe o e-mail. ## Agendando via API ### Criar cobrança ```bash theme={null} curl -X POST https://garu.com.br/api/v1/scheduled-charges \ -H "Authorization: Bearer sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "customerId": 42, "amount": 297.50, "type": "one_time", "dueDate": "2026-06-15", "methods": ["pix", "boleto"], "description": "Mensalidade Junho" }' ``` Resposta (resumida): ```json theme={null} { "id": "sch_abc123", "customerId": 42, "amount": 297.5, "type": "one_time", "dueDate": "2026-06-15", "methods": ["pix", "boleto"], "status": "scheduled", "createdAt": "2026-05-01T12:00:00Z" } ``` O `amount` é em Reais (BRL decimal), nunca em centavos, e precisa ser de \*\*pelo menos R$ 5,00**, o valor mínimo da plataforma por cobrança, na cobrança avulsa e na recorrente. Abaixo disso a API responde `400` com "O valor mínimo de uma cobrança é R$ 5,00.". As cobranças agendadas criadas antes dessa regra mantêm o valor e continuam sendo cobradas. Envie o header `X-Idempotency-Key` na criação: a mesma chave devolve a série original por 24h, então um retry após falha de rede não cria uma segunda série. O SDK Node só envia o header quando você passa `idempotencyKey`. Derive a chave de algo estável no seu sistema. ### Cobrança recorrente com Pix Automático Para uma série recorrente debitada automaticamente, use `type: "recurring"` com `methods: ["pix_automatic"]`. O cliente autoriza a recorrência uma vez no app do banco e os ciclos seguintes caem sozinhos — veja o [guia do Pix Automático](/guias/pix-automatico). ```bash theme={null} curl -X POST https://garu.com.br/api/v1/scheduled-charges \ -H "Authorization: Bearer sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "customerId": 42, "productId": 456, "amount": 297.50, "type": "recurring", "dueDate": "2026-06-15", "methods": ["pix_automatic"], "recurrence": { "interval": "monthly", "intervalCount": 1, "endsAfter": 12 }, "description": "Plano Premium - mensal" }' ``` `methods` com `pix_automatic` **exige** `type: "recurring"` **e** um `productId`, e o produto precisa ter `pixAutomatic: true`. Caso contrário, a criação volta `400`. A receita completa está em [Como integrar Pix Automático](/guias/integrar-pix-automatico). ### Listar cobranças ```bash theme={null} # Próximas (scheduled + due_today) curl "https://garu.com.br/api/v1/scheduled-charges?status=scheduled&status=due_today" \ -H "Authorization: Bearer sk_live_xxx" # Em atraso de um cliente específico curl "https://garu.com.br/api/v1/scheduled-charges?status=overdue&customerId=42" \ -H "Authorization: Bearer sk_live_xxx" ``` ### Buscar uma cobrança (com timeline e transações) ```bash theme={null} curl https://garu.com.br/api/v1/scheduled-charges/sch_abc123 \ -H "Authorization: Bearer sk_live_xxx" ``` A resposta é um pacote com a cobrança, eventos do ciclo de vida e transações geradas: ```json theme={null} { "charge": { "id": "sch_abc123", "status": "paid", "amount": 297.5, "...": "..." }, "events": [ { "eventType": "created", "actor": { "type": "user", "id": 1 }, "createdAt": "..." }, { "eventType": "paid", "actor": { "type": "system" }, "createdAt": "..." } ], "transactions": [ { "id": 4472, "value": 297.5, "paymentMethod": "pix", "status": "payedPix", "...": "..." } ] } ``` `charge.amount` e `transactions[].value` estão ambos em BRL decimal (`297.50`) — nunca centavos. ### Ações no ciclo de vida | Ação | Endpoint | Status permitidos | | - | - | - | | Cobrar agora | [`POST /api/v1/scheduled-charges/{id}/charge-now`](/api-reference/cobrancas-agendadas/cobrar-agora) | `scheduled` / `due_today` | | Adiar | [`POST /api/v1/scheduled-charges/{id}/postpone`](/api-reference/cobrancas-agendadas/adiar) | `scheduled` / `due_today` / `overdue` / `paused` | | Pausar | [`POST /api/v1/scheduled-charges/{id}/pause`](/api-reference/cobrancas-agendadas/pausar) | `scheduled` / `due_today` / `overdue` | | Retomar | [`POST /api/v1/scheduled-charges/{id}/resume`](/api-reference/cobrancas-agendadas/retomar) | `paused` | | Marcar como paga | [`POST /api/v1/scheduled-charges/{id}/mark-paid`](/api-reference/cobrancas-agendadas/marcar-como-paga) | `due_today` / `overdue` | ```bash theme={null} # Adiar para uma nova data curl -X POST https://garu.com.br/api/v1/scheduled-charges/sch_abc123/postpone \ -H "Authorization: Bearer sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "newDueDate": "2026-07-01", "reason": "cliente pediu mais prazo" }' # Marcar como paga (cliente pagou via TED, fora do Garu) curl -X POST https://garu.com.br/api/v1/scheduled-charges/sch_abc123/mark-paid \ -H "Authorization: Bearer sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "paymentDate": "2026-06-20", "externalReference": "TED 4472881" }' ``` ### Cobrar agora (sem esperar o vencimento) Quer disparar a cobrança e o e-mail ao cliente na hora, antes do `dueDate`? Use `charge-now`. Roda o mesmo fluxo do cron diário, só que antecipado, e é **idempotente por ciclo** — se o ciclo já foi disparado, volta `already_sent` e não cobra de novo. ```bash theme={null} curl -X POST https://garu.com.br/api/v1/scheduled-charges/sch_abc123/charge-now \ -H "Authorization: Bearer sk_live_xxx" ``` A resposta traz o `outcome` (`dispatched` / `already_sent` / `not_sent` / `failed`) e uma `message` em pt-BR pronta para exibir. Veja [Cobrar agora](/api-reference/cobrancas-agendadas/cobrar-agora) para todos os resultados e motivos. Na criação, o campo opcional `maxRecoveryDays` (1–365, padrão `14`) define por quantos dias a Garu tenta recuperar automaticamente uma cobrança cujo disparo o cron tenha perdido. `charge-now` é o atalho manual para o mesmo efeito. ## Webhooks emitidos Configure um endpoint em **Configurações → Desenvolvedores → Webhooks** para receber: | Evento | Quando | | - | - | | `scheduled_charge.created` | Cobrança agendada com sucesso | | `scheduled_charge.postponed` | Data alterada | | `scheduled_charge.paused` | Cobrança pausada | | `scheduled_charge.resumed` | Cobrança retomada | | `scheduled_charge.charged` | E-mail D-day enviado ao cliente | | `scheduled_charge.overdue` | Atrasou; emitido em D+1, D+2 e D+3 | | `scheduled_charge.paid` | Pagamento confirmado | | `scheduled_charge.failed` | Pagamento recusado | Verifique a assinatura HMAC do webhook como em qualquer outro evento Garu — veja [Webhooks](/api-reference/webhooks). ## Notificações para o time Quando uma cobrança fica em atraso, a Garu notifica os membros do time que tenham pelo menos uma das permissões: * `transaction:view` * `billing:view` * `admin:view` Cada membro pode silenciar o alerta em **Configurações → Notificações → Cobranças agendadas**. ## Permissões | Ação | Permissão | | - | - | | Listar / ver cobranças | `scheduled_charge:view` | | Criar cobrança | `scheduled_charge:create` | | Cobrar agora / adiar / pausar / retomar / marcar como paga | `scheduled_charge:edit` | | Cancelar cobrança | `scheduled_charge:delete` | Por papel (padrão da Garu): * **Owner / Administrator**: tudo. * **Support**: ver, editar, cancelar (não cria). * **Developer / Analyst / View Only**: apenas ver. Ajuste em **Configurações → Equipe** se precisar de papéis personalizados. ## SDK e MCP * **Node SDK**: [Cobranças agendadas no `@garuhq/node`](/guias/node-sdk#scheduledcharges). * **MCP server**: [Tools de cobranças no Garu MCP](/guias/mcp-server#scheduled-charges). ## Próximos passos * [Pix Automático](/guias/pix-automatico) em cobranças recorrentes (já disponível com `type: "recurring"`). * Cartão de crédito como método agendável (após tokenização do cliente). * Portal do cliente em `/minha-area` para o próprio cliente acompanhar e pagar cobranças sem login. # Criando Produtos Source: https://docs.garu.com.br/guias/criando-produtos Guia completo para criar produtos e começar a vender via API ## Visão Geral Este guia cobre tudo que você precisa saber para criar produtos na Garu e começar a receber pagamentos. Ao final, você terá: * Um produto configurado com todos os métodos de pagamento * Um link de pagamento para compartilhar com seus clientes * Webhooks configurados para receber notificações de pagamento **Pré-requisito:** Você precisa de uma chave de API. Veja como obter em [Autenticação](/api-reference/autenticacao). ## Tipos de Produto A Garu suporta dois tipos de produto: | Tipo | Descrição | Uso | | - | - | - | | **Venda Avulsa** | Pagamento único | Cursos, e-books, produtos digitais | | **Assinatura** | Pagamento recorrente | SaaS, membresias, serviços mensais | Este guia foca em **vendas avulsas**. Para assinaturas, consulte o [guia de Assinaturas](/guias/assinaturas). ## Estrutura de um Produto Um produto na Garu possui os seguintes campos: ### Campos Obrigatórios | Campo | Tipo | Descrição | | - | - | - | | `name` | string | Nome do produto (exibido ao cliente) | | `image` | string | URL da imagem do produto | | `value` | number | Preço em **Reais** (ex: `47.00`), nunca em centavos. `0` = produto sem preço; qualquer outro preço precisa ser de pelo menos R\$ 5,00 | ### Campos Opcionais (com valores padrão) | Campo | Tipo | Padrão | Descrição | | - | - | - | - | | `description` | string | `""` | Descrição do produto | | `pix` | boolean | `false` | Habilitar pagamento via PIX | | `creditCard` | boolean | `false` | Habilitar cartão de crédito | | `boleto` | boolean | `false` | Habilitar boleto bancário | | `pixAutomatic` | boolean | `true` | Habilitar [Pix Automático](/guias/pix-automatico) (débito recorrente) no checkout de assinatura | | `installments` | number | `1` | Parcelas máximas (1-12) | | `isSubscription` | boolean | `false` | Produto de assinatura: o preço fica nos [preços de assinatura](/api-reference/assinaturas/precos/criar) | ### Campos Opcionais (sem padrão) | Campo | Tipo | Descrição | | - | - | - | | `tags` | string\[] | Tags para categorização | | `returnUrl` | string | URL de redirecionamento pós-pagamento | | `returnUrlButtonText` | string | Texto do botão de retorno | | `pixelFB` | string | ID do Facebook Pixel | Os métodos de pagamento vêm **desligados** por padrão. Envie `pix`, `creditCard` e `boleto` com `true` para os que você quer aceitar. ## O Preço do Produto O `value` de um produto é sempre em **Reais** (`47.00` = R\$ 47,00) e segue uma regra simples: | `value` | Resultado | | - | - | | `0` | Aceito: o produto fica **sem preço** | | De `0.01` a `4.99` | Recusado com `400` | | Negativo | Recusado com `400` | | `5.00` ou mais | Aceito | R$ 5,00 é o preço mínimo da plataforma. Ele vale também para o preço de uma [oferta](#ofertas) e para o valor de um [preço de assinatura](/api-reference/assinaturas/precos/criar). Nesses dois casos não existe a opção de ficar sem preço: o mínimo é sempre R$ 5,00. Um produto sem preço **não vende pelo link de pagamento**: a cobrança de R$ 0,00 é recusada. Use-o para um produto que você cobra de outro jeito, como por [cobranças agendadas](/guias/cobrancas-agendadas), que levam o próprio valor. Para vender pelo link, dê ao produto um preço de pelo menos R$ 5,00. Ao [atualizar](/api-reference/produtos/atualizar) um produto, a regra só vale quando o pedido envia `value`. Um produto que já custava menos de R\$ 5,00 antes da regra continua vendendo pelo mesmo preço, e um pedido que não envia `value` mantém o preço antigo. ## Criando um Produto com Preço Para produtos de venda avulsa, defina o valor e personalize os métodos de pagamento: ```bash theme={null} curl -X POST https://garu.com.br/api/v1/products \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "name": "E-book: Guia de Marketing Digital", "description": "Aprenda as melhores estratégias de marketing digital", "image": "https://meusite.com/imagens/ebook-marketing.jpg", "value": 47.00, "pix": true, "creditCard": true, "boleto": true, "installments": 3 }' ``` **Resposta:** ```json theme={null} { "name": "E-book: Guia de Marketing Digital", "description": "Aprenda as melhores estratégias de marketing digital", "image": "https://meusite.com/imagens/ebook-marketing.jpg", "value": 47.00, "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "pix": true, "creditCard": true, "boleto": true, "installments": [ { "quantity": 1, "value": 47.00 }, { "quantity": 2, "value": 24.30 }, { "quantity": 3, "value": 16.61 } ], "isActive": true, "createdAt": "2024-12-24T10:30:00.000Z" } ``` **Link de pagamento:** `https://garu.com.br/pay/a1b2c3d4-e5f6-7890-abcd-ef1234567890` Saiba mais sobre o formato dos links em [Link de Pagamento](/api-reference/produtos/link-pagamento). ## Criando um Produto Sem Preço Para um produto que você não vende pelo link, por exemplo um profissional de uma plataforma que você cobra por cobranças agendadas, envie `value: 0`: ```bash theme={null} curl -X POST https://garu.com.br/api/v1/products \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "name": "Coach Maria", "image": "https://meusite.com/imagens/coach-maria.jpg", "value": 0 }' ``` ## Criando um Produto Completo Exemplo com todas as opções disponíveis: ```bash theme={null} curl -X POST https://garu.com.br/api/v1/products \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "name": "Curso Completo de Python", "description": "Do zero ao avançado com projetos práticos. Inclui certificado.", "value": 297.00, "pix": true, "creditCard": true, "boleto": true, "installments": 12, "image": "https://meusite.com/imagens/curso-python.jpg", "tags": ["curso", "programacao", "python"], "returnUrl": "https://meusite.com/obrigado", "returnUrlButtonText": "Acessar o Curso", "pixelFB": "123456789012345" }' ``` ## Vendendo o Mesmo Produto por Outro Preço (Ofertas) Uma **oferta** é um preço com nome em um produto, com link próprio. Use para vender o mesmo produto por R$ 497,00 na página de vendas e por R$ 97,00 para a sua lista de e-mails, sem duplicar o produto. A oferta muda **só o preço**: métodos de pagamento, parcelas, nome, descrição e imagem continuam os do produto. ```bash theme={null} curl -X POST https://garu.com.br/api/v1/products/a1b2c3d4-e5f6-7890-abcd-ef1234567890/offers \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "name": "Black Friday", "value": 97.00, "slug": "black-friday" }' ``` O link da oferta é `https://garu.com.br/pay/{uuid}?offer=black-friday`. O link do produto sem `?offer=` continua cobrando o preço do produto. | Campo | Descrição | | - | - | | `name` | Seu rótulo interno. **Nunca aparece para o comprador** | | `value` | Preço em **Reais**, na mesma unidade do `value` do produto. Precisa ser de **pelo menos R\$ 5,00**. Diferente do produto, a oferta não pode ter preço `0`: de `0` a `4.99` a API responde `400` | | `slug` | Opcional. Letras minúsculas, números e hífens, de 3 a 40 caracteres. É **público e fácil de adivinhar**: sem ele, o link usa o id da oferta | | `isActive` | Padrão `true`. Para encerrar uma promoção, envie `false` com `PATCH /api/v1/offers/{offerId}` | Ao mudar o preço de uma oferta com `PATCH /api/v1/offers/{offerId}`, o mínimo vale para o novo `value`. Um pedido sem `value` (por exemplo, só para desativar a oferta) passa normalmente e mantém o preço. ## Configurando Métodos de Pagamento ### PIX O PIX oferece confirmação instantânea e é o método preferido para produtos digitais. ```json theme={null} { "pix": true } ``` **Vantagens:** * Confirmação em segundos * Sem taxas para o cliente * Maior taxa de conversão ### Cartão de Crédito Permite parcelamento, ideal para produtos de maior valor. ```json theme={null} { "creditCard": true, "installments": 12 } ``` **Parcelamento:** * O valor de cada parcela é calculado automaticamente * Inclui juros conforme configurado na sua conta ### Boleto Para clientes que preferem não usar cartão ou PIX. ```json theme={null} { "boleto": true } ``` **Considerações:** * Pagamento leva 1-3 dias úteis para compensar * Maior taxa de abandono ### Pix Automático Débito recorrente do Pix: o cliente autoriza uma vez no app do banco e os próximos ciclos são debitados sozinhos. Ideal para mensalidades e assinaturas sem cartão. ```json theme={null} { "pixAutomatic": true } ``` **Considerações:** * Vem ligado por padrão (`true`) na API; envie `false` para não oferecer * Só faz sentido para cobranças recorrentes * Habilita a aba **Pix Automático** no checkout do produto Prefere o terminal? A [CLI](/guias/cli) liga o Pix Automático na criação com a flag `--pix-automatic`: ```bash theme={null} garu products create \ --name "Plano Mensal" \ --image "https://meusite.com/imagens/plano-mensal.jpg" \ --value 49.90 \ --pix --credit-card --pix-automatic \ --subscription --subscription-type monthly ``` `--value` é em **Reais**: `49.90` é R$ 49,90. Não use centavos, porque `--value 4990` cadastraria um produto de R$ 4.990,00. Entenda como o cliente autoriza, o que acontece nos próximos ciclos e como cancelar. **Recomendação:** Habilite todos os métodos de pagamento para maximizar conversões. Deixe o cliente escolher. ## URL de Retorno Personalizada Configure para onde o cliente será redirecionado após o pagamento: ```json theme={null} { "returnUrl": "https://meusite.com/obrigado", "returnUrlButtonText": "Acessar Meu Produto" } ``` Após o pagamento, o cliente verá: 1. Uma mensagem de sucesso 2. Um botão com o texto "Acessar Meu Produto" 3. Ao clicar, será redirecionado para `https://meusite.com/obrigado` ### Passando Dados na URL de Retorno Você pode incluir parâmetros dinâmicos: ```json theme={null} { "returnUrl": "https://meusite.com/ativar?produto=curso-python" } ``` A URL de retorno é apenas visual. Sempre valide o pagamento via webhook antes de liberar acesso. ## Rastreamento com Facebook Pixel Para rastrear conversões no Facebook Ads: ```json theme={null} { "pixelFB": "123456789012345" } ``` Eventos disparados automaticamente: * `PageView` - Quando a página de checkout carrega * `InitiateCheckout` - Quando o cliente inicia o checkout * `Purchase` - Quando o pagamento é confirmado ## Usando Tags para Organização Tags ajudam a categorizar e filtrar produtos: ```json theme={null} { "tags": ["curso", "programacao", "python", "iniciante"] } ``` **Boas práticas:** * Use tags consistentes em todos os produtos * Inclua categoria, tipo e nível * Evite tags muito específicas ## Fluxo Completo de Integração ### 1. Criar Produto ```javascript theme={null} // Exemplo mínimo - name, image e value são obrigatórios const produtoSimples = await criarProduto({ name: 'Meu Produto', image: 'https://meusite.com/imagens/meu-produto.jpg', value: 49.90, pix: true }); // Exemplo completo - com todas as opções const produto = await criarProduto({ name: 'Curso de Python', description: 'Aprenda Python do zero', image: 'https://meusite.com/imagens/curso-python.jpg', value: 297.00, pix: true, creditCard: true, boleto: true, installments: 12, returnUrl: 'https://meusite.com/obrigado' }); // Salvar UUID no banco de dados await salvarNoBanco({ uuid: produto.uuid, nome: produto.name }); ``` ### 2. Exibir Link de Pagamento ```javascript theme={null} const linkPagamento = `https://garu.com.br/pay/${produto.uuid}`; // Enviar por email, exibir no site, etc. ``` ### 3. Receber Webhook de Pagamento Configure webhooks para receber notificações em tempo real. Veja a [documentação de Webhooks](/api-reference/webhooks) para todos os eventos disponíveis. ```javascript theme={null} app.post('/webhooks/garu', async (req, res) => { // Validar assinatura if (!validarAssinatura(req)) { return res.status(401).json({ error: 'Inválido' }); } const { event, data } = req.body; if (event === 'transaction.paid') { // Liberar acesso ao produto await liberarAcesso({ email: data.customer.email, produtoId: data.productId }); // Enviar email de boas-vindas await enviarEmail({ para: data.customer.email, assunto: 'Seu acesso está liberado!', conteudo: 'Clique aqui para acessar seu produto...' }); } res.status(200).json({ received: true }); }); ``` ## Erros Comuns ```json theme={null} { "statusCode": 400, "message": "O preço mínimo de um produto é R$ 5,00. Use R$ 0,00 para um produto sem preço." } ``` **Solução:** Envie um `value` de pelo menos `5.00`, ou `0` para um produto sem preço. De R$ 0,01 a R$ 4,99 o produto é recusado. ```json theme={null} { "message": "At least one payment method is required" } ``` **Solução:** Habilite pelo menos `pix`, `creditCard` ou `boleto`. ```json theme={null} { "message": ["installments must be between 1 and 12"] } ``` **Solução:** Use um valor entre 1 e 12. ## Checklist de Criação Antes de criar um produto, verifique: * [ ] Nome claro e descritivo * [ ] URL da imagem do produto * [ ] Preço em Reais: pelo menos R\$ 5,00 para vender pelo link, ou `0` para um produto sem preço * [ ] URL de retorno configurada (opcional mas recomendado) * [ ] Webhook configurado para receber confirmações `name`, `image` e `value` são obrigatórios. Os métodos de pagamento (PIX, Cartão, Boleto) vêm desligados por padrão: ligue os que você quer aceitar. Precisa listar, atualizar ou excluir produtos? Consulte a referência completa: [Listar](/api-reference/produtos/listar), [Atualizar](/api-reference/produtos/atualizar), [Excluir](/api-reference/produtos/excluir). ## Próximos Passos Configure notificações de pagamento Checklist para lançar em produção # Claude Code Source: https://docs.garu.com.br/guias/integracoes/claude-code Aceite pagamentos com Claude Code e Garu ## Aceite pagamentos com Claude Code Conecte a Garu ao Claude Code e crie cobranças, gerencie clientes e consulte transações diretamente pelo terminal. ### Instale o MCP Server ```bash theme={null} claude mcp add garu -- npx -y --package=@garuhq/mcp@latest garu-mcp ``` Depois, defina a variável de ambiente `GARU_API_KEY` no seu projeto ou shell: ```bash theme={null} export GARU_API_KEY=YOUR_API_KEY ``` Substitua `YOUR_API_KEY` pela sua chave de API. Veja como obter em [Autenticação](/api-reference/autenticacao). ### Instale as Skills ```bash theme={null} npx skills add Garu-Pagamentos/garu-skills ``` As skills ensinam o Claude a seguir boas práticas de pagamento (LGPD, PCI-DSS, tratamento de erros). ### Comece a usar Peça ao Claude para executar tarefas de pagamento. Experimente: ``` Crie uma cobrança PIX de R$49,90 para joao@email.com ``` ## Mais Exemplos de Prompts ``` Liste todas as cobranças pagas do último mês ``` ``` Cadastre o cliente Maria Silva, email maria@email.com, CPF 123.456.789-00 ``` ## Recursos # Codex Source: https://docs.garu.com.br/guias/integracoes/codex Aceite pagamentos com OpenAI Codex e Garu ## Aceite pagamentos com Codex Conecte a Garu ao Codex e deixe o agente criar cobranças e gerenciar clientes diretamente no terminal. ### Instale o MCP Server ```bash theme={null} codex mcp add garu --env GARU_API_KEY=YOUR_API_KEY -- npx -y --package=@garuhq/mcp@latest garu-mcp ``` Substitua `YOUR_API_KEY` pela sua chave de API. Veja como obter em [Autenticação](/api-reference/autenticacao). ### Instale as Skills ```bash theme={null} npx skills add Garu-Pagamentos/garu-skills ``` As skills são adicionadas ao `AGENTS.md` e ensinam o Codex a seguir boas práticas de pagamento. ### Comece a usar Peça ao agente para executar tarefas de pagamento. Experimente: ``` Crie uma cobrança PIX de R$49,90 para joao@email.com ``` ## Mais Exemplos de Prompts ``` Implemente uma API de checkout usando a SDK da Garu ``` ``` Reembolse a cobrança #12345 ``` ## Recursos # Cursor Source: https://docs.garu.com.br/guias/integracoes/cursor Aceite pagamentos com Cursor e Garu ## Aceite pagamentos com Cursor Conecte a Garu ao Cursor e deixe o agente criar cobranças, consultar transações e gerenciar clientes enquanto você codifica. ### Configure o MCP Server Crie ou edite o arquivo `.cursor/mcp.json` na raiz do seu projeto: ```json theme={null} { "mcpServers": { "garu": { "command": "npx", "args": ["-y", "--package=@garuhq/mcp@latest", "garu-mcp"], "env": { "GARU_API_KEY": "YOUR_API_KEY" } } } } ``` Substitua `YOUR_API_KEY` pela sua chave de API. Veja como obter em [Autenticação](/api-reference/autenticacao). ### Instale as Skills ```bash theme={null} npx skills add Garu-Pagamentos/garu-skills ``` As skills são instaladas em `.cursor/rules/` e ensinam o Cursor a seguir boas práticas de pagamento. ### Reinicie o Cursor Feche e reabra o Cursor para carregar o servidor MCP. Você verá as ferramentas da Garu disponíveis no painel de ferramentas MCP. ### Comece a usar Peça ao agente para executar tarefas de pagamento. Experimente: ``` Crie uma cobrança PIX de R$49,90 para joao@email.com ``` ## Mais Exemplos de Prompts ``` Integre o checkout da Garu nessa página de pagamento ``` ``` Liste os clientes cadastrados e mostre em uma tabela ``` ## Recursos # Windsurf Source: https://docs.garu.com.br/guias/integracoes/windsurf Aceite pagamentos com Windsurf e Garu ## Aceite pagamentos com Windsurf Conecte a Garu ao Windsurf e deixe o agente criar cobranças, consultar transações e gerenciar clientes enquanto você codifica. ### Configure o MCP Server Crie ou edite o arquivo `.windsurf/mcp.json` na raiz do seu projeto: ```json theme={null} { "mcpServers": { "garu": { "command": "npx", "args": ["-y", "--package=@garuhq/mcp@latest", "garu-mcp"], "env": { "GARU_API_KEY": "YOUR_API_KEY" } } } } ``` Substitua `YOUR_API_KEY` pela sua chave de API. Veja como obter em [Autenticação](/api-reference/autenticacao). ### Instale as Skills ```bash theme={null} npx skills add Garu-Pagamentos/garu-skills ``` ### Reinicie o Windsurf Feche e reabra o Windsurf para carregar o servidor MCP. ### Comece a usar Peça ao agente para executar tarefas de pagamento. Experimente: ``` Crie uma cobrança PIX de R$49,90 para joao@email.com ``` ## Mais Exemplos de Prompts ``` Integre pagamentos da Garu nessa aplicação ``` ``` Qual o status da cobrança #12345? ``` ## Recursos # Como integrar Pix Automático Source: https://docs.garu.com.br/guias/integrar-pix-automatico Receita prática: ative o Pix Automático no produto, cobre via cobrança agendada ou checkout público, escute os webhooks e trate as falhas. ## Visão Geral Este guia é a receita de ponta a ponta para colocar o **Pix Automático** no ar. Se você ainda não sabe o que é ou como o cliente autoriza, comece pela [visão geral do Pix Automático](/guias/pix-automatico). Tudo aqui é **aditivo**: se você já cobra com Pix, Boleto ou cartão, nada do que existe muda. Você só passa a ter mais uma opção de recorrência. O fluxo tem duas formas de cobrar (escolha uma) e dois passos que valem para as duas: Liga o campo `pixAutomatic` no produto. **Opção A:** cobrança agendada recorrente via API. **Opção B:** checkout público (o cliente autoriza pela página da Garu). `subscription.activated`, `transaction.payment.succeeded` e companhia. Mesma lógica das assinaturas no cartão — sem código novo. ## Step 1: ativar no produto O Pix Automático só aparece para o cliente quando o produto tem `pixAutomatic: true`. Ligue com um `PATCH` no produto (funciona também no `POST` de criação): ```bash cURL theme={null} curl -X PATCH https://garu.com.br/api/v1/products/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ -H "Authorization: Bearer sk_live_xxx" \ -H "Content-Type: application/json" \ -d '{ "pixAutomatic": true }' ``` ```javascript Node theme={null} const response = await fetch('https://garu.com.br/api/v1/products/a1b2c3d4-e5f6-7890-abcd-ef1234567890', { method: 'PATCH', headers: { 'Authorization': `Bearer ${process.env.GARU_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ pixAutomatic: true }) }); const product = await response.json(); console.log(product.pixAutomatic); // true ``` `pixAutomatic` é opcional e vale `false` por padrão. Ligar não desativa nada: PIX, Boleto e cartão continuam exatamente como estavam no produto. ## Step 2 — Opção A: cobrança agendada recorrente Use esta opção quando **você** dispara a recorrência pela API (ex.: o cliente já está cadastrado e você quer agendar a série de cobranças). Mande `pix_automatic` no array `methods` do [`POST /api/v1/scheduled-charges`](/api-reference/cobrancas-agendadas/criar). `pix_automatic` exige `type: "recurring"` **e** um `productId` — e o produto precisa ter `pixAutomatic: true`. Sem isso, a criação volta `400`. ```bash theme={null} curl -X POST https://garu.com.br/api/v1/scheduled-charges \ -H "Authorization: Bearer sk_live_xxx" \ -H "Content-Type: application/json" \ -H "X-Idempotency-Key: 6c4c2a1e-4c2b-4f1c-9a8d-1b2e3f4a5b6c" \ -d '{ "customerId": 123, "productId": 456, "amount": 297.50, "type": "recurring", "dueDate": "2026-06-15", "methods": ["pix_automatic"], "recurrence": { "interval": "monthly", "intervalCount": 1, "endsAfter": 12 }, "description": "Plano Premium - mensal" }' ``` ```javascript theme={null} import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY }); const charge = await garu.scheduledCharges.create({ customerId: 123, productId: 456, amount: 297.5, type: 'recurring', dueDate: '2026-06-15', methods: ['pix_automatic'], recurrence: { interval: 'monthly', intervalCount: 1, endsAfter: 12 }, description: 'Plano Premium - mensal' }); console.log(charge.id); // sch_abc123 ``` Com o [Garu MCP server](/guias/mcp-server) conectado, peça em linguagem natural ao seu agente: ```text theme={null} Agende uma cobrança recorrente mensal de R$ 297,50 no Pix Automático para o cliente 123, do produto 456, começando em 15/06/2026, por 12 ciclos. ``` O agente chama a tool `create_scheduled_charge` com `type: "recurring"` e `methods: ["pix_automatic"]`. Lembre que o produto precisa estar com `pixAutomatic: true` antes. O `interval` aceita `weekly`, `biweekly`, `monthly`, `bimonthly`, `quarterly`, `biannual` e `yearly`. Os campos `endsAfter` (nº de ciclos) e `endsOn` (data final) são opcionais. Veja todos os parâmetros em [Criar cobrança agendada](/api-reference/cobrancas-agendadas/criar). ## Step 3 — Opção B: checkout público Use esta opção quando você prefere que **a Garu cuide da página de autorização**. Você só manda o cliente para o link de pagamento do produto — ele escolhe a aba **Pix Automático** e autoriza a recorrência pelo app do banco. ```text theme={null} https://garu.com.br/pay/{product_uuid}?priceId={price_id} ``` | Parâmetro | Origem | | - | - | | `product_uuid` | Campo `uuid` do produto (com `pixAutomatic: true`) | | `price_id` | `id` do [preço de assinatura](/api-reference/assinaturas/precos/criar) | A aba **Pix Automático** aparece automaticamente no checkout quando o produto tem o campo ligado. O resto do fluxo (autorização no app, ativação) está descrito em [Como o cliente autoriza](/guias/pix-automatico#como-o-cliente-autoriza). Se você renderiza o seu próprio checkout, pode iniciar a assinatura direto pela API pública e exibir o QR Code de autorização você mesmo. Mande `paymentMethod: "pix_automatic"`: ```bash theme={null} curl -X POST https://garu.com.br/api/subscribe \ -H "Content-Type: application/json" \ -d '{ "subscriptionPriceId": "price_abc", "paymentMethod": "pix_automatic", "customer": { "name": "João Silva", "document": "12345678901", "emails": ["joao@email.com"], "phones": ["11999998888"] } }' ``` A resposta traz o código Pix de autorização para você renderizar como QR Code: ```json theme={null} { "type": true, "paymentMethod": "pix_automatic", "subscriptionId": 27, "subscriptionUuid": "uuid-do-cliente", "status": "waitingPayment", "amount": 14.90, "brCode": "00020101...", "expiresAt": "2026-06-15T12:00:00.000Z" } ``` * **`brCode`** é o código Pix copia-e-cola (EMV). Renderize como QR Code para o cliente autorizar no banco. * **`subscriptionUuid`** é o que você usa para consultar o status publicamente. Erros possíveis: `400` (`Pix Automático não está habilitado para este produto.`) e `404`. ### Acompanhar a autorização (polling) Enquanto o cliente não confirma no app, a assinatura fica em `waitingPayment`. Consulte o status público (só com o UUID, sem chave de API) a cada 2–3 segundos até virar `active`: ```bash theme={null} curl https://garu.com.br/api/subscribe/uuid-do-cliente/status ``` ```json theme={null} { "status": "active" } ``` O `status` segue o ciclo de vida: `waitingPayment` → `active` → (`past_due` / `canceled` / `inactive`). ## Step 4: escutar os webhooks Pix Automático **não cria eventos novos** — ele reaproveita os mesmos webhooks de assinatura e transação que o cartão já usa. Para saber que a cobrança foi via Pix Automático, cheque `paymentMethod === 'pix_automatic'` no payload. Os principais para acompanhar: | Evento | Quando | | - | - | | `subscription.activated` | Cliente autorizou no banco; recorrência ativa | | `transaction.payment.succeeded` | Um ciclo foi debitado com sucesso | | `subscription.renewed` | Assinatura renovou para o próximo ciclo | | `subscription.payment_failed` | Um débito foi recusado (vai para `past_due`) | | `subscription.cancelled` | Encerrada — inclusive se o cliente revogou pelo banco | ```javascript theme={null} app.post('/webhooks/garu', async (req, res) => { // valide a assinatura HMAC antes de processar (ver /api-reference/webhooks) const { event, data } = req.body; switch (event) { case 'subscription.activated': // Pix Automático autorizado — libere o acesso await liberarAcesso(data.customerId); break; case 'transaction.payment.succeeded': if (data.paymentMethod === 'pix_automatic') { // ciclo do Pix Automático pago — registre o pagamento await registrarPagamento(data); } break; } res.status(200).json({ received: true }); }); ``` A validação de assinatura e as boas práticas (responder rápido, idempotência) são as mesmas de qualquer webhook Garu. Veja [Webhooks](/api-reference/webhooks). ## Step 5: tratar falhas Aqui está a melhor parte: **você não precisa de código novo para falhas.** Pix Automático usa a mesma régua de inadimplência das assinaturas no cartão. A diferença está só na rede: o Pix Automático **não retenta o débito recusado no nível da rede de pagamento**. Quando um débito é recusado, a Garu dispara `subscription.payment_failed`, coloca a assinatura em `past_due` e aplica a régua de cobrança (dunning) de sempre. ```javascript theme={null} app.post('/webhooks/garu', async (req, res) => { const { event, data } = req.body; if (event === 'subscription.payment_failed') { // Mesmo tratamento do cartão: a assinatura está em `past_due`. // A Garu cuida da régua de recuperação automaticamente. await avisarClienteInadimplencia(data.customerId); } if (event === 'subscription.cancelled') { // Régua esgotada OU cliente revogou no banco. Corte o acesso. await revogarAcesso(data.customerId); } res.status(200).json({ received: true }); }); ``` O percurso de inadimplência é `active` → `past_due` → (`active` se recuperar | `canceled` se a janela se esgotar). Detalhes do modelo de falha em [Pix Automático › Modelo de falha](/guias/pix-automatico#modelo-de-falha). ## Próximos passos O conceito, a autorização e o modelo de falha em detalhe. Referência completa do `POST /scheduled-charges`. Recorrência, preços e portal do cliente. Todos os eventos e como validar a assinatura. # MCP Server Source: https://docs.garu.com.br/guias/mcp-server Conecte agentes de IA à API Garu com o protocolo MCP ## O que é MCP? O **Model Context Protocol (MCP)** é um padrão aberto que permite que agentes de IA se conectem a ferramentas e serviços externos. Com o MCP Server da Garu, seu agente pode criar cobranças, consultar transações e gerenciar clientes diretamente pelo chat. **Por que isso importa?** * Seu agente de IA entende o contexto do seu código e cria cobranças sem sair do editor * Menos copy-paste de documentação, mais produtividade * Funciona com Claude Code, Cursor, Codex, Windsurf e qualquer cliente MCP **Pré-requisito:** Você precisa de uma chave de API. Veja como obter em [Autenticação](/api-reference/autenticacao). ## Instalação Escolha sua ferramenta e siga as instruções: ### Instale o servidor MCP ```bash theme={null} claude mcp add garu -- npx -y --package=@garuhq/mcp@latest garu-mcp ``` Depois, defina a variável de ambiente `GARU_API_KEY` no seu projeto ou shell: ```bash theme={null} export GARU_API_KEY=YOUR_API_KEY ``` Adicione ao arquivo `.cursor/mcp.json` do seu projeto: ```json theme={null} { "mcpServers": { "garu": { "command": "npx", "args": ["-y", "--package=@garuhq/mcp@latest", "garu-mcp"], "env": { "GARU_API_KEY": "YOUR_API_KEY" } } } } ``` ```bash theme={null} codex mcp add garu --env GARU_API_KEY=YOUR_API_KEY -- npx -y --package=@garuhq/mcp@latest garu-mcp ``` Adicione ao arquivo `.windsurf/mcp.json` do seu projeto: ```json theme={null} { "mcpServers": { "garu": { "command": "npx", "args": ["-y", "--package=@garuhq/mcp@latest", "garu-mcp"], "env": { "GARU_API_KEY": "YOUR_API_KEY" } } } } ``` **Boa prática:** Use sempre `sk_test_` em desenvolvimento. O MCP não distingue ambientes — quem distingue é a chave. Trocar para `sk_live_` em produção é responsabilidade sua. ### Teste a conexão Peça ao agente para executar um comando simples: ``` Liste minhas cobranças recentes ``` Se a conexão estiver funcionando, o agente vai usar a ferramenta `list_charges` e retornar os resultados. ## Transportes O MCP Server da Garu suporta dois modos de transporte: | Transporte | Descrição | Quando usar | | - | - | - | | **stdio** (padrão) | Comunicação via entrada/saída padrão | IDEs locais (Claude Code, Cursor, Codex, Windsurf) | | **HTTP** | Servidor HTTP com Server-Sent Events | Integrações remotas, servidores web | O modo **stdio** é o padrão e funciona automaticamente com os comandos de instalação acima. Para usar o modo HTTP: ```bash theme={null} npx --package=@garuhq/mcp@latest garu-mcp --transport http --port 3100 ``` ## Catálogo de Ferramentas O servidor expõe mais de 50 ferramentas. Estas são as principais que o agente pode usar: ### Produtos | Ferramenta | Descrição | | - | - | | `list_products` | Lista os produtos do vendedor autenticado com paginação e busca | | `get_product` | Consulta um produto pelo UUID | | `create_product` | Cria um produto. `value` em **Reais** (`29.90`), nunca em centavos | | `update_product` | Atualiza só os campos enviados de um produto | O `value` de um produto é `0` (produto **sem preço**, que não vende pelo link de pagamento) ou \*\*pelo menos R$ 5,00**. De R$ 0,01 a R\$ 4,99 a API recusa com `400`. No `update_product`, a regra só vale quando o agente envia `value`: sem ele, o preço atual fica como está. Para criar uma cobrança você precisa do **UUID do produto**. Use `list_products` para descobrir os UUIDs disponíveis antes de chamar as ferramentas de cobrança. ### Ofertas Uma oferta é um preço com nome em um produto, com link próprio (`/pay/{uuid}?offer={slug ou id}`). Ela muda só o preço; o resto continua o do produto. | Ferramenta | Descrição | | - | - | | `list_offers` | Lista as ofertas de um produto (só as ativas, por padrão) | | `get_offer` | Consulta uma oferta: preço atual e se ainda está ativa | | `create_offer` | Cria uma oferta. `value` em **Reais**, de **pelo menos R\$ 5,00** | | `update_offer` | Muda o preço, o nome, ou ativa/desativa a oferta | | `delete_offer` | Exclui a oferta, só enquanto ela nunca vendeu | Diferente do produto, uma oferta não pode ficar sem preço: o `value` precisa ser de pelo menos R\$ 5,00, e `0` é recusado. ### Cobranças | Ferramenta | Descrição | | - | - | | `create_pix_charge` | Cria uma cobrança PIX. Retorna um QR Code para o cliente pagar | | `create_boleto_charge` | Cria um boleto bancário. Retorna a linha digitável para pagamento | | `list_charges` | Lista cobranças do vendedor autenticado com paginação e filtros | | `get_charge` | Consulta detalhes de uma cobrança pelo ID numérico (inclui o `status`) | | `refund_charge` | Reembolsa uma cobrança total ou parcialmente (valor em BRL) | ### Clientes | Ferramenta | Descrição | | - | - | | `create_customer` | Cadastra um cliente e vincula ao vendedor atual | | `list_customers` | Lista clientes com paginação, busca e filtro `status: "overdue"` (clientes com cobrança em atraso) | | `get_customer` | Consulta detalhes de um cliente pelo ID numérico | | `update_customer` | Atualiza dados de um cliente do vendedor atual | | `set_customer_billing_email_override` | Define ou limpa o e-mail fixo de cobrança do cliente | | `delete_customer` | Remove o cliente do vendedor atual (não exclui globalmente) | ### Cobranças agendadas | Ferramenta | Descrição | | - | - | | `create_scheduled_charge` | Agenda uma cobrança PIX/Boleto para uma data futura. Valor em BRL decimal, mínimo R\$ 5,00 | | `list_scheduled_charges` | Lista com filtros de status (single ou array), cliente, intervalo de datas e busca | | `get_scheduled_charge` | Detalhes da cobrança + linha do tempo + transações geradas | | `postpone_scheduled_charge` | Adia para uma nova data; limpa lembretes pendentes | | `pause_scheduled_charge` | Pausa a cobrança; nenhum lembrete é enviado enquanto pausada | | `resume_scheduled_charge` | Retoma uma cobrança pausada | | `mark_paid_scheduled_charge` | Marca como paga (pagamento feito fora do Garu) | | `charge_now_scheduled_charge` | Dispara a cobrança e o e-mail na hora, sem esperar o vencimento; idempotente por ciclo | | `cancel_recurrence_scheduled_charge` | Encerra os próximos ciclos de uma série recorrente (recorrente-only) | | `set_cancel_at_period_end_scheduled_charge` | Cancelamento suave estilo Stripe; reversível (recorrente-only) | | `change_scheduled_charge_payment_method` | Troca o cartão salvo de uma série recorrente. O cartão precisa ser do mesmo cliente **e** já cobrar uma cobrança sua para esse cliente (assinatura, cobrança agendada ou tentativa anterior); senão, `404` | | `clear_scheduled_charge_payment_method` | Remove o cartão salvo; próximos ciclos caem para e-mail com link | | `list_scheduled_charge_attempts` | Log por tentativa de cobrança — auditoria de billing recorrente | `charge.amount` e `transactions[].value` estão ambos em **BRL decimal** (`297.50`) — nunca centavos. Nenhuma conversão é necessária entre os dois. ## Exemplos de Prompts Experimente pedir ao seu agente: ``` Liste meus produtos no Garu ``` ``` Crie uma cobrança PIX do produto "Curso Avançado" para joao@email.com ``` ``` Gere um boleto do produto para maria@email.com ``` ``` Liste todas as cobranças do último mês com status "paid" ``` ``` Qual o status da cobrança #12345? ``` ``` Reembolse a cobrança #67890 ``` ``` Cadastre um cliente: Maria Silva, maria@email.com, CPF 123.456.789-00 ``` ``` Atualize o telefone do cliente 42 para (11) 98765-4321 ``` ``` Agende uma cobrança PIX de R$ 297,50 para o cliente 42, vencimento 15/06 ``` ``` Quais cobranças estão em atraso? ``` ``` Adia a cobrança sch_abc123 para o dia 1 de julho — o cliente pediu mais prazo ``` ``` Marque a cobrança sch_abc123 como paga, pagamento via TED em 20/06, comprovante 4472881 ``` ``` Quanto faturei esse mês? Mostre o total das cobranças pagas. ``` ## Resolução de Problemas Erros comuns ao instalar e usar o MCP Server: O cliente MCP não está instalado ou não está no `PATH`. Reinstale o Claude Code/Cursor/Codex/Windsurf seguindo o guia oficial e reabra o terminal antes de tentar de novo. Confira se `GARU_API_KEY` está definida no ambiente em que o cliente MCP está rodando. No Claude Code, o cliente herda o shell — então `export GARU_API_KEY=...` no `~/.zshrc` (ou `~/.bashrc`) resolve. No Cursor e Windsurf, a chave fica no campo `env` do `mcp.json`. Confira também se a chave começa com `sk_test_` ou `sk_live_`. Chaves antigas ou rotacionadas no dashboard deixam de funcionar imediatamente. Provavelmente firewall ou proxy corporativo bloqueando `https://garu.com.br/api`. Adicione `garu.com.br` à lista de exceções da sua rede ou use uma conexão fora da rede corporativa para testar. O cliente MCP geralmente carrega o servidor só na primeira conversa. Reinicie completamente o IDE (não só recarregar a janela). No Claude Code, rode `claude mcp list` para confirmar que `garu` aparece na lista. Ambiente Node desatualizado ou sem permissão de escrita no cache do `npx`. Atualize o Node para 20.x ou superior e rode `npx --yes --package=@garuhq/mcp@latest garu-mcp --version` uma vez no terminal para popular o cache antes de o cliente MCP tentar. O MCP usa exatamente a chave que você passou — ele não detecta sozinho se é teste ou produção. Mantenha chaves de teste no ambiente de desenvolvimento e revogue qualquer chave `sk_live_` que tenha vazado para o ambiente local. Cobranças criadas em produção por engano podem ser canceladas pelo dashboard, ou reembolsadas com `refund_charge` se já tiverem sido pagas. ## Recursos Código-fonte e documentação técnica @garuhq/mcp no npm ## Próximos Passos Use a Garu direto no terminal Ensine boas práticas ao seu agente # Node.js SDK Source: https://docs.garu.com.br/guias/node-sdk Integre pagamentos Garu no seu projeto Node.js em minutos ## Visão Geral A SDK Node.js da Garu permite integrar pagamentos PIX, Cartão de Crédito e Boleto no seu projeto com poucas linhas de código. A SDK abstrai a API REST, com tipos prontos e status simplificados. **Os valores em dinheiro na SDK são em Reais** (BRL decimal): `49.90` é R$ 49,90. Isso vale para o `value` de produtos e ofertas, o `amount` de cobranças agendadas e de estornos. Nunca use centavos: `4990` seria R$ 4.990,00. **Pré-requisito:** Você precisa de uma chave de API. Veja como obter em [Autenticação](/api-reference/autenticacao). ## Instalação ```bash theme={null} npm install @garuhq/node ``` ## Inicialização ```javascript theme={null} import { Garu } from "@garuhq/node"; const garu = new Garu({ apiKey: process.env.GARU_API_KEY, }); ``` Nunca coloque sua chave de API diretamente no código. Use variáveis de ambiente. ## Produtos ### Criar um produto ```javascript theme={null} const product = await garu.products.create({ name: "Curso de Marketing Digital", image: "https://meusite.com/imagens/curso-marketing.jpg", value: 297.0, // R$ 297,00 em Reais, NÃO centavos pix: true, creditCard: true, }); console.log(`https://garu.com.br/pay/${product.uuid}`); ``` A API exige `name`, `image` e `value` na criação. O `value` segue esta regra: | `value` | Resultado | | - | - | | `0` | Aceito: produto **sem preço**, que não vende pelo link de pagamento | | De `0.01` a `4.99`, ou negativo | Recusado com `400` (`GaruValidationError`) | | `5.00` ou mais | Aceito | Um produto sem preço serve para quem cobra de outro jeito, como por [cobranças agendadas](#scheduledcharges). Em um produto de assinatura (`isSubscription: true`), envie `value: 0`: o preço fica nos preços de assinatura, que também têm mínimo de R\$ 5,00. ### Atualizar um produto ```javascript theme={null} await garu.products.update("a1b2c3d4-e5f6-7890-abcd-ef1234567890", { value: 347.0, // R$ 347,00 em Reais }); ``` A atualização é parcial. O mínimo de R$ 5,00 só vale quando você envia `value`: sem ele, o preço atual fica como está, mesmo que seja menor que R$ 5,00. ## Ofertas Uma oferta vende o mesmo produto por outro preço, com link próprio. Ela muda só o preço; o resto continua o do produto. ```javascript theme={null} const offer = await garu.offers.create("a1b2c3d4-e5f6-7890-abcd-ef1234567890", { name: "Black Friday", value: 97.0, // R$ 97,00 em Reais, NÃO centavos slug: "black-friday", }); // Link: https://garu.com.br/pay/a1b2c3d4-e5f6-7890-abcd-ef1234567890?offer=black-friday // Encerrar a promoção sem apagar a oferta await garu.offers.update(offer.id, { isActive: false }); ``` O `value` de uma oferta precisa ser de **pelo menos R\$ 5,00**. Diferente do produto, uma oferta não pode ficar sem preço: `0` é recusado. ## Cobranças A cobrança é sempre de um produto: o preço vem do produto (ou da oferta), nunca da requisição. ### Criar uma cobrança PIX ```javascript theme={null} const charge = await garu.charges.create({ productId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", paymentMethod: "pix", customer: { name: "João Silva", email: "joao@email.com", document: "12345678909", // CPF, só números phone: "11987654321", }, }); console.log(charge.uuid); // identificador da cobrança console.log(charge.pix?.code); // código PIX copia-e-cola console.log(charge.amount); // preço do produto, em Reais ``` ### Criar uma cobrança com cartão de crédito ```javascript theme={null} const charge = await garu.charges.create({ productId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", paymentMethod: "creditCard", card: { number: "4111111111111111", holderName: "MARIA SILVA", expirationDate: "2030-12", cvv: "123", installments: 3, }, customer: { name: "Maria Silva", email: "maria@email.com", document: "98765432100", phone: "11987654321", }, }); // chargedTotal inclui o acréscimo do parcelamento; use-o para conciliar console.log(charge.amount, charge.chargedTotal); ``` ### Listar cobranças ```javascript theme={null} const charges = await garu.charges.list({ status: "paid", limit: 10, }); for (const charge of charges.data) { console.log(`${charge.uuid}: R$ ${charge.chargedTotal} — ${charge.status}`); } ``` ### Consultar uma cobrança ```javascript theme={null} const charge = await garu.charges.retrieve("6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f"); console.log(charge.status); // "paid", "pending", "refunded", etc. ``` ### Reembolsar uma cobrança ```javascript theme={null} // Total await garu.charges.refund("6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f"); // Parcial: R$ 10,00, em Reais await garu.charges.refund("6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f", { amount: 10.0 }); ``` ### Cancelar uma cobrança não paga ```javascript theme={null} await garu.charges.cancel("6f1c9b2e-4a7d-4f0b-9a3e-1d2c3b4a5e6f"); ``` ## Clientes ### Criar um cliente ```javascript theme={null} const customer = await garu.customers.create({ name: "João Silva", email: "joao@email.com", document: "12345678900", phone: "11999999999", }); ``` ### Listar clientes ```javascript theme={null} const customers = await garu.customers.list({ limit: 20 }); ``` ### Consultar um cliente ```javascript theme={null} const customer = await garu.customers.get("cust_abc123"); ``` ### Atualizar um cliente ```javascript theme={null} const customer = await garu.customers.update("cust_abc123", { phone: "11988888888", }); ``` ### Remover um cliente ```javascript theme={null} await garu.customers.delete("cust_abc123"); ``` ### Filtrar clientes em atraso ```javascript theme={null} const overdue = await garu.customers.list({ status: "overdue" }); // retorna apenas clientes com pelo menos uma cobrança agendada em atraso ``` ## Cobranças agendadas PIX e Boleto agendados para uma data futura. A Garu envia o e-mail no vencimento e alerta o time se atrasar. Disponível a partir do `@garuhq/node` 0.5. ### Criar uma cobrança agendada ```javascript theme={null} const charge = await garu.scheduledCharges.create({ customerId: 42, amount: 297.5, // BRL decimal, NÃO centavos; mínimo R$ 5,00 type: "one_time", dueDate: "2026-06-15", // YYYY-MM-DD, fuso de São Paulo methods: ["pix", "boleto"], description: "Mensalidade Junho", idempotencyKey: "mensalidade-42-2026-06", }); console.log(charge.id); // sch_abc123 ``` Passe `idempotencyKey` (derivada de algo estável no seu sistema, como o id do pedido) para que um retry após falha de rede não crie uma segunda série. Sem ela, a SDK não envia chave nenhuma. O `amount` precisa ser de pelo menos **R\$ 5,00**, na cobrança avulsa e na recorrente. Abaixo disso a API responde `400` (`GaruValidationError`). As cobranças criadas antes dessa regra mantêm o valor e continuam sendo cobradas. ### Listar e filtrar ```javascript theme={null} // Múltiplos status como array const upcoming = await garu.scheduledCharges.list({ status: ["scheduled", "due_today"], limit: 50, }); // Em atraso de um cliente específico const overdueForCustomer = await garu.scheduledCharges.list({ status: "overdue", customerId: 42, }); // Janela de vencimento + busca pelo cliente const this_month = await garu.scheduledCharges.list({ dueFrom: "2026-06-01", dueTo: "2026-06-30", search: "maria", }); ``` ### Detalhes (cobrança + linha do tempo + transações) ```javascript theme={null} const { charge, events, transactions } = await garu.scheduledCharges.get("sch_abc123"); // charge.amount e transactions[].value são ambos BRL decimal — nunca centavos. ``` ### Ações no ciclo de vida ```javascript theme={null} // Adiar await garu.scheduledCharges.postpone("sch_abc123", { newDueDate: "2026-07-01", reason: "cliente pediu mais prazo", }); // Pausar (sem lembretes até retomar) await garu.scheduledCharges.pause("sch_abc123", { reason: "em negociação" }); // Retomar await garu.scheduledCharges.resume("sch_abc123"); // Marcar como paga (pagamento off-Garu) await garu.scheduledCharges.markPaid("sch_abc123", { paymentDate: "2026-06-20", externalReference: "TED 4472881", }); // Cobrar agora (dispara na hora, sem esperar o vencimento; idempotente por ciclo) const result = await garu.scheduledCharges.chargeNow("sch_abc123"); result.outcome; // 'dispatched' | 'already_sent' | 'not_sent' | 'failed' result.message; // texto pt-BR pronto para exibir // Trocar o cartão salvo de uma série recorrente await garu.scheduledCharges.changePaymentMethod("sch_abc123", { paymentMethodId: 42, }); ``` Em `changePaymentMethod`, o cartão precisa ser do mesmo cliente da série **e** já cobrar uma cobrança sua para esse cliente: uma assinatura, uma cobrança agendada ou uma tentativa anterior. Um cartão de outro cliente, ou que você nunca cobrou, responde `404` (`GaruNotFoundError`), como um cartão que não existe. Veja [Cobranças agendadas](/guias/cobrancas-agendadas) para o ciclo de vida completo e a tabela de status permitidos por ação. ## Chaves de Idempotência Use chaves de idempotência para garantir que uma operação não seja executada duas vezes, mesmo em caso de retry: ```javascript theme={null} const charge = await garu.charges.create({ productId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", paymentMethod: "pix", customer: { name: "João Silva", email: "joao@email.com", document: "12345678909", phone: "11987654321", }, idempotencyKey: "order_12345", }); ``` A SDK não inventa uma chave quando você não passa `idempotencyKey`: uma chave nova a cada chamada não protege nada, porque o retry levaria outra chave. Use o ID do pedido no seu sistema como chave de idempotência. Assim, mesmo que a requisição seja enviada duas vezes, apenas uma cobrança será criada. ## Tratamento de Erros ```javascript theme={null} import { Garu, GaruAPIError } from "@garuhq/node"; try { await garu.products.create({ name: "Adesivo", image: "https://meusite.com/imagens/adesivo.jpg", value: 4.99, // abaixo do mínimo de R$ 5,00 }); } catch (error) { if (error instanceof GaruAPIError) { console.error(`Erro ${error.status}: ${error.message}`); // Erro 400: O preço mínimo de um produto é R$ 5,00. Use R$ 0,00 para um produto sem preço. } else { throw error; } } ``` **Códigos de erro comuns:** Verifique os campos enviados. A resposta inclui detalhes sobre quais campos estão incorretos. Confira se a variável `GARU_API_KEY` está definida e se a chave é válida. O ID informado não corresponde a nenhum recurso existente. A chave de idempotência já foi usada com dados diferentes. Use uma chave única por operação. A requisição é válida mas viola uma regra de negócio (ex: status incompatível). Um preço abaixo do mínimo de R\$ 5,00 responde `400`, não `422`. Aguarde alguns segundos e tente novamente. Implemente backoff exponencial em automações. ## Recursos Código-fonte e documentação técnica @garuhq/node no npm ## Próximos Passos Conecte agentes de IA à API Receba notificações de pagamento # Pix Automático Source: https://docs.garu.com.br/guias/pix-automatico Cobrança recorrente no Pix: o cliente autoriza uma vez no app do banco e os próximos ciclos são debitados sozinhos, sem QR Code a cada mês. ## O que é O **Pix Automático** é o débito recorrente do Pix, regulamentado pelo Banco Central. A ideia é simples: o seu cliente autoriza a cobrança **uma única vez**, direto no app do banco dele, e a partir daí cada ciclo é debitado automaticamente — sem QR Code novo, sem boleto, sem cartão. É o melhor dos dois mundos: a conversão e a familiaridade do Pix, com a recorrência silenciosa de uma assinatura no cartão. O cliente aprova a recorrência no app do banco. Só uma vez. Os próximos ciclos caem automaticamente, no valor e na data combinados. Funciona com qualquer conta que tenha Pix. Não precisa de cartão de crédito. ## Quando usar Pix Automático, Pix tradicional e cartão recorrente resolvem coisas diferentes. Escolha pelo tipo de cobrança: **Use para cobranças que se repetem** e onde você quer evitar atrito a cada ciclo. * Mensalidades, assinaturas, clubes, planos de serviço. * O cliente não tem cartão ou prefere pagar pela conta. * Você quer recorrência sem depender da validade/limite de um cartão. O cliente autoriza uma vez e os ciclos seguintes são silenciosos. **Use para pagamentos únicos** ou quando o cliente decide pagar a cada vez. * Venda avulsa, um produto, uma fatura pontual. * Cobranças recorrentes em que você prefere mandar um link/QR a cada ciclo (veja [Cobranças agendadas](/guias/cobrancas-agendadas)). Cada cobrança gera um novo QR Code que o cliente precisa pagar ativamente. **Use quando o cliente quer pagar no cartão** e aproveitar parcelamento ou benefícios do cartão. * Público acostumado a assinar no cartão. * Você já tem o fluxo de [Assinaturas](/guias/assinaturas) com cartão rodando. Depende do cartão estar válido e com limite — Pix Automático não tem esse problema. Não é exclusivo. Você pode oferecer Pix Automático **e** cartão no mesmo produto e deixar o cliente escolher no checkout. ## Como o cliente autoriza A autorização é o único momento em que o cliente precisa agir. Depois disso, é tudo automático. No checkout, ao escolher **Pix Automático**, a Garu mostra um QR Code (ou código Pix copia-e-cola) de **autorização de recorrência** — não é um pagamento comum. O cliente abre o app do banco e vai até a seção **"Pix Automático"** ou **"Recorrência Pix"** (o nome varia por banco). Lá ele revisa quem está cobrando, o valor e a frequência, e confirma a autorização. O banco confirma a recorrência e devolve a confirmação para a Garu. A assinatura sai de `waitingPayment` e vira `active`. O primeiro ciclo é debitado conforme o combinado e os próximos seguem sozinhos. O cliente não precisa fazer mais nada. Enquanto o cliente não confirma no app, a assinatura fica em `waitingPayment`. Acompanhe esse estado pelo webhook ou pelo [endpoint de status](/guias/integrar-pix-automatico#opcao-b-checkout-publico) até virar `active`. ## O que acontece nos próximos ciclos Aqui está a graça do Pix Automático: **nos ciclos seguintes não acontece nada visível para o cliente**. * A Garu solicita o débito na data do ciclo. * O banco debita a conta do cliente automaticamente, dentro da autorização que ele já deu. * A Garu confirma o pagamento e dispara os webhooks de sempre (`transaction.payment.succeeded`, `subscription.renewed`). * O cliente não recebe QR Code, não precisa abrir o app, não precisa pagar nada manualmente. É silencioso por design — exatamente como uma assinatura no cartão, só que pela conta Pix. ## Como cancelar A recorrência pode ser encerrada pelos dois lados. Em qualquer caminho, a Garu reflete o cancelamento e dispara `subscription.cancelled`. Você cancela pela API, exatamente como faria com uma assinatura no cartão — **não há endpoint novo** para Pix Automático: ```bash theme={null} # Assinatura recorrente curl -X DELETE https://garu.com.br/api/subscriptions/27 \ -H "Authorization: Bearer sk_live_xxx" # Cobrança agendada recorrente curl -X POST https://garu.com.br/api/v1/scheduled-charges/sch_abc123/cancel-recurrence \ -H "Authorization: Bearer sk_live_xxx" ``` A Garu comunica o banco para encerrar a autorização e os próximos ciclos não são mais debitados. O cliente pode revogar a autorização **a qualquer momento, direto no app do banco** — na mesma seção "Pix Automático" / "Recorrência Pix" onde autorizou. Quando isso acontece, o banco avisa a Garu, que encerra a assinatura e dispara o webhook **`subscription.cancelled`** para você. Trate esse evento como o cancelamento definitivo: corte o acesso, atualize seu sistema, etc. Como o cliente pode cancelar pelo banco sem passar pelo seu sistema, **sempre confie no webhook `subscription.cancelled`** como fonte da verdade — não assuma que toda baixa veio da sua própria API. ## Modelo de falha Pix Automático falha diferente de cartão, e é importante entender a diferença. **Não existe retry de rede.** Quando o banco recusa um débito (saldo insuficiente, autorização revogada, etc.), a rede do Pix **não fica retentando** o débito sozinha como acontece com cartão. A Garu recebe a recusa e age na hora. O que a Garu faz: Você recebe `subscription.payment_failed` com o motivo da recusa. O status vira `past_due` (inadimplente). O cliente continua na base, mas o ciclo não foi pago. A mesma máquina de estado de inadimplência das assinaturas no cartão entra em ação — lembretes ao cliente, janela de recuperação, etc. Se o débito for recuperado dentro da janela, a assinatura volta para `active`. Se a janela se esgotar, a assinatura segue para `canceled`. A trajetória de inadimplência, então, é: **`active` → `past_due` → (`active` se recuperar | `canceled` se não)**. Como a régua de inadimplência é a mesma do cartão, o seu código de tratamento de falha **não muda**. Escute `subscription.payment_failed` e reaja ao status `past_due` do mesmo jeito — veja [Tratar falhas](/guias/integrar-pix-automatico#step-5-tratar-falhas). ## Estados da assinatura No checkout público e no polling de status, a assinatura de Pix Automático passa por estes estados: | Status | Significado | | - | - | | `waitingPayment` | Criada, aguardando o cliente autorizar a recorrência no app do banco | | `active` | Autorizada e cobrando normalmente | | `past_due` | Um ciclo foi recusado; em régua de recuperação | | `canceled` | Encerrada (pelo vendedor, pelo cliente no banco, ou por inadimplência) | | `inactive` | Inativa | ## Próximos passos Receita passo a passo: ativar no produto, cobrar e escutar webhooks. Agende ciclos recorrentes com `pix_automatic`. Recorrência via checkout público e portal do cliente. Eventos de assinatura e transação que você vai escutar. # Portal do cliente Source: https://docs.garu.com.br/guias/portal-do-cliente Em garu.com.br/minha-area seu cliente vê e paga todas as cobranças agendadas em aberto, sem login, com um link mágico de 24h. ## Visão Geral A partir da v0.6.0, todo cliente Garu tem um portal em **`garu.com.br/minha-area`**. Com um único link mágico enviado por e-mail, o cliente vê e paga todas as cobranças em aberto — em qualquer vendedor que use a Garu, sem cadastro nem senha. Para o vendedor, o portal é completamente automático: assim que o cliente recebe uma [cobrança agendada](/guias/cobrancas-agendadas), ele já pode usar o `/minha-area`. Nada para configurar. **Onde fica:** `https://garu.com.br/minha-area`. O fluxo é todo do lado do cliente — o vendedor só agenda a cobrança normalmente. ## Como o cliente usa O cliente abre `garu.com.br/minha-area` e digita o CPF ou CNPJ. A Garu envia um link mágico para o e-mail registrado. A validade é de 24 horas. Ao clicar no link, o cliente vê todas as cobranças agendadas em aberto, agrupadas por loja. Cada cobrança tem um botão **Pagar agora** que abre a página de pagamento (PIX ou Boleto). O link é reutilizável dentro das 24h — o cliente pode revisitar a página sem solicitar um novo e-mail. ## E-mail de cobrança por loja Dentro do portal, o cliente pode definir um **e-mail específico** para receber cobranças daquela loja. Por exemplo: cobranças da Loja Garu vão para `cobrancas@empresa.com.br`, mas as da Loja Beta continuam no e-mail pessoal. Esse e-mail é **fixo** — mesmo que o cliente troque o e-mail principal em uma cobrança futura, o desta loja não muda. Para voltar ao padrão, basta clicar em **Voltar ao padrão**. A regra de resolução do e-mail enviado pelo vendedor segue exatamente: ```text theme={null} billingEmailOverride (definido pelo cliente neste portal) ?? CustomerSellerProfile.lastUsedEmail (último visto neste vendedor) ?? Customer.email (e-mail principal global) ``` ## Segurança * O link mágico é um **JWT** com TTL de 24h, assinado com um segredo dedicado (`MAGIC_LINK_SECRET`) — separado do segredo do dashboard. Se um leakar, o outro permanece intacto. * A resposta do pedido de link **mascara o e-mail** (`u***@gmail.com`) e o **tempo de resposta é constante** com ou sem cliente correspondente. Não há como enumerar CPFs cadastrados na Garu via timing. * Throttle de **3 pedidos por hora por IP** no endpoint de pedido de link. * Ao editar o e-mail de cobrança, o cliente só consegue alterar para vendedores com os quais já tem um relacionamento (uma cobrança ou pagamento existente). Tentativas de tocar em outros vendedores retornam **403**. * O link é portátil: qualquer pessoa com o link consegue ver as cobranças do CPF associado. Oriente seus clientes a não compartilhar o link. ## Endpoints Três endpoints públicos novos sob `/api/public/minha-area/*`: * [POST /api/public/minha-area/request](/api-reference/portal-do-cliente/solicitar-link) — envia o link mágico. Sempre 200, sem enumeração. * [GET /api/public/minha-area/charges](/api-reference/portal-do-cliente/listar-cobrancas) — lista as cobranças do cliente em todos os vendedores. * [PATCH /api/public/minha-area/profile](/api-reference/portal-do-cliente/atualizar-email) — define ou limpa o e-mail de cobrança específico de um vendedor. Esses endpoints são **públicos** (sem `Authorization: Bearer sk_…`). A autenticação é via o JWT do link mágico, no header `Authorization: Bearer ` para os endpoints autenticados. ## Convivência com o portal de assinaturas A Garu tem dois portais: | Portal | Onde | Para quê | Auth | | - | - | - | - | | Cobranças agendadas | `/minha-area` | Cobranças PIX/Boleto avulsas | Link mágico CPF | | Assinaturas | `/portal?token=…` | Self-service de assinatura recorrente | Token gerado pelo vendedor | Eles coexistem hoje. Em uma versão futura, o `/minha-area` deve absorver também as assinaturas — em uma única página o cliente vê tudo. ## Próximos passos * Histórico de pagamentos do cliente dentro do portal. * Cancelamento de cobrança pelo próprio cliente, sob aprovação do vendedor. * Unificação com o portal de assinaturas em `/minha-area`. # Ir para Produção Source: https://docs.garu.com.br/guias/producao Checklist completo para lançar sua integração em produção ## Visão Geral Antes de processar pagamentos reais, siga este checklist para garantir que sua integração está pronta para produção. Pagamentos em produção são reais. Certifique-se de testar completamente antes de usar chaves `sk_live_`. ## Checklist de Produção ### 1. Autenticação Veja a [documentação de Autenticação](/api-reference/autenticacao) para entender o sistema de chaves de API. No [Dashboard](https://garu.com.br/inicio), vá em **Configurações** → **Desenvolvedores** e crie uma chave `sk_live_`. Armazene suas chaves de forma segura: ```bash theme={null} # .env.production GARU_API_KEY=sk_live_sua_chave_producao WEBHOOK_SECRET=seu_segredo_webhook ``` Verifique que nenhuma chave aparece em código JavaScript do cliente ou em repositórios públicos. ### 2. Produtos Veja a [API de Produtos](/api-reference/produtos/criar) para criar e gerenciar produtos. * [ ] Todos os produtos criados com chaves `sk_live_` * [ ] Preços corretos (conferir valores) * [ ] Descrições e nomes revisados * [ ] Imagens funcionando (URLs públicas) * [ ] URLs de retorno apontando para produção ### 3. Webhooks Consulte a [documentação de Webhooks](/api-reference/webhooks) para implementação completa. Use uma URL HTTPS do seu servidor de produção: ``` https://seusite.com/webhooks/garu ``` Implemente validação de assinatura para todos os webhooks: ```javascript theme={null} const crypto = require('crypto'); function validarAssinatura(payload, signature, secret) { const expected = crypto .createHmac('sha256', secret) .update(JSON.stringify(payload)) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } ``` Evite processar o mesmo webhook duas vezes: ```javascript theme={null} async function processarWebhook(data) { const jaProcessado = await verificarProcessado(data.id); if (jaProcessado) return; await processarPagamento(data); await marcarComoProcessado(data.id); } ``` Retorne 200 em menos de 5 segundos. Processe lógica pesada de forma assíncrona. ### 4. Tratamento de Erros Consulte a [Solução de Problemas](/api-reference/troubleshooting) para erros comuns e soluções. * [ ] Retry com backoff exponencial para erros 429 * [ ] Logs detalhados para debugging * [ ] Alertas para erros críticos * [ ] Fallback para falhas de webhook ```javascript theme={null} async function criarProdutoComRetry(dados, tentativas = 3) { for (let i = 0; i < tentativas; i++) { try { return await criarProduto(dados); } catch (error) { if (error.status === 429 && i < tentativas - 1) { await sleep(Math.pow(2, i) * 1000); continue; } throw error; } } } ``` ### 5. Segurança * [ ] HTTPS em todos os endpoints * [ ] Chaves de API em variáveis de ambiente * [ ] Validação de assinatura em webhooks * [ ] Logs não expõem dados sensíveis * [ ] Acesso ao Dashboard com 2FA habilitado ### 6. Monitoramento Configure logs para todas as requisições à API e webhooks recebidos. Configure alertas para erros de pagamento e falhas de webhook. Monitore transações e métricas no Dashboard da Garu. Monitore a disponibilidade do seu endpoint de webhook. ## Testes Finais Antes de ir ao ar, execute estes testes: ### Teste de Pagamento Completo Com chave `sk_live_`, crie um produto de R\$ 5,00 (valor mínimo). Use seu próprio cartão ou PIX para fazer um pagamento real. Confirme que seu servidor recebeu e processou o webhook corretamente. Confirme que o acesso foi liberado ou o produto foi entregue. Solicite estorno pelo Dashboard e verifique se seu sistema trata corretamente. ### Teste de Falhas * [ ] Testar comportamento quando webhook falha * [ ] Testar rate limiting (429) * [ ] Testar com chave inválida * [ ] Testar com dados inválidos ## Migração de Teste para Produção ```javascript theme={null} // config.js const config = { development: { apiKey: process.env.GARU_TEST_KEY, baseUrl: 'https://garu.com.br/api' }, production: { apiKey: process.env.GARU_LIVE_KEY, baseUrl: 'https://garu.com.br/api' } }; module.exports = config[process.env.NODE_ENV || 'development']; ``` A URL base é a mesma para teste e produção. A diferença está apenas na chave de API usada. ## Pós-Lançamento ### Primeiros Dias * Monitore de perto todas as transações * Verifique se webhooks estão sendo recebidos * Responda rapidamente a problemas reportados ### Manutenção Contínua * Rotacione chaves de API periodicamente * Mantenha logs por pelo menos 90 dias * Revise métricas de conversão * Atualize integrações conforme necessário ## Suporte Se encontrar problemas em produção: 1. Verifique os logs do seu servidor 2. Consulte a [Solução de Problemas](/api-reference/troubleshooting) 3. Acesse o [Dashboard](https://garu.com.br/inicio) para ver status das transações 4. Entre em contato: **[contato@garu.com.br](mailto:contato@garu.com.br)** Nossa equipe está disponível para ajudar com sua integração. # Reenviar webhook Source: https://docs.garu.com.br/guias/reenviar-webhook Listou os eventos que falharam, escolheu o que precisa e mandou de novo — clonando o original ou retentando in place. **Importante (v0.11.0): mudou o `Idempotency-Key` em reenvios manuais.** Toda entrega outbound da Garu manda um header `Idempotency-Key`. Em entregas normais e em auto-retries, o valor é o `payload.id` do evento (`evt_…`). Em **reenvios manuais** (dashboard "Reenviar" ou `POST /api/v1/webhook-events/:uuid/resend`), o valor passa a ser **`resend_`** — uma chave HTTP distinta da entrega original. Dois cenários para o seu handler: * **Você deduplica pelo valor bruto do header `Idempotency-Key`** (padrão recomendado): não precisa mudar código. `resend_…` é uma chave nova, passa naturalmente pelo seu cache de dedup como entrega fresca. **Mas atenção:** você vai começar a receber reenvios manuais que, pré-v0.11.0, eram silenciosamente descartados pelo seu próprio dedup. Sua idempotência **de negócio** (orderId, transactionId) precisa absorver isso. * **Você deduplica pelo `payload.id`** (ou normaliza/strippa o header antes de comparar): precisa atualizar. O `payload.id` é idêntico entre original e clone, então um reenvio manual seria descartado como duplicata. Veja o [código antes/depois](#mudanca-no-idempotency-key) abaixo. ## Visão Geral Acontece: o cliente jura que não recebeu o webhook do pagamento, ou seu endpoint ficou meia hora fora do ar e perdeu uma rajada de eventos. A Garu guarda todo evento de webhook emitido — entregue ou não — e você pode **listar, inspecionar e reenviar** qualquer um pela API, SDK, CLI ou direto pelo seu agente de IA. Hoje existem dois endpoints de reenvio. **Use `/resend` para tudo a partir de agora** — o `/retry` segue funcionando mas tem dois problemas que `/resend` resolve. | | `/resend` (v0.11.0+) | `/retry` (v0.10.3+) | | - | - | - | | O que faz | Clona o evento e entrega o clone | Reseta o original para `pending` e reentrega | | Evento original | Intacto, histórico preservado | **Sobrescrito** (status, tentativas, respostas) | | `Idempotency-Key` outbound | `resend_` — handler vê como entrega nova | `event.payload.id` — handler que deduplica por header **descarta como duplicata** | | Para qual situação | Tudo, daqui pra frente | Legado | **Por que dois endpoints?** O `/retry` veio antes (v0.10.3) e mutava o evento em lugar. Quando começamos a expor o reenvio para automação e agentes, virou claro que perder o histórico de tentativas do original é ruim para auditoria, e que o `Idempotency-Key` precisava distinguir reenvio manual de auto-retry. `/resend` (v0.11.0) resolve as duas coisas. Mantemos `/retry` ativo por compatibilidade — se você já automatizou em cima dele, não quebra. ## Endpoints * [`GET /api/v1/webhook-events`](/api-reference/webhooks/eventos/listar) — lista com filtros (status, tipo de evento, endpoint). * [`GET /api/v1/webhook-events/:uuid`](/api-reference/webhooks/eventos/detalhes) — detalhes de um evento (payload, tentativas, último HTTP, `manualResendOf`). * [`POST /api/v1/webhook-events/:uuid/resend`](/api-reference/webhooks/eventos/resend) — **clona** e entrega o clone. **Use este.** * [`POST /api/v1/webhook-events/:uuid/retry`](/api-reference/webhooks/eventos/reenviar) — reseta o original e reentrega. Legado. Os endpoints aceitam **chave de API do seller** (`sk_test_…` / `sk_live_…`) **ou** sessão JWT do dashboard. Use a chave de API para automação; o dashboard usa o JWT por baixo dos panos. ## Status do evento Toda entrega de webhook tem um destes três status: | Status | Significado | | - | - | | `pending` | Aguardando entrega ou retry agendado | | `success` | Seu endpoint respondeu 2xx | | `failed` | Esgotou as 6 tentativas (\~26h36min) sem 2xx | É `success`, não `delivered`. Se você consumia `delivered` em alguma versão preview, ajuste — a API e os tipos do SDK usam `success` em todos os lugares. ## Mudança no Idempotency-Key A Garu sempre mandou um header `Idempotency-Key` nos POSTs para o seu endpoint. O que mudou na v0.11.0: | Origem da entrega | Valor de `Idempotency-Key` | | - | - | | Primeira entrega de um evento | `evt_` (= `payload.id`) | | Auto-retry da retry policy | `evt_` (= `payload.id`, **mesmo da primeira tentativa**) | | Reenvio manual via `/resend` ou botão dashboard | **`resend_`** | | Reenvio manual via `/retry` (legado) | `evt_` (= `payload.id`, mesmo do original) | O ponto crítico: **`/resend` e o botão "Reenviar" agora mandam uma chave HTTP distinta da chave da entrega original.** Três caminhos no seu handler: * **Dedup pelo valor bruto do header**: já trata `resend_` como entrega nova naturalmente — `resend_X` nunca colide com `evt_Y`. Nenhuma mudança de código. Mas pré-v0.11.0 o resend chegava com a mesma chave do original e seu dedup descartava silenciosamente; agora ele passa, e sua idempotência de **negócio** precisa segurar. * **Dedup pelo `payload.id`**: descarta o resend, porque `payload.id` é idêntico entre original e clone. Atualize. * **Dedup com normalização do header** (strip de prefixo, regex, comparações truncadas): pode esconder a distinção entre `evt_…` e `resend_…`. Atualize. Os exemplos abaixo cobrem o caso `payload.id` — o mais comum entre handlers que efetivamente quebram com a mudança. ### Código do receiver — antes/depois ```javascript Antes (quebra: dedup por payload.id) theme={null} app.post('/webhooks/garu', async (req, res) => { const event = req.body; // Dedup pelo `payload.id`. Funciona perfeitamente para // auto-retries (mesma `evt_…` se repete entre tentativas). // Quebra para reenvios manuais: o clone carrega o MESMO // `payload.id` do original, então depois do original ter // sido processado, o resend é descartado aqui como duplicata. // É exatamente esse drop silencioso que o novo `Idempotency-Key` // (`resend_`) existe para evitar — mas só se o seu dedup // olhar para o header, não para o `payload.id`. if (await cache.has(event.id)) { return res.status(200).json({ deduped: true }); } await cache.set(event.id, true, { ttl: '7d' }); await processar(event); res.status(200).json({ received: true }); }); ``` ```javascript Depois (fix A: dedup pelo header bruto) theme={null} app.post('/webhooks/garu', async (req, res) => { // `evt_…` para entregas/auto-retries; `resend_` para reenvios // manuais. As duas chaves são distintas — o resend passa como // entrega nova naturalmente, sem caso especial. const key = req.headers['idempotency-key']; if (await cache.has(key)) { return res.status(200).json({ deduped: true }); } await cache.set(key, true, { ttl: '7d' }); await processar(req.body); res.status(200).json({ received: true }); }); ``` ```javascript Depois (fix B: payload.id + allow-list resend_) theme={null} app.post('/webhooks/garu', async (req, res) => { const event = req.body; const key = req.headers['idempotency-key']; const isManualResend = key?.startsWith('resend_'); // Reenvios manuais são deliberados — bypass do dedup de header. if (!isManualResend && (await cache.has(event.id))) { return res.status(200).json({ deduped: true }); } await cache.set(event.id, true, { ttl: '7d' }); await processar(event); res.status(200).json({ received: true }); }); ``` ```python Depois (Python/Flask — fix B) theme={null} @app.route('/webhooks/garu', methods=['POST']) def webhook(): event = request.json key = request.headers.get('Idempotency-Key', '') is_manual_resend = key.startswith('resend_') if not is_manual_resend and cache.has(event['id']): return jsonify({'deduped': True}), 200 cache.set(event['id'], True, ttl=7 * 24 * 3600) processar(event) return jsonify({'received': True}), 200 ``` **Idempotência de header ≠ idempotência de negócio.** O dedup de header (qualquer um dos fixes acima) protege contra retries de rede e auto-retries. **Não** protege contra dois operadores clicando "Reenviar" em sequência — cada clique gera um clone novo com `Idempotency-Key: resend_` distinto, então passam os dois. A regra de "não creditar o mesmo pedido duas vezes" precisa de uma chave do **seu** domínio (`orderId`, `transactionId`, etc.) num passo separado, depois do dedup de header. ## Fluxo: listar com falha → escolher → reenviar ```bash theme={null} # 1. Liste os últimos eventos que falharam curl "https://garu.com.br/api/v1/webhook-events?status=failed&limit=5" \ -H "Authorization: Bearer $GARU_API_KEY" # 2. (Opcional) inspecione um evento específico curl https://garu.com.br/api/v1/webhook-events/a1b2c3d4-e5f6-7890-abcd-ef1234567890 \ -H "Authorization: Bearer $GARU_API_KEY" # 3. Reenvie (clona — preferido a partir da v0.11.0) curl -X POST https://garu.com.br/api/v1/webhook-events/a1b2c3d4-e5f6-7890-abcd-ef1234567890/resend \ -H "Authorization: Bearer $GARU_API_KEY" # Resposta traz o clone: # { # "uuid": "f9e8d7c6-b5a4-3210-9876-543210fedcba", # "manualResendOf": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", # "status": "pending", # ... # } ``` 1. Entre em **Configurações → Webhooks → Eventos**. 2. Localize o evento na tabela. A partir da v0.11.0, o botão **Reenviar** aparece em **toda** linha — incluindo eventos `success` e `pending`. Em eventos `success`, abre uma confirmação ("este evento já foi entregue com sucesso — reenviar mesmo assim?"). 3. Clique **Reenviar**. Uma nova linha aparece no topo da tabela com o badge 🔁 e o status `pending`. Abrindo o drawer, a linha **"Reenviado a partir de #\"** mostra a origem. 4. O contador de tentativas mostra `X/6` (corrigido na v0.11.0 — antes mostrava `X/5` erroneamente). O botão do dashboard usa o mesmo `POST /resend` por baixo — toda a semântica de clone-on-resend e `Idempotency-Key: resend_…` se aplica. `/resend` é método/comando/tool dedicado em todos os surfaces oficiais desde `@garuhq/node` 0.12.0, `@garuhq/cli` 0.5.0 e `@garuhq/mcp` 0.12.0. Desde `@garuhq/node` **3.0.0**, `@garuhq/cli` **0.11.0** e `@garuhq/mcp` **0.21.0**, todos os três migraram para `/api/v1/webhook-events` e passaram a identificar eventos por `uuid` em vez de `id` numérico. ```javascript SDK (@garuhq/node ≥ 3.0.0) theme={null} import { Garu } from '@garuhq/node'; const garu = new Garu({ apiKey: process.env.GARU_API_KEY }); // 1. Lista eventos que falharam const { data: events } = await garu.webhookEvents.list({ status: 'failed', limit: 5 }); // 2. Reenvia (clona — preferido a partir da v0.11.0) const clone = await garu.webhookEvents.resend(events[0].uuid); clone.uuid; // novo uuid, distinto do original clone.manualResendOf; // === events[0].uuid — aponta de volta clone.status; // 'pending' (ou 'success' se a entrega já fechou) ``` ```bash CLI (@garuhq/cli ≥ 0.11.0) theme={null} # Lista os últimos eventos que falharam garu webhooks events list --status failed --limit 5 # Reenvia o evento (clona — preferido a partir da v0.11.0) garu webhooks events resend a1b2c3d4-e5f6-7890-abcd-ef1234567890 # Output: # uuid: f9e8d7c6-b5a4-3210-9876-543210fedcba # manualResendOf: a1b2c3d4-e5f6-7890-abcd-ef1234567890 # status: pending # ... ``` ```text MCP (@garuhq/mcp ≥ 0.21.0) theme={null} # Em uma conversa com seu agente Claude / Cursor / similar: "Lista meus últimos 5 webhooks com falha e reenvia o mais recente." # O agente encadeia duas tool calls: # list_webhook_events({ status: "failed", limit: 5 }) # resend_webhook_event({ uuid: }) # # A resposta traz o clone com novo uuid e manualResendOf apontando ao original. ``` Os métodos legados — `webhookEvents.retry()` no SDK, `garu webhooks events retry` na CLI, `retry_webhook_event` no MCP — continuam funcionando, mas chamam `/retry` (in-place) por baixo. Eles **não** passam pelo novo `Idempotency-Key: resend_`. Para o contrato novo (clone + chave distinta), use a variante `resend` em todos os três surfaces. ## Limite de chamadas `/resend` e `/retry` têm **rate limit de 20 requisições por minuto por IP** cada. É o suficiente para reprocessar um lote considerável sem pesar nos seus endpoints — e impede que um script em loop infinito martele um receiver que está fora do ar. Listou 200 eventos para reprocessar? Coloque um `setTimeout`/`sleep` curto entre as chamadas (≥ 3s entre cada uma mantém você bem abaixo do limite) ou processe em lotes de 20 por minuto. ## O que `/resend` faz por dentro 1. **Cria uma linha nova** na tabela de eventos, com `manualResendOf` apontando para o original. 2. **Copia o `payload`** exatamente como foi gerado — `payload.id` (`evt_…`) é preservado. 3. **Calcula o `Idempotency-Key` outbound** como `resend_` antes do POST para seu endpoint. 4. **Dispara a entrega** imediatamente, em background. Se falhar, aplica a retry policy padrão (1min, 5min, 30min, 2h, 8h, 16h até `failed`). 5. **Não toca no original.** Ele fica para sempre no status terminal (`success` / `failed`), com todas as tentativas e respostas históricas preservadas. É o que você quer para auditoria. ## Quando reenviar (e quando não) Janelas de indisponibilidade curtas (deploy, restart, k8s drain) frequentemente passam dos 26h da retry policy se forem aninhadas a outros incidentes. Reenviar é a forma de recuperar a fila sem precisar de plumbing manual. Suporte clássico. Localiza o evento via filtro `eventType` + janela de tempo, confirma que o status está `failed` (ou `success` mas o cliente não processou), reenvia com `/resend`. `/resend` funciona em eventos `success` também. Dispara um clone para o mesmo endpoint, útil para validar que mudanças no handler ainda processam o payload corretamente. Reenviar duplica a notificação. Sua idempotência de negócio (chave do seu domínio, não do header) deve absorver, mas é trabalho à toa. Se seu endpoint devolve 4xx por bug ou validação, o reenvio vai falhar de novo. Corrija o handler primeiro e depois reenvie. ## Permissões Por papel padrão da Garu: * **Owner / Administrator / Developer**: listar, ver e reenviar. * **Support**: listar e ver; reenvio depende de permissão `webhook:edit`. * **View Only**: somente listar e ver. Chaves de API herdam as permissões do seller que as criou. ## Próximos passos Referência completa do endpoint de reenvio por clone Endpoint legado que muta o original in place Inspecione `manualResendOf`, tentativas e payload Roteie falhas de pagamento pelo enum normalizado # Skills para Agentes Source: https://docs.garu.com.br/guias/skills Ensine boas práticas de pagamento ao seu agente de IA com as skills da Garu ## O que são Skills? Skills são pacotes de conhecimento que ensinam agentes de IA a escrever código de pagamento seguindo boas práticas. Com as skills da Garu, seu agente sabe como: * Usar a SDK corretamente * Seguir regras de compliance (LGPD, PCI-DSS, BACEN) * Tratar erros de pagamento * Validar webhooks com segurança **Pré-requisito:** Você precisa de uma chave de API. Veja como obter em [Autenticação](/api-reference/autenticacao). Skills complementam o [MCP Server](/guias/mcp-server). O MCP dá ao agente **ferramentas** para agir; as skills dão **conhecimento** para agir bem. ## Instalação ```bash theme={null} npx skills add Garu-Pagamentos/garu-skills ``` ## Skills Disponíveis ### `garu-sdk` Skill principal que funciona como roteador para as sub-skills. Ensina o agente a: * Inicializar o cliente da SDK * Criar cobranças com os parâmetros corretos * Gerenciar clientes (CRUD) * Usar chaves de idempotência * Tratar erros da API ### `payment-best-practices` Boas práticas e compliance para pagamentos no Brasil: | Tópico | O que cobre | | - | - | | **LGPD** | Tratamento de dados pessoais, consentimento, direitos do titular | | **PCI-DSS** | Nunca armazenar dados de cartão, tokenização, HTTPS obrigatório | | **BACEN** | Regras do Banco Central para PIX, limites, horários | | **Webhooks** | Validação de assinatura, idempotência, retry handling | ## Configuração por Ferramenta As skills são instaladas automaticamente em `.claude/skills/` no seu projeto: ``` .claude/ └── skills/ ├── garu-sdk.md └── payment-best-practices.md ``` O Claude Code carrega as skills automaticamente ao trabalhar no projeto. As skills são instaladas em `.cursor/` no seu projeto: ``` .cursor/ └── rules/ ├── garu-sdk.mdc └── payment-best-practices.mdc ``` O Cursor carrega as rules automaticamente. As skills são adicionadas ao arquivo `AGENTS.md` na raiz do projeto. O Codex lê esse arquivo automaticamente. ## Exemplo na Prática Sem skills, um agente pode gerar código assim: ```javascript theme={null} // ❌ Sem skills — código inseguro const response = await fetch("https://garu.com.br/api/charges", { method: "POST", headers: { Authorization: "Bearer sk_live_abc123" }, // chave hardcoded! body: JSON.stringify({ amount: 49.9 }), // sem idempotência }); ``` Com skills, o agente gera código correto: ```javascript theme={null} // ✅ Com skills — boas práticas aplicadas import { Garu } from "@garuhq/node"; const garu = new Garu({ apiKey: process.env.GARU_API_KEY }); const charge = await garu.charges.create({ productId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890", // o preço vem do produto paymentMethod: "pix", customer: { name: "João Silva", email: "joao@email.com", document: "12345678909", phone: "11987654321", }, idempotencyKey: "order_12345", }); ``` ## Recursos Código-fonte e skills disponíveis Conecte ferramentas de pagamento ao agente ## Próximos Passos Referência completa da SDK Configure sua ferramenta favorita # Bem-vindo à Garu Source: https://docs.garu.com.br/index Gateway de pagamentos que simplifica a gestão e apoia empreendedores no seu crescimento ## Comece Agora Integre pagamentos via PIX, Cartão de Crédito e Boleto em minutos. Crie seu primeiro produto e comece a receber pagamentos em 5 minutos. ## Por que escolher a Garu? Receba pagamentos via PIX com confirmação em segundos. Ofereça até 12x no cartão de crédito para seus clientes. Integre em minutos com nossa API RESTful bem documentada. ## Escolha seu Caminho Aprenda a criar produtos para vendas avulsas e gerar links de pagamento. Documentação técnica completa de todos os endpoints. Receba notificações em tempo real sobre pagamentos. Implementações prontas em JavaScript, Python e PHP. ## Métodos de Pagamento Aceite os principais métodos de pagamento do Brasil: | Método | Confirmação | Recorrência | | - | - | - | | PIX | Instantânea | Pagamento a cada ciclo | | Cartão de Crédito | Instantânea | Automática (até 12x parcelado) | | Boleto | 1-3 dias úteis | Pagamento a cada ciclo | | [Pix Automático](/guias/pix-automatico) | Instantânea | Automática (autoriza uma vez no banco) | ## Precisa de Ajuda? Entre em contato com nossa equipe de suporte. Acesse seu painel para gerenciar produtos e transações. # Início Rápido Source: https://docs.garu.com.br/quickstart Crie seu primeiro produto e comece a receber pagamentos em 5 minutos ## Visão Geral Este guia vai te ajudar a: 1. Obter sua chave de API 2. Criar seu primeiro produto 3. Gerar o link de pagamento 4. Receber seu primeiro pagamento Você pode completar todo este guia usando chaves de teste (`sk_test_`). Nenhuma cobrança real será processada. ## Passo 1: Obtenha sua Chave de API Faça login no [Dashboard da Garu](https://garu.com.br/inicio) Vá para **Configurações** → **Desenvolvedores** Clique em **Criar chave de API** e selecione o ambiente **Teste** (`sk_test_`) Copie e salve a chave em um local seguro. Ela só será exibida uma vez! ## Passo 2: Crie seu Primeiro Produto Use o comando abaixo para criar um produto. Substitua `sk_test_sua_chave` pela sua chave de API. Veja todos os campos disponíveis na [API de Criação de Produtos](/api-reference/produtos/criar). ```bash cURL theme={null} curl -X POST https://garu.com.br/api/v1/products \ -H "Authorization: Bearer sk_test_sua_chave" \ -H "Content-Type: application/json" \ -d '{ "name": "Meu Primeiro Produto", "description": "Produto de teste criado via API", "image": "https://meusite.com/imagens/produto.jpg", "value": 49.90, "pix": true, "creditCard": true, "boleto": true, "installments": 6 }' ``` ```javascript JavaScript theme={null} const response = await fetch('https://garu.com.br/api/v1/products', { method: 'POST', headers: { 'Authorization': 'Bearer sk_test_sua_chave', 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Meu Primeiro Produto', description: 'Produto de teste criado via API', image: 'https://meusite.com/imagens/produto.jpg', value: 49.90, pix: true, creditCard: true, boleto: true, installments: 6 }) }); const produto = await response.json(); console.log(produto); ``` ```python Python theme={null} import requests response = requests.post( 'https://garu.com.br/api/v1/products', headers={ 'Authorization': 'Bearer sk_test_sua_chave', 'Content-Type': 'application/json' }, json={ 'name': 'Meu Primeiro Produto', 'description': 'Produto de teste criado via API', 'image': 'https://meusite.com/imagens/produto.jpg', 'value': 49.90, 'pix': True, 'creditCard': True, 'boleto': True, 'installments': 6 } ) produto = response.json() print(produto) ``` ## Passo 3: Obtenha o Link de Pagamento A resposta da API incluirá um campo `uuid`. Use-o para construir o link de pagamento. Veja mais detalhes em [Link de Pagamento](/api-reference/produtos/link-pagamento). ```json theme={null} { "name": "Meu Primeiro Produto", "value": 49.90, "uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", ... } ``` **Seu link de pagamento:** ``` https://garu.com.br/pay/a1b2c3d4-e5f6-7890-abcd-ef1234567890 ``` Pronto! Compartilhe este link com seus clientes para receber pagamentos. ## Passo 4: Teste o Pagamento 1. Abra o link de pagamento no navegador 2. Preencha os dados de teste 3. Escolha um método de pagamento (PIX é o mais rápido para testar) 4. Complete o pagamento de teste Em modo de teste, os pagamentos não são reais. Use para validar sua integração antes de ir para produção. ## Passo 5: Configure Webhooks (Opcional) Para receber notificações quando um pagamento for confirmado. Consulte a [documentação de Webhooks](/api-reference/webhooks) para todos os eventos disponíveis. 1. Acesse **Configurações** → **Webhooks** no Dashboard 2. Adicione a URL do seu endpoint (ex: `https://seusite.com/webhooks/garu`) 3. Selecione o evento `transaction.paid` 4. Salve o segredo do webhook para validar as requisições ```javascript theme={null} // Exemplo de handler de webhook app.post('/webhooks/garu', (req, res) => { const { event, data } = req.body; if (event === 'transaction.paid') { console.log(`Pagamento confirmado: ${data.id}`); console.log(`Cliente: ${data.customer.email}`); // Liberar acesso ao produto, enviar email, etc. } res.status(200).json({ received: true }); }); ``` ## E se eu quiser o pagamento na minha própria tela? Os passos acima levam o cliente para uma página hospedada pela Garu — é o caminho mais rápido e cobre a maioria dos casos. Se você precisa que o pagamento aconteça **dentro da sua interface**, com a sua marca do começo ao fim, troque o Passo 3 por uma chamada de cobrança: ```bash theme={null} curl -X POST https://garu.com.br/api/v1/charges \ -H "Authorization: Bearer sk_live_sua_chave_api" \ -H "Content-Type: application/json" \ -d '{ "productId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "paymentMethod": "pix", "customer": { "name": "Maria Silva", "email": "maria@exemplo.com.br", "document": "12345678909", "phone": "11987654321" } }' ``` A resposta traz o **código PIX copia-e-cola** em `pix.code`, que você renderiza como QR Code no seu front. Nada de redirecionamento. O guia completo: PIX, boleto com PDF servido pela Garu, cartão e as implicações de PCI. ## Próximos Passos Aprenda todas as opções disponíveis para criar produtos. Entenda como funciona a autenticação da API. Configure notificações em tempo real. Checklist para lançar em produção. Receba PIX, boleto e cartão dentro da sua própria interface. Venda um produto em até 12 boletos mensais. Leia antes de ativar: o risco de inadimplência é seu, não da Garu.