# PIX Cash-In -- Generate QR Code

Generates a PIX charge with a QR Code for receiving payments into the account associated with your API Key.

## Endpoint

```
POST /api/external/pix/cash-in
```

## Headers

| Header | Type | Required | Description |
|--------|------|----------|-------------|
| `Authorization` | String | Yes | `ApiKey {client_id}:{client_secret}` |
| `Content-Type` | String | Yes | `application/json` |
| `hmac` | String | Yes | HMAC-SHA512 signature of the body ([learn more](/en/hmac)) |
| `Idempotency-Key` | String | No | Unique key to prevent duplicate processing (max 256 chars) |
| `X-Key-Case` | String | No | Set to `camelCase` to receive response fields in camelCase (default is snake_case) |

## Request Body

| Field | Type | Required | Description | Example |
|-------|------|----------|-------------|---------|
| `amount` | Integer | Yes | Amount in **centavos** (R$ 30.00 = `3000`) | `3000` |
| `description` | String | No | Charge description. If omitted, the default is `"Cobranca PIX"`. | `"Pedido #1234"` |
| `external_id` | String | No | Your system identifier for tracking. Max 128 chars. Only `a-zA-Z0-9._:-` (out-of-pattern values are silently discarded and the field returns as `null` -- see warning below). Returned in responses and webhooks. | `"order-9876"` |
| `pix_key` | String | No | Specific PIX key to generate the QR Code. If omitted, uses the most recent active key of the account (ordered by `inserted_at DESC`). | `"12345678901"` |
| `city` | String | No | Recipient city in the QR Code. Default: `SAO PAULO`. Automatically truncated to 15 characters. | `"RIO DE JANEIRO"` |
| `expiration_seconds` | Integer | No | QR Code lifetime in seconds. Range **3–86400** (3 s to 24h); out-of-range returns `400`. If omitted, uses the account default, then the system default (15 min). Use to cap the exposure of dynamic QRs (e.g. `300` = 5 min, anti-fraud). | `300` |
| `include_image` | Boolean | No | If `false`, the response returns an empty `qr_code_image` (`""`) and image generation is skipped. Use when you only need the copy-paste code (`qr_code`). Default: `true`. | `false` |
| `expected_payer_document` | String | No | CPF (11 digits) or CNPJ (14 digits) of the expected payer. Formatted input (dots/dashes/slash) is accepted and normalized to digits; invalid length/format returns `400`. Binds the charge to an expected payer - used by an **opt-in** control (see below). camelCase alias: `expectedPayerDocument`. | `"12345678901"` |

::: danger Always send `amount` as integer (in centavos)
The `amount` field **MUST** be an **integer in centavos**. **NEVER** send `float`/`decimal` values:

- ✅ `"amount": 3000` → R$ 30.00
- ❌ `"amount": 30.00` → interpreted as `30` centavos = R$ 0.30 (charge **100× smaller** than intended)
- ❌ `"amount": 30` → R$ 0.30 (also incorrect)

In JavaScript, always convert with `Math.round` before sending:

```js
const valueInBRL = 30.0;
const amount = Math.round(valueInBRL * 100); // 3000
```

In Python: `amount = round(value_in_brl * 100)`. In Go: `amount := int(math.Round(valueInBRL * 100))`.
:::

::: danger Monetary values (request vs response)
**Request** values are in **centavos** (R$ 1.00 = `100`). **Response** values are in **base units** (R$ 1.00 = `10000`). To convert the response to BRL, **divide by 10,000**. Never use floating point in either direction.
:::

::: warning Invalid `external_id` = silent discard
If `external_id` contains characters outside `a-zA-Z0-9._:-`, exceeds 128 bytes, or is only whitespace, the server **silently discards** the value and the response returns with `"external_id": null` (no error is returned). **Always check** the `external_id` present in the response before using it for reconciliation -- if it came back `null`, your identifier was rejected by validation.
:::

::: info Expected-payer opt-in control - no default impact
`expected_payer_document` feeds an **optional, per-account control that is OFF by default**. No existing integration is affected unless your account team enables the "expected-payer verification" control.

- **With the control enabled:** an incoming PIX for this charge whose payer document does **not match** (or is missing) is **rejected and returned to the payer** (not credited).
- **With the control disabled (default):** the value is merely stored and has no gating effect.
- **If the field is omitted:** nothing changes.

Details in [Account Controls](/en/account-controls).
:::

## Example

```bash
BODY='{"amount":3000,"description":"Pedido #1234","external_id":"order-9876"}'
HMAC=$(echo -n "$BODY" | openssl dgst -sha512 -hmac "$CLIENT_SECRET" | awk '{print $2}')

curl -X POST https://api.monetarie.com/api/external/pix/cash-in \
  -H "Authorization: ApiKey $CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/json" \
  -H "hmac: $HMAC" \
  -d "$BODY"
```

## Success Response (200)

```json
{
  "worked": true,
  "transaction_id": "7popu57v6us7p6pcicgq12345",
  "qr_code": "00020126580014br.gov.bcb.pix...",
  "qr_code_image": "data:image/png;base64,iVBORw0KGgo...",
  "external_id": "order-9876",
  "amount": 300000,
  "status": "active",
  "type": "dynamic",
  "location_url": "https://qrcode-h.monetarie.com/pix/v2/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  "expires_at": "2026-03-07T16:30:00Z"
}
```

| Field | Type | Description |
|-------|------|-------------|
| `worked` | Boolean | `true` indicates operation success |
| `transaction_id` | String \| null | Unique charge identifier (QR Code tx_id, alphanumeric up to 35 chars per BACEN spec). `null` only in rare internal persistence failures |
| `qr_code` | String \| null | EMV copy-and-paste code for payment. `null` (literal JSON value, **not** the string `"null"`) only in rare internal persistence failures |
| `qr_code_image` | String | QR Code image encoded in base64 (PNG, prefix `data:image/png;base64,`). Empty string (`""`) in rare internal failures. Also empty when the request sends `include_image: false`. |
| `external_id` | String \| null | Your identifier, returned as sent. `null` if not provided, invalid (see warning above), or discarded by sanitization |
| `amount` | Integer | Charge amount in **base units** (÷ 10,000 for BRL). `300000` = R$ 30.00 |
| `status` | String | Initial status is always `active` (QR Code active for payment). Subsequent evolution (`paid`/`expired`/`cancelled`) is visible in queries and webhooks (see [QR lifecycle](#qr-code-lifecycle)) |
| `type` | String \| null | QR Code type: `dynamic` (default since April 2026) or `static`. Dynamic requires the payer's bank to query `location_url` and validate the JWS |
| `location_url` | String \| null | URL to query the BACEN dynamic payload (e.g., `https://qrcode-h.monetarie.com/pix/v2/{uuid}`). **It is not the JWS itself** -- the payer's bank performs a `GET` on this endpoint to receive the signed ISO 20022 payload (JWS PS256). Present only on dynamic QR; `null` on static |
| `expires_at` | String \| null | QR Code expiration date/time in **ISO 8601 UTC with `Z` suffix** (e.g., `2026-03-07T16:30:00Z`). `null` if QR has no expiration |

::: info Dynamic QR is the default
All production accounts use **dynamic QR** by default since April 2026. Dynamic QR includes a URL (`location_url`) that the payer's bank queries to obtain the signed JWS payload -- maximum compatibility with BACEN-strict banks.
:::

## Error Response (400)

```json
{
  "worked": false,
  "detail": "O campo amount é obrigatório"
}
```

## Error Response (401)

```json
{
  "error": {
    "status": 401,
    "message": "Missing API key credentials. Use Authorization: ApiKey <client_id>:<client_secret>"
  }
}
```

## Error Response (422)

```json
{
  "worked": false,
  "detail": "Invalid HMAC signature"
}
```

## Recommended Flow

1. Generate the charge with this endpoint
2. Display the QR Code (`qr_code_image`) or the copy-and-paste code (`qr_code`) to the payer
3. Receive confirmation via [Webhook](/en/webhooks) when payment is made
4. Or query the status: [by ID](/en/pix-cashin-status), [by E2E](/en/pix-cashin-e2e), [by Tag](/en/pix-cashin-tag)

::: warning QR Code validity
By default the QR Code is valid for **15 minutes** (system default). You can override this **per request** with `expiration_seconds` (range 3–86400s), or set a **per-account** default (`qrcode_expiration_seconds`). Precedence: `expiration_seconds` (request) > account default > system default. Always check the `expires_at` returned in the response for the exact date/time.

After expiration, the status changes to `expired` automatically (via internal worker, runs every 5 minutes). For charges manually cancelled by the merchant, the status is `cancelled`. See the full status list in [Query Cash-In by ID](/en/pix-cashin-status).
:::

## QR Code Lifecycle

The QR Code transitions through these states after creation:

| State | Origin | Description |
|-------|--------|-------------|
| `active` | Initial | QR generated successfully, ready to be scanned/paid |
| `paid` | Payment received | BACEN payment confirmed and linked to the QR (via internal worker after ACCC) |
| `expired` | `QrExpirationChecker` worker | TTL reached `expires_at`. Worker runs every 5 min marking expired `active` QRs and dispatching the `pix.charge.expired` webhook |
| `cancelled` | Manual or bulk | Cancelled by the merchant (via portal) or in administrative operations (e.g., static→dynamic migration in April 2026 cancelled active static QRs with value) |
| `used` | Legacy | Transient intermediate state of the old pipeline. New clients should treat it as equivalent to `paid` |

::: info In queries, `paid` may appear as `settled`
The endpoints `GET /transactions/:id` and equivalents return the `status` field from the perspective of the transaction (not the QR). A paid QR appears as `status: "settled"` in the query, with `type: "pix"` (or `type: "pix_qrcode"` in a short window post-settlement while the final row in `transactions` is still being persisted). See [Query Cash-In by ID](/en/pix-cashin-status#possible-statuses) for the full table.
:::
