# Quickstart

From nothing to a collected payment in about five minutes, entirely in sandbox.

**1. Get a sandbox API key**

Create an account in the [BaxPay Business dashboard](https://business.baxpay.com) and, on the
**Developers** page, create a `bx_test_` key. Copy it now — it is shown once. The key is the
mode: everything it touches is sandbox data, and no real money can move.

**2. Create a payment link**

One call: an amount and a description. You get back a hosted page URL.

**3. Pay it with a test card**

Open the URL and pay with <code>4242 4242 4242 4242</code>, or watch
<code>4000 0000 0000 0002</code> get declined.

**4. See the money land**

Retrieve the link (now <code>PAID</code>) — or better, receive the
<code>payment_link.paid</code> webhook.

## 1 · Create a payment link

Charge €47.30 for a haircut. Three things to notice:

- **`Idempotency-Key`** is required on every call that creates or moves money. Retrying with the same
  key can never create a second link.
- **`businessWide: true`** takes the payment for the whole business. To book it to one location
  instead, send that location's `storeId`. One of the two is required.
- **`amount`** is in euros (`47.30`), not cents.

```bash
curl -X POST https://sandbox-api.baxpay.com/v1/payment-links \
  -H "Authorization: Bearer $BAXPAY_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "description": "Haircut — 16 July", "amount": 47.30, "businessWide": true }'
```

```js
const res = await fetch('https://sandbox-api.baxpay.com/v1/payment-links', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.BAXPAY_API_KEY}`,
    'Idempotency-Key': crypto.randomUUID(),
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ description: 'Haircut — 16 July', amount: 47.3, businessWide: true }),
});
const body = await res.json();
if (!res.ok) throw new Error(JSON.stringify(body));

console.log(JSON.stringify(body, null, 2)); // the hosted page is body.data.url
```

```python
import json, os, uuid, requests

res = requests.post(
    "https://sandbox-api.baxpay.com/v1/payment-links",
    headers={
        "Authorization": f"Bearer {os.environ['BAXPAY_API_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={"description": "Haircut — 16 July", "amount": 47.30, "businessWide": True},
)
res.raise_for_status()

print(json.dumps(res.json(), indent=2))  # the hosted page is ["data"]["url"]
```

Every response is wrapped: the link itself is in `data`.

```json
{
  "success": true,
  "data": {
    "id": "1c9f4a2e-…",
    "token": "plk_4f1f0c4c4e1a…",
    "url": "https://pay.baxpay.com/plk_4f1f0c4c4e1a…",
    "description": "Haircut — 16 July",
    "amount": 47.3,
    "feeAmount": 1.47,
    "chargeAmount": 47.3,
    "netAmount": 45.83,
    "currency": "EUR",
    "feePayer": "RECEIVER",
    "status": "OPEN",
    "livemode": false,
    "paidAt": null
  },
  "timestamp": "2026-07-16T14:02:10.000Z",
  "path": "/v1/payment-links"
}
```

`chargeAmount` is what the customer pays and `netAmount` is what you receive after BaxPay's fee
(`feeAmount`). With the default `feePayer: "RECEIVER"`, you absorb the fee.

## 2 · Pay it

Open `data.url` in a browser. You'll see the hosted page your customers see: your business name,
the description, the amount and a **test mode** banner. Pay by card with `4242 4242 4242 4242`
(any future expiry, any CVC); `4000 0000 0000 0002` is always declined. The page flips to a
receipt.

In sandbox you can also pay from a terminal. This is the call the hosted page makes:

```bash
curl -X POST https://sandbox-api.baxpay.com/v1/pay/link/plk_4f1f0c4c4e1a…/card \
  -H "Content-Type: application/json" \
  -d '{ "number": "4242 4242 4242 4242", "expMonth": 12, "expYear": 2030, "cvc": "123", "holder": "MARIE DUPONT" }'
```

```json
{ "success": true, "data": { "status": "PAID", "maskedInstrument": "•••• 4242" } }
```

## 3 · Confirm the money landed

If you registered a [webhook](https://developers.baxpay.com/webhooks) for `payment_link.paid`, BaxPay has already pushed this to
your endpoint, signed:

```json
{
  "id": "f017738f-…",
  "type": "payment_link.paid",
  "livemode": false,
  "data": {
    "paymentLinkId": "1c9f4a2e-…",
    "description": "Haircut — 16 July",
    "amount": 47.3,
    "currency": "EUR",
    "method": "CARD_NOT_PRESENT",
    "transactionRef": "be294a82-…"
  },
  "createdAt": "2026-07-16T14:03:21.530Z"
}
```

Or retrieve the link:

```bash
curl https://sandbox-api.baxpay.com/v1/payment-links/1c9f4a2e-… \
  -H "Authorization: Bearer $BAXPAY_API_KEY"
```

```js
const res = await fetch('https://sandbox-api.baxpay.com/v1/payment-links/1c9f4a2e-…', {
  headers: { Authorization: `Bearer ${process.env.BAXPAY_API_KEY}` },
});
console.log(JSON.stringify(await res.json(), null, 2));
```

```python
import json, os, requests

res = requests.get(
    "https://sandbox-api.baxpay.com/v1/payment-links/1c9f4a2e-…",
    headers={"Authorization": f"Bearer {os.environ['BAXPAY_API_KEY']}"},
)
res.raise_for_status()
print(json.dumps(res.json(), indent=2))
```

```json
{
  "success": true,
  "data": {
    "id": "1c9f4a2e-…",
    "status": "PAID",
    "paidAt": "2026-07-16T14:03:21.512Z",
    "paidMethod": "CARD_NOT_PRESENT",
    "paidTransactionId": "be294a82-…"
  }
}
```

Polling is fine for a quickstart. In production, subscribe to [webhooks](https://developers.baxpay.com/webhooks) instead.

## Where to go next

- [Payment links](https://developers.baxpay.com/payment-links):
  Expiry, references, clients, cancellation — the full resource.
- [E-invoicing (France)](https://developers.baxpay.com/einvoicing):
  Regulated French e-invoices with the same one-call collection.
- [In-store](https://developers.baxpay.com/terminal-api):
  Sessions for tills, pumps and kiosks — BaxPay terminals handle the payment itself.
- [Errors](https://developers.baxpay.com/errors):
  Status codes, the error shape, and how to retry safely.
