# Sessions

Create a PIX charge from a product and get a hosted URL plus a raw PIX code for a transparent checkout.

A **session** creates one PIX charge from an active PIX [product](/docs/checkout-products) and returns two presentations of that same charge: a Dolafy-hosted `checkoutUrl` and the raw PIX **Copia e Cola** string in `pix.code`. Render the code as your own QR image for a transparent checkout, or redirect the buyer to the URL. Switching between them never creates a second charge.

## Create a session

```http
POST /v1/checkout/sessions
x-api-key: dlfy_live_...
Idempotency-Key: session:txn_1048
content-type: application/json
```

Provide **exactly one** of `productId` (≤ 120 chars) or `externalProductId` (≤ 200 chars). Optional fields: `transactionReference` (≤ 200), `userId` (≤ 200), `buyerEmail` (valid email, ≤ 320), `description` (≤ 500), and `discountPercent`. The discount may be a number or numeric string greater than 0 and less than 100, with at most two decimal places. It changes only this session's charge; the reusable product and its rail remain unchanged. If `transactionReference` is omitted, Dolafy generates `txn_<uuid>`.

### Request

```json
{
  "externalProductId": "curso-pro",
  "transactionReference": "txn_1048",
  "userId": "user_42",
  "buyerEmail": "ana.silva@example.com",
  "description": "Curso Pro checkout",
  "discountPercent": "20.00"
}
```

### Response

Returns `201 Created` on first creation, `200 OK` on an identical replay.

```json
{
  "session": {
    "id": "310ace43-4c69-4fd2-acd8-94645dbe1938",
    "transactionReference": "txn_1048",
    "status": "created",
    "checkoutUrl": "https://dolafy.com/checkout/c-8f41a2d9?dolafy_session_id=310ace43-4c69-4fd2-acd8-94645dbe1938",
    "expiresAt": "2026-07-22T12:30:00.000Z",
    "product": {
      "id": "6a713a92-350a-4cc6-a996-1e55e97d3a36",
      "externalProductId": "curso-pro",
      "slug": "c-8f41a2d9",
      "name": "Curso Pro",
      "amount": "39.92",
      "currency": "BRL"
    },
    "pricing": {
      "originalAmount": "49.90",
      "discountPercent": "20.00",
      "discountAmount": "9.98",
      "finalAmount": "39.92",
      "currency": "BRL"
    },
    "pix": {
      "code": "000201...6304ABCD",
      "amount": "39.92",
      "currency": "BRL",
      "expiresAt": "2026-07-22T12:30:00.000Z"
    }
  }
}
```

### Notes

- `session.id` is the checkout charge id. `pix.code` is the payable Copia e Cola payload — **not** a bank PIX key.
- The hosted URL uses the reserved `dolafy_session_id` parameter to load and poll that already-created charge; opening it never creates a second charge. This parameter is **not** copied into your attribution, webhook `data.urlParams`, or success redirects. Treat the full hosted URL as payment-session data — do not expose it in logs.
- `session.status` is the current normalized status — normally `created` on first response; a replay can return a later `pending`, `paid`, `expired`, or `failed`.
- `pricing` makes the one-off adjustment auditable. `product.amount` and `pricing.finalAmount` are the final charge total; `pix.amount` is the exact BRL amount the buyer must pay.
- When `discountPercent` is omitted, `pricing.discountPercent` is `null`, `discountAmount` is `0.00`, and the original and final amounts are equal.
- `transactionReference` is persisted as `txn_id` and `userId` as the charge `externalId`; both are returned by the [Transactions](/docs/checkout-transactions) reads and included in [webhook](/docs/checkout-webhooks) data.
- Confirmation is driven by [webhooks](/docs/checkout-webhooks) — `checkout.payment.completed` or `checkout.payment.failed`. It never depends on a success page or browser redirect. Fulfil only after `checkout.payment.completed`.

## Idempotency & conflicts

An identical retry with the same `Idempotency-Key` returns the same charge, hosted URL, and PIX code. Replaying a completed session still works even after its product is archived.

- Reusing an idempotency key with a **different** body → `409 idempotency_key_conflict`.
- Changing `discountPercent` changes the body. Equivalent forms such as `20`, `"20"`, and `"20.00"` normalize identically.
- Assigning the same `transactionReference` to another session → `409 transaction_reference_conflict`.

## Rate limits

Session creation is limited per customer and per organization: **3 new sessions per customer per 10 minutes**, and **30 per organization per minute**. Exceeding a limit returns `429` with a `Retry-After` header and either `checkout_session_customer_rate_limit_exceeded` or `checkout_session_organization_rate_limit_exceeded`. Customer identity is derived from `userId`, then `buyerEmail`, then `transactionReference`. Identical idempotent replays do not consume the limit.

## Errors

| Status | Code | Meaning |
|--------|------|---------|
| `400` | `invalid_idempotency_key` | The required key is missing or malformed. |
| `400` | `invalid_body` | Invalid fields, or not exactly one product identifier. |
| `400` | `invalid_discount_percent` | Percentage is not greater than 0 and less than 100, or has more than two decimals. |
| `400` | `checkout_session_pix_only` | The selected product is not PIX-only. |
| `400` | `checkout_pix_invalid_amount` | The product has an invalid payable amount. |
| `404` | `product_not_found` | No active organization-owned product matched. |
| `409` | `idempotency_key_conflict` | The key was reused with another body. |
| `409` | `transaction_reference_conflict` | Another session already reserved the reference. |
| `409` | `checkout_session_in_progress` | Retry the identical request with the same key shortly. |
| `429` | `checkout_session_*_rate_limit_exceeded` | Wait for `Retry-After`. |
| `503` | `checkout_pix_not_available` | Your organization's PIX route is unavailable. |

Rare edge-case codes are listed under [Advanced](#advanced).

## Advanced

### Discount rounding and limits

The discount is calculated with exact basis-point arithmetic and rounded half-up to cents. A discount that rounds to less than one cent, or that produces a PIX total below R$1.00, is rejected before payment instructions are created — see the codes below. Discounts are accepted only by this authenticated session endpoint; do not add discount query parameters to reusable hosted product links.

### Rare errors

| Status | Code | Meaning |
|--------|------|---------|
| `400` | `checkout_session_discount_too_small` | The discount rounds to less than one cent. |
| `400` | `checkout_session_amount_below_minimum` | The final PIX total would be below R$1.00. |
| `409` | `checkout_session_creation_failed` | Do not blindly retry with a new key; contact support. |
| `502` | `checkout_pix_missing_payment_instructions` | A payable PIX code could not be generated; retry shortly. |
| `503` | `checkout_session_discount_unavailable` | Discounted charge persistence is temporarily unavailable; retry after deployment completes. |
