# Checkout API

Take a one-off payment. Create a checkout session for an amount, send the customer to the hosted page, and read the result.

The Checkout API is a small REST surface for taking a single payment. You create a **checkout session** for an amount, Payer returns a hosted payment page, the customer pays there, and you read the session's status — or let a [webhook](/developers/docs/webhooks) tell you — to know when it's done.

All endpoints live under `/checkout` and authenticate with a [publishable or secret key](/developers/docs/authentication) holding the `checkout:write` scope.

## Create a session

```http title="POST /checkout/sessions"
POST /checkout/sessions
```

Creates a checkout session and returns a hosted payment link. Send an `Idempotency-Key` so a retried create replays the first response instead of making a second session. Requires the `checkout:write` scope.

### Body parameters

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `amount` | integer | Yes | Amount in **laari** (100 laari = 1 MVR). Between `100` and `500000`. |
| `title` | string | Yes | Shown on the checkout page. Max 255 characters. |
| `reference` | string | Yes | Your own identifier. Must be unique for your account. |
| `description` | string | No | Extra line under the title. Max 255 characters. |
| `return_url` | string (url) | No | Where the payer is sent after the session reaches a terminal state. |

```bash title="Request"
curl https://api.payer.app/checkout/sessions \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: order_10482" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 2490,
    "title": "Order #10482",
    "reference": "order_10482",
    "return_url": "https://example.com/thanks"
  }'
```

```json title="200 OK"
{
  "id": "9b1c8f2e-3d4a-4f11-a0c2-1e2d3c4b5a69",
  "reference": "order_10482",
  "url": "https://api.payer.app/9b1c8f2e-3d4a-4f11-a0c2-1e2d3c4b5a69"
}
```

Redirect the customer to `url`. A session expires 24 hours after it's created if it hasn't been paid.

## Retrieve a session

```http title="GET /checkout/sessions/{id}"
GET /checkout/sessions/{id}
```

Returns the current state of a session. The `{id}` may be the session UUID **or** the `reference` you set when creating it.

```bash title="Request"
curl https://api.payer.app/checkout/sessions/9b1c8f2e-3d4a-4f11-a0c2-1e2d3c4b5a69 \
  -H "Authorization: Bearer sk_test_..."
```

```json title="200 OK"
{
  "id": "9b1c8f2e-3d4a-4f11-a0c2-1e2d3c4b5a69",
  "reference": "order_10482",
  "status": "COMPLETED"
}
```

`status` is one of:

| Status | Meaning |
| --- | --- |
| `INITIATED` | Created, not yet paid. |
| `COMPLETED` | Paid and settled. |
| `CANCELLED` | Cancelled before payment. |
| `EXPIRED` | The 24-hour window elapsed unpaid. |

> [!TIP]
> Don't poll in a tight loop. Register a [webhook](/developers/docs/webhooks) for `checkout.session.completed` and read the session once when it arrives.

## Cancel a session

```http title="POST /checkout/sessions/{id}/cancel"
POST /checkout/sessions/{id}/cancel
```

Cancels a session that hasn't been paid. Requires the `checkout:write` scope. Send an `Idempotency-Key` so a retried cancel is safe.

```bash title="Request"
curl -X POST https://api.payer.app/checkout/sessions/9b1c8f2e-.../cancel \
  -H "Authorization: Bearer sk_test_..." \
  -H "Idempotency-Key: cancel_order_10482"
```

```json title="200 OK"
{ "message": "Checkout session cancelled" }
```

A session that is already cancelled, or already in another terminal state, returns `400`:

```json title="400 Bad Request"
{ "message": "Checkout session already cancelled" }
```

## Read a session over GraphQL

The session is also readable at `POST /graphql`, and this query is **public** — the UUID is the capability, so no key is needed. This is the same data the hosted checkout page loads.

```graphql title="Query"
query CheckoutSession($id: UUID!) {
  checkoutSession(id: $id) {
    id
    merchantName
    reference
    title
    description
    amount
    currency
    status
    expiresAt
    returnUrl
  }
}
```

> [!NOTE]
> Over GraphQL, `amount` is in **major MVR units** (`24.90`), not laari — this query drives the customer-facing page. `returnUrl` is only exposed once the session reaches a terminal state.

## Try it live

The panel below runs real requests from your browser straight to `api.payer.app`. Paste an `sk_test_` key, edit the inputs, and send. Nothing is proxied through this site, and the key stays in this browser tab.
