> ## Documentation Index
> Fetch the complete documentation index at: https://docs.appreval.com/llms.txt
> Use this file to discover all available pages before exploring further.

# API da Reval: consulte suas assinaturas a partir das suas próprias ferramentas

> API GraphQL sobre assinaturas, pedidos, clientes, planos e caixas: consultas e ações (pausar, cancelar, pular, trocar produtos, endereço, link do portal, links de checkout). API keys por loja, idempotência, paginação, filtros, limites e exemplos.

A API da Reval expõe os dados de assinaturas da sua loja para você levá-los ao seu BI, ao seu data warehouse ou às suas automações, e permite **operá-las**: pausar, cancelar, pular, trocar produtos e endereço. É **GraphQL**, e cada chave enxerga e opera apenas a loja que a gerou.

> [!NOTE]
>
> As consultas estão nas seções a seguir; as ações, em [Ações](#ações). A lista completa de tipos e campos está na [referência do esquema](https://docs.appreval.com/pt/desenvolvedores/api-referencia.md), e há [receitas](https://docs.appreval.com/pt/desenvolvedores/receitas.md) para os casos mais comuns. Para saber o que acontece sem consultar, veja os [webhooks](https://docs.appreval.com/pt/desenvolvedores/webhooks.md).

***

## Autenticação

As chaves são geradas em **Reval → Integrações** no seu admin da Shopify. Dê a cada chave o nome da ferramenta que vai usá-la («Triple Whale», «Looker») e guarde a chave na hora: **ela aparece completa uma única vez**. Depois, a lista mostra só o prefixo.

Envie a chave em cada request como Bearer token:

```http theme={null}
POST https://api.appreval.com/api/graphql
Authorization: Bearer reval_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Content-Type: application/json
```

Se o seu cliente não permite usar `Authorization`, o header `X-Reval-Api-Key` é equivalente.

* Uma chave revogada para de funcionar na hora. As consultas recebem `401` com o código `UNAUTHENTICATED`.
* A Reval guarda apenas um hash da chave. Se você a perder, revogue e gere outra.
* Você pode ter até **25 chaves ativas** por loja, cada uma com um nome de até 60 caracteres. A lista mostra o último uso de cada uma, assim você sabe quais revogar.
* Ao gerar a chave você escolhe **leitura e escrita** ou **somente leitura**. Uma chave somente leitura consulta tudo, mas não executa ações (veja [Ações](#ações)). O tipo não muda depois: revogue e gere outra.

> [!WARNING]
>
> As respostas incluem dados pessoais dos seus clientes (email, endereço, telefone, documento fiscal). Trate as chaves como uma senha: não as coloque em código publicado nem as compartilhe por chat.

***

## Primeira consulta

```bash theme={null}
curl https://api.appreval.com/api/graphql \
  -H "Authorization: Bearer reval_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "Content-Type: application/json" \
  -d '{"query":"{ shop { myshopifyDomain } subscriptions(first: 5, filter: { status: [ACTIVE] }) { totalCount nodes { id customerEmail amount currency nextPaymentDate } } }"}'
```

```json theme={null}
{
  "data": {
    "shop": { "myshopifyDomain": "tu-tienda.myshopify.com" },
    "subscriptions": {
      "totalCount": 312,
      "nodes": [
        { "id": "cmgabc123…", "customerEmail": "ana@example.com", "amount": "185.00", "currency": "BRL", "nextPaymentDate": "2026-11-02T13:00:00.000Z" }
      ]
    }
  }
}
```

O endpoint aceita **introspecção**, então qualquer cliente GraphQL (GraphiQL, Insomnia, Postman, Altair) mostra o esquema completo com a descrição de cada campo. Esta página é o resumo; a [referência do esquema](https://docs.appreval.com/pt/desenvolvedores/api-referencia.md) tem todos os tipos e campos.

* Só se aceita `POST` com um body JSON `{ "query", "variables", "operationName" }`: uma operação por request, sem batching nem `GET`.
* O endpoint responde a CORS, então uma ferramenta no navegador pode chamá-lo. Não faça isso a partir do front da sua loja: a chave ficaria exposta a qualquer um que abrir o código-fonte.

***

## O que você pode consultar

| Consulta | Retorna |
| - | - |
| `shop` | O domínio myshopify e o idioma da loja |
| `subscription(id)` / `subscriptions` | Assinaturas com produtos, plano, frequência, status, próxima cobrança, preço, frete, impostos, cartão mascarado e histórico de status e preços |
| `order(id)` / `orders` | Pedidos gerados pelas assinaturas: um por cobrança, com número do ciclo, valor e o ID do pedido na Shopify |
| `customer(email)` / `customers` | Clientes (por email) com suas assinaturas e pedidos |
| `planGroup(id)` / `planGroups` | Os planos como você os vê em **Planos**: cadência, tipo de preço, descontos, ciclos mínimos e máximos |
| `plan(id)` / `plans` | Cada produto (variante) vinculado a um plano |
| `box(id)` / `boxes` | As caixas com seus planos, tamanhos, produtos elegíveis, add-ons e faixas |

Os objetos que existem na Shopify trazem o seu **GID da Shopify** para cruzar com a Admin API: `shopifyOrderId`, `shopifyCustomerId`, `shopifyVariantId`, `shopifyProductId`. Os identificadores do Mercado Pago (`mercadoPagoPreapprovalId`, `mercadoPagoPaymentId`) servem para conciliar com a sua conta MP; as credenciais nunca são expostas.

### Convenções

* **Valores** (`Money`) são strings com duas casas decimais: `"185.00"`. A moeda está no campo `currency` do mesmo objeto.
* **Datas** (`DateTime`) são ISO-8601 em UTC: `"2026-11-02T13:00:00.000Z"`.
* **Enums** vêm em maiúsculas: `ACTIVE`, `PAUSED`, `CANCELLED`, `MONTHS`, `PERCENT`, `DYNAMIC_BOX`.
* Os IDs da Reval são opacos; use sempre o que a API devolveu.

***

## Paginação

Todas as listagens são *connections* no estilo Shopify: você pede `first` itens e continua com `after` usando o `endCursor` da página anterior.

```graphql theme={null}
query ($after: String) {
  orders(first: 100, after: $after, filter: { createdAfter: "2026-10-01T00:00:00Z" }) {
    edges {
      cursor
      node { id shopifyOrderId shopifyOrderName type cycleNumber amount currency createdAt subscription { id customerEmail } }
    }
    pageInfo { hasNextPage endCursor }
    totalCount
  }
}
```

* `nodes` é o atalho quando você não precisa do cursor de cada linha.
* `totalCount` conta todas as linhas que atendem ao filtro, não só a página.
* `reverse: true` inverte a ordem (do mais antigo ao mais novo).
* As listagens aninhadas (`subscription.orders`, `customer.subscriptions`) paginam do mesmo jeito.

***

## Filtros

Cada listagem aceita um `filter` com os campos mais usados:

| Listagem | Filtros |
| - | - |
| `subscriptions` | `status`, `customerEmail`, `shopifyCustomerId`, `shopifyVariantId`, `planGroupId`, `boxId`, `createdAfter`, `createdBefore`, `updatedAfter`, `nextPaymentAfter`, `nextPaymentBefore`; ordenação por `CREATED_AT`, `UPDATED_AT` ou `NEXT_PAYMENT_DATE` |
| `orders` | `subscriptionId`, `customerEmail`, `type` (`FIRST` / `RENEWAL`), `shopifyOrderId`, `createdAfter`, `createdBefore` |
| `customers` | `emailContains`, `shopifyCustomerId` |
| `planGroups` | `status`, `type` |
| `plans` | `status`, `shopifyVariantId`, `planGroupId` |
| `boxes` | `status`, `type` |

> [!TIP]
>
> Para sincronizar um warehouse de forma incremental, peça `subscriptions(filter: { updatedAfter: "<última execução>" }, sortKey: UPDATED_AT, reverse: true)` e `orders(filter: { createdAfter: "<última execução>" })`.

***

## Limites

Os limites existem para que um integrador que consulta muito nunca afete o checkout nem as cobranças. Quando uma consulta os ultrapassa, a resposta diz **qual**:

| Limite | Valor | Código de erro |
| - | - | - |
| Requests por minuto, por chave | 2000 | `RATE_LIMITED` (HTTP 429, com `Retry-After`) |
| Profundidade de objetos aninhados por consulta | 4 | `QUERY_TOO_DEEP` |
| Nós por listagem (`first`) | 100 | `PAGE_TOO_LARGE` |
| Nós estimados por request (listagens aninhadas multiplicam) | 1000 | `QUERY_TOO_EXPENSIVE` |

A profundidade conta objetos do modelo: `subscriptions › items › plan › group` são quatro níveis. `edges`, `node`, `nodes` e `pageInfo` não contam.

Toda resposta traz os headers `X-RateLimit-Limit`, `X-RateLimit-Remaining` e `X-RateLimit-Reset` (epoch em segundos) para o seu cliente se regular sozinho.

***

## Erros

Os erros seguem o formato GraphQL, com um `extensions.code` para o seu código decidir:

```json theme={null}
{ "errors": [ { "message": "Page size limit exceeded: \"subscriptions\" asks for first: 250, the maximum is 100 nodes per connection.", "extensions": { "code": "PAGE_TOO_LARGE" } } ] }
```

| HTTP | Código | Quando |
| - | - | - |
| 401 | `UNAUTHENTICATED` | Chave ausente, inválida ou revogada |
| 429 | `RATE_LIMITED` | Mais de 2000 requests no último minuto |
| 400 | `GRAPHQL_PARSE_FAILED`, `GRAPHQL_VALIDATION_FAILED` | A consulta não é GraphQL válido ou pede campos que não existem |
| 400 | `QUERY_TOO_DEEP`, `PAGE_TOO_LARGE`, `QUERY_TOO_EXPENSIVE` | Ver limites |
| 200 | `BAD_USER_INPUT`, `NOT_FOUND` | Um campo falhou (cursor inválido, data mal formada); o resto de `data` chega mesmo assim |
| 403 | `FORBIDDEN_READ_ONLY` | Uma mutation com uma chave somente leitura |
| 400 | `IDEMPOTENCY_KEY_INVALID` | O header `Idempotency-Key` está vazio, passa de 255 caracteres ou tem espaços |
| 422 | `IDEMPOTENCY_KEY_REUSED` | O mesmo `Idempotency-Key` já foi usado com outro body |
| 400 | `BAD_REQUEST` | O body não é JSON no formato `{ "query": "…", "variables": { } }` |
| 500 | `INTERNAL_ERROR` | Falha inesperada do lado da Reval; tente de novo e, se persistir, nos avise com a hora e a consulta |

As regras de negócio das ações (estado inválido, compromisso mínimo, plano arquivado, recusa do Mercado Pago) **não** são erros: voltam em `userErrors` com `200`. Veja [Ações](#ações).

***

## Ações

Além de consultar, a API permite **operar** as assinaturas da sua loja com as mesmas ações que o lojista tem no admin: pausar, retomar, cancelar, pular a próxima entrega, trocar produtos e quantidades, atualizar o endereço e gerar um link para o portal. Também permite **montar links de checkout** com vários produtos e os dados do cliente já preenchidos, para que um bot feche a venda com um link pronto. São **mutations** GraphQL no mesmo endpoint.

Cada ação tem exatamente os mesmos efeitos que no admin: o Mercado Pago é atualizado primeiro (se recusar, nada muda na Reval), preço e frete são recalculados quando mudam, os mesmos eventos disparam para Klaviyo, Flow e seus webhooks, e o histórico da assinatura mostra **«API (nome da chave)»** como autor.

### Chaves somente leitura

Ao gerar uma chave em **Reval → Integrações** você escolhe **leitura e escrita** ou **somente leitura**. Uma chave somente leitura executa qualquer consulta, mas toda mutation recebe `403` com o código `FORBIDDEN_READ_ONLY`. Use-a para dashboards e relatórios; reserve as de escrita para as automações que realmente agem.

### Idempotência

As ações movem cobranças e envios reais, então uma nova tentativa (timeout, queda do seu worker) não pode repeti-las. Envie um header `Idempotency-Key` com um identificador único por operação (um UUID). Se a mesma chave chegar de novo com o **mesmo** body em até 24 horas, a API devolve a resposta original sem executar nada e marca o header `Idempotency-Replayed: true`. Se chegar com **outro** body, responde `422` com `IDEMPOTENCY_KEY_REUSED`: é um bug do seu lado, não uma segunda operação.

```http theme={null}
POST https://api.appreval.com/api/graphql
Authorization: Bearer reval_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Idempotency-Key: 6f1c2a8e-3b7d-4e0f-9a21-5c8d7e6f4a3b
Content-Type: application/json
```

O header é opcional, mas sem ele uma nova tentativa pode pular dois ciclos ou cancelar duas vezes.

### Erros de negócio: `userErrors`

Uma mutation nunca falha com um erro GraphQL por uma regra de negócio. Ela responde `200` com um payload que traz o objeto **ou** uma lista `userErrors` com um `code` para o seu código decidir, e a assinatura fica como estava:

```json theme={null}
{ "data": { "subscriptionPause": { "subscription": null, "userErrors": [ { "code": "MIN_CYCLES", "message": "The plan has a minimum commitment of 3 cycles and 1 has been charged. Pass overrideMinCycles: true to act as the merchant.", "field": null } ] } } }
```

| Código | Quando |
| - | - |
| `NOT_FOUND` | Não há assinatura com esse id na sua loja (ids de outras lojas também respondem isto) |
| `INVALID_STATE` | O status não permite a ação: pausar uma pausada, retomar uma ativa, editar uma cancelada ou uma que chegou ao último ciclo |
| `MIN_CYCLES` | O plano tem um compromisso mínimo de entregas ainda não cumprido. É a mesma regra do portal; `overrideMinCycles: true` age como o lojista e a ignora |
| `SKIP_NOT_ALLOWED` | A loja desativou os pulos, já há um pulo pendente ou não há data de próxima cobrança |
| `PLAN_UNAVAILABLE` | Um `planId` não existe na sua loja ou está arquivado (planos que a assinatura já tem podem ficar) |
| `FREQUENCY_MISMATCH` | Um plano cobra em outra frequência. Mandatos do Mercado Pago não mudam de cadência |
| `BELOW_MINIMUM` | O novo total fica abaixo do mínimo de cobrança do Mercado Pago para a moeda |
| `INVALID_INPUT` | Dados mal formados: lista de produtos vazia, quantidade inválida, endereço incompleto. `field` diz qual |
| `MERCADO_PAGO_ERROR` | O Mercado Pago recusou a mudança. Nada foi aplicado; tente de novo em alguns minutos |
| `REJECTED` | Qualquer outra regra do admin (por exemplo, caixas são editadas no portal). O `message` explica |

### Mutations disponíveis

| Mutation | O que faz |
| - | - |
| `subscriptionPause(id, overrideMinCycles)` | Pausa uma assinatura ativa. O Mercado Pago para de cobrar até ela ser retomada |
| `subscriptionResume(id)` | Retoma uma pausada. O Mercado Pago retoma o calendário e define a próxima cobrança |
| `subscriptionCancel(id, reason, note, overrideMinCycles)` | Cancela com um motivo (`TOO_EXPENSIVE`, `PRODUCT_PILED_UP`, `WANT_ANOTHER_PRODUCT`, `DELIVERY_PROBLEMS`, `NO_LONGER_NEEDED`, `OTHER`) e uma nota opcional. Aparece na análise de cancelamentos |
| `subscriptionSkipNextCycle(id)` | Pula a próxima entrega: esse ciclo não é cobrado nem enviado |
| `subscriptionSetItems(id, items)` | Substitui as linhas de produto pela lista dada (`planId` + `quantity`). Mesma frequência, planos ativos. Recalcula total e frete e atualiza o Mercado Pago |
| `subscriptionUpdateShippingAddress(id, address)` | Substitui o endereço de entrega dos próximos pedidos. `address1`, `city` e `zip` são obrigatórios; o frete não é recotado |
| `subscriptionPortalLink(id)` | Um link assinado e com validade que abre o portal do assinante no domínio principal da loja. Trate-o como uma credencial |
| `checkoutLinkCreate(lines, customer, externalRef)` | Monta um link de checkout com vários produtos (cada linha com o seu `planId` e quantidade, mesmo com frequências diferentes; ou só `variantId` para compra avulsa) e, opcionalmente, os dados do cliente pré-preenchidos e uma referência sua que volta na assinatura e nos webhooks. Com dados do cliente, o link vence em 30 dias e é de uso único. Veja a [receita do bot](https://docs.appreval.com/pt/desenvolvedores/receitas.md#3b-um-bot-que-monta-o-checkout-pronto-para-o-cliente) |

Todas devolvem `{ subscription, userErrors }`, exceto `subscriptionPortalLink` e `checkoutLinkCreate`, que devolvem `{ url, expiresAt, userErrors }`.

**Pausar a partir de um bot de suporte**

```graphql theme={null}
mutation PauseSub($id: ID!) {
  subscriptionPause(id: $id) {
    subscription { id status nextPaymentDate }
    userErrors { code message field }
  }
}
```

**Cancelar com motivo e nota**

```graphql theme={null}
mutation {
  subscriptionCancel(id: "cm2abc…", reason: OTHER, note: "Vai morar fora") {
    subscription { id status statusHistory { toStatus source reason changedAt } }
    userErrors { code message }
  }
}
```

**Trocar produtos**

```graphql theme={null}
mutation {
  subscriptionSetItems(id: "cm2abc…", items: [{ planId: "pl_mensal_cafe", quantity: 2 }, { planId: "pl_mensal_filtros", quantity: 1 }]) {
    subscription { amount currency items { shopifyVariantId quantity unitAmount } }
    userErrors { code message field }
  }
}
```

**Link para o portal em um e-mail de retenção**

```graphql theme={null}
mutation { subscriptionPortalLink(id: "cm2abc…") { url expiresAt userErrors { code } } }
```

***

## Exemplos

**Assinaturas ativas com seus produtos e a próxima cobrança**

```graphql theme={null}
{
  subscriptions(first: 50, filter: { status: [ACTIVE] }, sortKey: NEXT_PAYMENT_DATE, reverse: true) {
    nodes {
      id customerEmail shopifyCustomerId amount currency nextPaymentDate
      frequency frequencyType
      items { shopifyVariantId quantity unitAmount }
      shippingAddress { city province countryCode }
    }
    pageInfo { hasNextPage endCursor }
  }
}
```

**Um cliente com todo o histórico**

```graphql theme={null}
{
  customer(email: "ana@example.com") {
    email shopifyCustomerId subscriptionCount activeSubscriptionCount
    subscriptions(first: 20) { nodes { id status createdAt plan { name } } }
    orders(first: 50) { nodes { shopifyOrderName cycleNumber amount createdAt } }
  }
}
```

**Cancelamentos do mês, com motivo**

```graphql theme={null}
{
  subscriptions(first: 100, filter: { status: [CANCELLED], updatedAfter: "2026-10-01T00:00:00Z" }) {
    nodes {
      id customerEmail
      statusHistory { fromStatus toStatus source reason changedAt }
    }
  }
}
```

**Planos e caixas publicados**

```graphql theme={null}
{
  planGroups(filter: { status: ACTIVE }) {
    nodes { id title frequency frequencyType priceType discountPercent amount plans { shopifyVariantId amount } }
  }
  boxes(filter: { status: ACTIVE }) {
    nodes { id title handle type shopifyProductId planGroups { title } options { kind shopifyVariantId } }
  }
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.