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

# Produtos

> Existem dois tipos de produto com o mesmo nome. Aqui está qual é qual.

Esta é a parte da API que mais gera ticket de suporte, então vale começar pelo ponto:

> [!WARNING]
>
> `GET /v1/products` pode devolver uma lista vazia com a sua loja cheia de produtos. Não é
> bug. São duas coleções diferentes.

> [card] Produto de cobrança
>
> Criado **pela API**, com `POST /v1/products`.
>
> Tem `priceCents` (número inteiro).
>
> É o único aceito em `POST /v1/payments` e `POST /v1/subscriptions`.
>
> Vive em `/v1/products`.

> [card] Produto da loja
>
> Criado **no painel**, na sua loja do Discord.
>
> Tem `price` (texto tipo `"49,90"`), categoria, variações e estoque.
>
> Não serve para gerar cobrança pela API.
>
> Vive em `/v1/store/products`, somente leitura.

Os dois não vêm juntos por padrão porque a forma é diferente. Código fazendo
`priceCents / 100` viraria `NaN` ao receber um produto de loja, e quem repassasse um id
de loja para criar cobrança levaria `404` sem entender o motivo.

## Pedindo os dois de uma vez

| Chamada | Devolve |
| - | - |
| `GET /v1/products` | Só os de cobrança. É o padrão e não mudou |
| `GET /v1/products?include=all` | Os dois, com um campo `type` em cada item |
| `GET /v1/products?include=store` | Só os da loja |
| `GET /v1/store/products` | Só os da loja |

```json GET /v1/products?include=all theme={null}
{
  "products": [
    { "type": "api",   "_id": "60f7...", "name": "Plano Premium", "priceCents": 4990 },
    { "type": "store", "id": "70a2...", "name": "Conta Full", "price": "249,90", "variations": [] }
  ]
}
```

Todas aceitam `?includeInactive=true` para incluir os desativados.

> [!TIP]
>
> Se `GET /v1/products` vier vazio e você tiver produtos na loja, a resposta traz um campo
> `hint` apontando a rota certa. Ele é aditivo, então quem só lê `products` não sente
> diferença.

## Produtos de cobrança

```bash Criar theme={null}
curl -X POST https://api.purin.cash/v1/products \
  -H "Authorization: Bearer $PURINCASH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Plano Premium",
    "description": "Acesso completo por 30 dias",
    "priceCents": 4990,
    "metadata": "{\"sku\":\"premium-01\"}"
  }'
```

**name** `string` *required*

Até 200 caracteres.

**priceCents** `integer` *required*

Preço em centavos. Mínimo de 100, ou seja R\$ 1,00.

**description** `string`

Até 500 caracteres.

**currency** `string` (default: BRL)

Produto em outra moeda é convertido para real na hora de gerar a cobrança.

**metadata** `string`

String JSON de até 2 KB, devolvida sem alteração.

O `PUT` aceita os mesmos campos, todos opcionais, mais `active` para ativar ou desativar.
Mande apenas o que quer mudar.

Limite de 500 produtos por conta e por ambiente.

> [!NOTE]
>
> Repare que o mínimo de um produto (R\$ 1,00) é diferente do mínimo de uma cobrança avulsa
> (R\$ 0,80, ajustável pela loja). Não é inconsistência: produto é catálogo, cobrança é
> transação.

## Produtos da loja

Leitura apenas. Para criar ou editar, use o painel.

```bash theme={null}
curl "https://api.purin.cash/v1/store/products?includeInactive=true" \
  -H "Authorization: Bearer $PURINCASH_KEY"
```

```json theme={null}
{
  "products": [
    {
      "type": "store",
      "id": "70a2b1c3d4e5f6a7b8c9d0e1",
      "publicId": "plano-premium",
      "name": "Plano Premium",
      "description": "Acesso completo por 30 dias",
      "price": "49,90",
      "category": "Assinaturas",
      "active": true,
      "deliveryType": "automatica",
      "variations": [
        { "id": "a1b2...", "name": "Mensal", "price": "49,90", "stock": 42, "unlimited": false },
        { "id": "c3d4...", "name": "Anual", "price": "499,00", "stock": null, "unlimited": true }
      ]
    }
  ]
}
```

### Estoque ilimitado vem como `null`

> [!WARNING]
>
> Variação com fornecedor externo, estoque via API ou estoque fictício infinito não tem um
> número para contar. Nesses casos `stock` vem `null` e `unlimited` vem `true`.
>
> O valor **não** é `0`, justamente porque zero você leria como esgotado e esconderia um
> produto que está à venda.

Na prática, a checagem correta é:

```js theme={null}
const disponivel = variacao.unlimited || variacao.stock > 0;
```

### O que não sai nessa rota

> [!NOTE]
>
> O conteúdo do estoque (a chave, a conta ou o link que o comprador recebe) e as
> instruções de entrega não são retornados. Variações de seller ainda não aprovadas também
> ficam de fora, porque não são vendáveis.

Para ver o que foi entregue em um pagamento específico, existe a rota de
[entrega](https://docs.purin.cash/guias/entrega.md).


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