# 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.