# Criar checkout

Envie POST /v1/checkout para criar uma cobrança PIX.

**`POST` `/v1/checkout`**

## Endpoint

`POST /v1/checkout` cria uma cobrança PIX e devolve HTTP 200.

## Headers

- **`X-Public-Key`** `string` (obrigatório, header)
  Identifica a empresa.

- **`X-Secret-Key`** `string` (obrigatório, header)
  Valida a empresa com comparação timing-safe.

- **`Accept`** `string` (opcional, header)
  Recomendado: application/json.

- **`Content-Type`** `string` (obrigatório, header)
  application/json.

## Body

- **`amount`** `integer` (obrigatório, body)
  Valor cobrado em centavos inteiros, positivo. Não é recalculado a partir de items.
  - inteiro em centavos
  - maior que 0

- **`paymentMethod`** `"pix"` (obrigatório, body)
  Único método do checkout público. Case-sensitive.

- **`postbackUrl`** `string` (opcional, body)
  Prioridade sobre o webhook da empresa. HTTPS público, sem credenciais, até 500 caracteres.

- **`items`** `array` (obrigatório, body)
  Mínimo de um item.
  - mínimo 1 item
  - **`title`** `string` (obrigatório, body)
    Descrição do item.
    - 1 a 255 caracteres

  - **`unitPrice`** `integer` (obrigatório, body)
    Preço unitário em centavos.
    - inteiro ≥ 1

  - **`quantity`** `integer` (obrigatório, body)
    Quantidade.
    - inteiro ≥ 1

  - **`tangible`** `boolean` (opcional, body)
    Item físico.

  - **`externalRef`** `string` (opcional, body)
    Referência do item no seu sistema, gravada em metadata.
    - até 255 caracteres

- **`customer`** `object` (obrigatório, body)
  Pagador. O objeto é obrigatório.
  - **`name`** `string` (obrigatório, body)
    Nome do pagador.
    - 2 a 255 caracteres

  - **`email`** `string` (opcional, body)
    E-mail válido.
    - até 255 caracteres

  - **`phone`** `string` (opcional, body)
    Somente dígitos.
    - 10 ou 11 dígitos

  - **`document.number`** `string` (obrigatório, body)
    CPF ou CNPJ numérico, 11 a 14 dígitos.

  - **`document.type`** `"cpf" | "cnpj" | "CPF" | "CNPJ"` (obrigatório, body)
    Tipo do documento.

  - **`address`** `object` (opcional, body)
    Opcional. zipCode ≤10; street, neighborhood e city ≤255; streetNumber ≤20; state e country com 2 caracteres.

- **`pix.expiresInDays`** `integer` (opcional, body)
  Validade em dias, convertida para segundos.
  - 1 a 30

## Resposta 200

- **`uuid`** `uuid`
  Identificador público da transação na GrandePay.

- **`status`** `integer`
  Status numérico. Na criação é 0.

- **`amount_cents`** `integer`
  Valor da cobrança em centavos inteiros.

- **`pix_code`** `string` (nullable)
  Código copia e cola do PIX.

- **`pix_expiration`** `string` (nullable)
  ISO 8601 de expiração do QR, se o provedor informar.

- **`provider_transaction_id`** `string` (nullable)
  ID da operação no provedor.

- **`external_id`** `string`
  Referência interna no formato GP-uuid.

- **`created_at`** `string`
  Timestamp ISO 8601 de criação da transação.

No postback, `data.id` é o mesmo `uuid`. O postback omite campos nulos; esta resposta pode trazer `null`.

## Erros

A lista completa está em [Erros](https://www.grandepay.com.br/docs/api-reference/errors).

**Outros erros**

```json title="403"
{
  "error": "KYC approval required"
}
```

```json title="502"
{
  "error": "Provider Medusa: upstream timeout",
  "code": "PROVIDER_ERROR"
}
```

## Exemplos

**Requisição de checkout**

```bash title="cURL"
curl --request POST 'https://api-gateway.grandepay.com.br/v1/checkout' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/json' \
  --header 'X-Public-Key: pk_sua_empresa' \
  --header 'X-Secret-Key: sk_sua_empresa' \
  --data '{"amount":1500,"paymentMethod":"pix","postbackUrl":"https://empresa.example/api/grandepay/postback","items":[{"title":"Assinatura mensal","unitPrice":1500,"quantity":1,"externalRef":"plan-pro-001"}],"customer":{"name":"Maria de Souza","email":"maria@example.com","phone":"11999999999","document":{"number":"12345678901","type":"cpf"}},"pix":{"expiresInDays":2}}'
```

```javascript title="Node"
const publicKey = process.env.GRANDEPAY_PUBLIC_KEY;
const secretKey = process.env.GRANDEPAY_SECRET_KEY;

if (!publicKey || !secretKey) {
  throw new Error("Defina GRANDEPAY_PUBLIC_KEY e GRANDEPAY_SECRET_KEY no servidor.");
}

const response = await fetch("https://api-gateway.grandepay.com.br/v1/checkout", {
  method: "POST",
  headers: {
    Accept: "application/json",
    "Content-Type": "application/json",
    "X-Public-Key": publicKey,
    "X-Secret-Key": secretKey,
  },
  body: JSON.stringify({
    amount: 1500,
    paymentMethod: "pix",
    postbackUrl: "https://empresa.example/api/grandepay/postback",
    items: [
      {
        title: "Assinatura mensal",
        unitPrice: 1500,
        quantity: 1,
      },
    ],
    customer: {
      name: "Maria de Souza",
      document: {
        number: "12345678901",
        type: "cpf",
      },
    },
    pix: {
      expiresInDays: 2,
    },
  }),
});

if (!response.ok) {
  const errorBody = await response.json().catch(() => null);
  const message = errorBody && errorBody.error ? errorBody.error : "resposta não JSON";
  throw new Error("Checkout recusado (" + response.status + "): " + message);
}

const data = await response.json();
```

```php title="PHP"
<?php
$publicKey = getenv('GRANDEPAY_PUBLIC_KEY');
$secretKey = getenv('GRANDEPAY_SECRET_KEY');

if ($publicKey === false || $secretKey === false) {
    throw new RuntimeException('Defina GRANDEPAY_PUBLIC_KEY e GRANDEPAY_SECRET_KEY no servidor.');
}

$payload = [
    'amount' => 1500,
    'paymentMethod' => 'pix',
    'postbackUrl' => 'https://empresa.example/api/grandepay/postback',
    'items' => [
        [
            'title' => 'Assinatura mensal',
            'unitPrice' => 1500,
            'quantity' => 1,
        ],
    ],
    'customer' => [
        'name' => 'Maria de Souza',
        'document' => [
            'number' => '12345678901',
            'type' => 'cpf',
        ],
    ],
    'pix' => [
        'expiresInDays' => 2,
    ],
];

$ch = curl_init('https://api-gateway.grandepay.com.br/v1/checkout');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Accept: application/json',
        'Content-Type: application/json',
        'X-Public-Key: ' . $publicKey,
        'X-Secret-Key: ' . $secretKey,
    ],
    CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES),
]);

$result = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($result === false) {
    throw new RuntimeException('Falha de rede ao criar o checkout.');
}

$data = json_decode($result, true);

if ($status < 200 || $status >= 300) {
    $message = is_array($data) && isset($data['error']) ? $data['error'] : 'resposta não JSON';
    throw new RuntimeException('Checkout recusado (' . $status . '): ' . $message);
}
```

**Respostas**

```json title="200"
{
  "uuid": "4d78d8ca-a9f1-4c2a-bf15-7f8ca71e3a70",
  "status": 0,
  "amount_cents": 1500,
  "pix_code": "00020101021226870014br.gov.bcb.pix2565qrcodes.example.com/pix/4d78d8ca",
  "pix_expiration": "2026-03-15T17:00:00.000Z",
  "provider_transaction_id": "prov_tx_123",
  "external_id": "GP-9a5f1eb3-6885-4f26-93f3-3c0954d0f42f",
  "created_at": "2026-03-13T17:00:00.000Z"
}
```

```json title="401"
{
  "error": "Missing X-Public-Key / X-Secret-Key"
}
```

```json title="422"
{
  "error": "Validation failed",
  "details": "Expected property 'paymentMethod' to be equal to 'pix'"
}
```
