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

> ## Agent Instructions
> Documentação das APIs da DeliveryVip, em português. Regras que mais quebram integrações: (1) Merchant API: faça polling em GET /merchant/v3/events:polling no máximo a cada 30 s por token; confirme todo evento recebido com POST /merchant/v3/events/acknowledgment e corpo [{"id": "<eventId>"}] (o campo lido é id, não eventId), inclusive eventos repetidos ou que o sistema não trata; deduplique por eventId; o evento é só um aviso, busque o pedido na orderURL. (2) Tokens client_credentials valem 24 h e não têm refresh_token: peça outro ao receber 401. (3) Webhooks: valide a assinatura HMAC sobre o corpo bruto e responda 2xx rápido; o polling é a reconciliação. (4) Open Delivery: host od.deliveryvip.com.br, um client_secret por loja, datas em UTC. (5) Fidelidade: o operador implementa 5 rotas assinadas com HMAC e chama o config push; o estorno precisa ser idempotente. (6) Partner API: scope dv.partner. Não invente endpoints: use a referência gerada das specs OpenAPI.

# Autenticação

> Obtenha, use, renove e revogue o token OAuth2 que autentica o seu app nas APIs da DeliveryVip.

Todas as APIs da DeliveryVip usam **OAuth2**. O seu app troca as credenciais por um token de acesso e envia esse token no header `Authorization` de cada chamada.

| Endpoint | Para quê |
| - | - |
| `POST /authentication/v1/oauth/token` | Obter um token (`client_credentials`, `authorization_code` ou `refresh_token`). |
| `GET /authentication/v1/oauth/token/info` | Ver app, scopes e tempo restante do token atual. |
| `POST /authentication/v1/oauth/introspect` | Verificar se um token está ativo (RFC 7662). |
| `POST /authentication/v1/oauth/revoke` | Revogar um token antes de expirar (RFC 7009). |

Todos ficam em `https://api.deliveryvip.com.br`. O endpoint de token só aceita `Content-Type: application/x-www-form-urlencoded`.

> [!NOTE]
>
> O **Open Delivery** tem endpoint de token próprio, em `https://od.deliveryvip.com.br/oauth/token`, com um `client_secret` por loja. Veja [Primeiros passos do Open Delivery](/docs/guides/open-delivery/primeiros-passos.md).

## Obtendo o token

O fluxo depende do tipo de distribuição do app.

> [tab] Client credentials (direta)
>
> Usado por apps de [distribuição direta](/docs/inicio/primeiros-passos/distribuicao-direta.md). Um token atende todas as lojas que instalaram o app.
>
> **grant\_type** `string` *required*
>
> `client_credentials`
>
> **client\_id** `string` *required*
>
> Client ID do app.
>
> **client\_secret** `string` *required*
>
> Client Secret do app.
>
> **scope** `string` (default: od.all)
>
> Scopes separados por espaço, entre os que o app tem.
>
> ```bash cURL theme={null}
> curl --request POST 'https://api.deliveryvip.com.br/authentication/v1/oauth/token' \
>   --header 'Content-Type: application/x-www-form-urlencoded' \
>   --data-urlencode 'grant_type=client_credentials' \
>   --data-urlencode 'client_id=<clientId>' \
>   --data-urlencode 'client_secret=<clientSecret>' \
>   --data-urlencode 'scope=od.all'
> ```
>
> ```js Node.js theme={null}
> const response = await fetch('https://api.deliveryvip.com.br/authentication/v1/oauth/token', {
>   method: 'POST',
>   headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
>   body: new URLSearchParams({
>     grant_type: 'client_credentials',
>     client_id: process.env.DV_CLIENT_ID,
>     client_secret: process.env.DV_CLIENT_SECRET,
>     scope: 'od.all',
>   }),
> })
> const token = await response.json()
> ```
>
> ```python Python theme={null}
> response = requests.post(
>     "https://api.deliveryvip.com.br/authentication/v1/oauth/token",
>     data={
>         "grant_type": "client_credentials",
>         "client_id": os.environ["DV_CLIENT_ID"],
>         "client_secret": os.environ["DV_CLIENT_SECRET"],
>         "scope": "od.all",
>     },
> )
> token = response.json()
> ```
>
> > [!NOTE]
> >
> > Tokens de client credentials **não têm `refresh_token`**. Quando expirarem, peça outro com as mesmas credenciais.

> [tab] Authorization code (interativa)
>
> Usado por apps de [distribuição interativa](/docs/inicio/primeiros-passos/distribuicao-interativa.md), depois que a loja aprova a tela de consentimento e você recebe o `code` na Redirect URI.
>
> **grant\_type** `string` *required*
>
> `authorization_code`
>
> **client\_id** `string` *required*
>
> Client ID do app.
>
> **client\_secret** `string` *required*
>
> Client Secret do app.
>
> **code** `string` *required*
>
> O `code` recebido na Redirect URI. Uso único.
>
> **redirect\_uri** `string` *required*
>
> A mesma Redirect URI usada na autorização.
>
> ```bash cURL theme={null}
> curl --request POST 'https://api.deliveryvip.com.br/authentication/v1/oauth/token' \
>   --header 'Content-Type: application/x-www-form-urlencoded' \
>   --data-urlencode 'grant_type=authorization_code' \
>   --data-urlencode 'client_id=<clientId>' \
>   --data-urlencode 'client_secret=<clientSecret>' \
>   --data-urlencode 'code=<authorizationCode>' \
>   --data-urlencode 'redirect_uri=<redirectUri>'
> ```
>
> ```js Node.js theme={null}
> const response = await fetch('https://api.deliveryvip.com.br/authentication/v1/oauth/token', {
>   method: 'POST',
>   headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
>   body: new URLSearchParams({
>     grant_type: 'authorization_code',
>     client_id: process.env.DV_CLIENT_ID,
>     client_secret: process.env.DV_CLIENT_SECRET,
>     code,
>     redirect_uri: process.env.DV_REDIRECT_URI,
>   }),
> })
> const token = await response.json()
> ```
>
> ```python Python theme={null}
> response = requests.post(
>     "https://api.deliveryvip.com.br/authentication/v1/oauth/token",
>     data={
>         "grant_type": "authorization_code",
>         "client_id": os.environ["DV_CLIENT_ID"],
>         "client_secret": os.environ["DV_CLIENT_SECRET"],
>         "code": code,
>         "redirect_uri": os.environ["DV_REDIRECT_URI"],
>     },
> )
> token = response.json()
> ```

### Resposta

```json 200 OK theme={null}
{
  "access_token": "{token}",
  "token_type": "Bearer",
  "expires_in": 86400,
  "refresh_token": "{refreshToken}",
  "scope": "od.all",
  "created_at": 1761750000
}
```

**access\_token** `string`

O token para o header `Authorization`. É uma string opaca: não tente decodificá-la.

**token\_type** `string`

Sempre `Bearer`.

**expires\_in** `integer`

Segundos até o token expirar. Um token novo vale 86400 (24 horas); um token reaproveitado volta com o tempo que ainda resta.

**refresh\_token** `string`

Só no fluxo `authorization_code` (e nas renovações dele). Ausente em `client_credentials`.

**scope** `string`

Scopes concedidos, separados por espaço.

**created\_at** `integer`

Emissão do token, em Unix timestamp (segundos).

## Scopes

| Scope | Acesso |
| - | - |
| `od.all` | Merchant API V3 (eventos, pedidos, catálogo, indoor, descontos, tools) e Open Delivery. É o scope padrão quando `scope` é omitido. |
| `dv.loyalty` | Configuração do programa de [fidelidade](/docs/guides/merchant/loyalty.md) (`/merchant/v3/{merchantId}/loyalty/config`). Peça junto com `od.all`. |
| `dv.partner` | Partner API V2: lojas, revendas, assinaturas, recursos e faturas. |
| `dv.marketplace` | API de marketplace. |

Você só pode pedir scopes marcados nas permissões do app. Um token sem o scope que o endpoint exige recebe `403 Forbidden`.

## Usando o token

Envie o token no header `Authorization` em todas as chamadas:

```bash cURL theme={null}
curl 'https://api.deliveryvip.com.br/merchant/v3/{merchantId}/orders' \
  --header 'Authorization: Bearer {token}'
```

```js Node.js theme={null}
const response = await fetch(`https://api.deliveryvip.com.br/merchant/v3/${merchantId}/orders`, {
  headers: { Authorization: `Bearer ${token}` },
})
```

```python Python theme={null}
response = requests.get(
    f"https://api.deliveryvip.com.br/merchant/v3/{merchant_id}/orders",
    headers={"Authorization": f"Bearer {token}"},
)
```

## Expiração e reaproveitamento

O token de acesso expira em **24 horas**. Depois disso, as chamadas retornam `401 Unauthorized`.

> [!TIP]
>
> **Guarde o token e reaproveite.** Peça um token novo só quando ele estiver perto de expirar ou quando receber `401`. Pedir um token por chamada é desnecessário e só gera carga.

> [!NOTE]
>
> Enquanto o token vigente tiver usado menos de 80% da validade (cerca de 19 horas), um novo pedido com as mesmas credenciais e scopes devolve **o mesmo token**, com o `expires_in` restante. Passado esse ponto, a API emite um token novo.

### Renovando com refresh\_token

Tokens do fluxo `authorization_code` vêm com `refresh_token`. Use-o para obter um novo token sem pedir nova autorização à loja:

```bash cURL theme={null}
curl --request POST 'https://api.deliveryvip.com.br/authentication/v1/oauth/token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=refresh_token' \
  --data-urlencode 'refresh_token=<refreshToken>' \
  --data-urlencode 'client_id=<clientId>' \
  --data-urlencode 'client_secret=<clientSecret>'
```

```js Node.js theme={null}
const response = await fetch('https://api.deliveryvip.com.br/authentication/v1/oauth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'refresh_token',
    refresh_token: refreshToken,
    client_id: process.env.DV_CLIENT_ID,
    client_secret: process.env.DV_CLIENT_SECRET,
  }),
})
const token = await response.json()
```

```python Python theme={null}
response = requests.post(
    "https://api.deliveryvip.com.br/authentication/v1/oauth/token",
    data={
        "grant_type": "refresh_token",
        "refresh_token": refresh_token,
        "client_id": os.environ["DV_CLIENT_ID"],
        "client_secret": os.environ["DV_CLIENT_SECRET"],
    },
)
token = response.json()
```

> [!WARNING]
>
> A resposta traz um `access_token` **e** um `refresh_token` novos. Substitua os dois no seu armazenamento.

## Verificando o token

`GET /authentication/v1/oauth/token/info` descreve o token enviado no header. Útil como primeira chamada e para depurar `401` e `403`.

```bash cURL theme={null}
curl 'https://api.deliveryvip.com.br/authentication/v1/oauth/token/info' \
  --header 'Authorization: Bearer {token}'
```

```js Node.js theme={null}
const response = await fetch('https://api.deliveryvip.com.br/authentication/v1/oauth/token/info', {
  headers: { Authorization: `Bearer ${token}` },
})
const info = await response.json()
```

```python Python theme={null}
response = requests.get(
    "https://api.deliveryvip.com.br/authentication/v1/oauth/token/info",
    headers={"Authorization": f"Bearer {token}"},
)
info = response.json()
```

```json 200 OK theme={null}
{
  "resource_owner_id": null,
  "resource_owner_type": null,
  "scope": ["od.all"],
  "expires_in": 86321,
  "application": { "uid": "<clientId>" },
  "created_at": 1761750000
}
```

**scope** `string[]`

Scopes do token, como lista.

**expires\_in** `integer`

Segundos restantes até expirar.

**application.uid** `string`

O Client ID do app dono do token.

**resource\_owner\_id** `string | null`

Dono que autorizou o token no fluxo `authorization_code`, com o tipo em `resource_owner_type`. `null` em `client_credentials`.

Para checar um token sem usá-lo como Bearer, há também o [introspect](/docs/reference/merchant/v3/authentication.md), que responde `{ "active": false }` para tokens inválidos ou expirados.

## Revogando o token

Revogue um token (de acesso ou de refresh) quando ele não for mais usado, por exemplo ao desconectar uma loja do seu sistema. A resposta é sempre `200`, mesmo para tokens já inválidos.

```bash cURL theme={null}
curl --request POST 'https://api.deliveryvip.com.br/authentication/v1/oauth/revoke' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'token={token}' \
  --data-urlencode 'client_id=<clientId>' \
  --data-urlencode 'client_secret=<clientSecret>'
```

```js Node.js theme={null}
await fetch('https://api.deliveryvip.com.br/authentication/v1/oauth/revoke', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    token,
    client_id: process.env.DV_CLIENT_ID,
    client_secret: process.env.DV_CLIENT_SECRET,
  }),
})
```

```python Python theme={null}
requests.post(
    "https://api.deliveryvip.com.br/authentication/v1/oauth/revoke",
    data={
        "token": token,
        "client_id": os.environ["DV_CLIENT_ID"],
        "client_secret": os.environ["DV_CLIENT_SECRET"],
    },
)
```

## Erros de autenticação

Os erros do endpoint de token seguem o padrão OAuth2 (`error` e `error_description`), diferente do [formato de erros](/docs/inicio/conceitos/erros.md) das demais rotas.

```json 401 Unauthorized theme={null}
{
  "error": "invalid_client",
  "error_description": "Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method."
}
```

| Status | `error` | Causa e solução |
| - | - | - |
| `400` | `invalid_scope` | Pediu um scope que o app não tem. Ajuste `scope` ou as permissões do app. |
| `400` | `invalid_grant` | `code` ou `refresh_token` inválido, expirado ou já usado, ou `redirect_uri` diferente. Refaça a autorização. |
| `400` | `unsupported_grant_type` | `grant_type` diferente de `client_credentials`, `authorization_code` ou `refresh_token`. |
| `401` | `invalid_client` | `client_id` ou `client_secret` errado. Confira as credenciais no painel. |

Nas demais rotas:

| Status | Quando | O que fazer |
| - | - | - |
| `401` | Token ausente, expirado ou revogado. | Peça um novo token (ou renove com `refresh_token`) e repita a chamada uma vez. |
| `403` | Token válido sem o scope que a rota exige. A resposta vem **sem corpo**. | Peça o token com o scope certo; se o app não tiver a permissão, ajuste-a no painel. |

```json 401 Unauthorized theme={null}
{ "title": "Unauthorized", "status": 401 }
```


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