# Authentication

Two credentials, one rule each: API keys for your servers, connection credentials for your machines.

Everything a developer calls on BaxPay authenticates with one of two credentials, both sent the
same way — as a Bearer token:

| Credential | Prefix | Who uses it | Typical calls |
| --- | --- | --- | --- |
| **API key** | `bx_test_…` / `bx_live_…` | your backend servers | e-invoicing, clients, sessions overview, webhooks data |
| **Connection credential** | `bxc_…` | a till, pump, or kiosk | opening and driving in-store payment sessions |

Accounting OAuth (Pennylane / QuickBooks / Sage) does **not** use a merchant-supplied `bxc_` for
vendor login — merchants approve BaxPay from **Developers → Accounting**. See
[Accounting & ERP sync](https://developers.baxpay.com/accounting).

## API keys

Create keys in the [Business dashboard](https://business.baxpay.com) under **Developers**. Every key
is pinned to your business **and** to a mode: a `bx_test_` key can only ever read and write
sandbox data, a `bx_live_` key only live data. There is no header or flag to switch — the key
*is* the mode (see [Sandbox & test mode](https://developers.baxpay.com/sandbox)). Call `https://sandbox-api.baxpay.com/v1` with
test keys and `https://api.baxpay.com/v1` with live keys.

```bash
curl https://sandbox-api.baxpay.com/v1/einvoices \
  -H "Authorization: Bearer bx_test_9f2a4c8e1d…"
```

```js
// One small helper for every call: base URL, key, JSON, and the `data` envelope.
async function baxpay(path, { method = 'GET', body, idempotencyKey } = {}) {
  const res = await fetch(`https://sandbox-api.baxpay.com/v1${path}`, {
    method,
    headers: {
      Authorization: `Bearer ${process.env.BAXPAY_API_KEY}`,
      ...(body && { 'Content-Type': 'application/json' }),
      ...(idempotencyKey && { 'Idempotency-Key': idempotencyKey }),
    },
    body: body && JSON.stringify(body),
  });
  const json = await res.json();
  if (!res.ok) throw new Error(`${res.status} ${JSON.stringify(json.message)}`);
  return json.data;
}

const invoices = await baxpay('/einvoices');
console.log(JSON.stringify(invoices, null, 2));
```

### Check a key

`GET /ping` tells you which business and which mode a key belongs to. It's the quickest
way to confirm a key works before writing anything else.

```bash
curl https://sandbox-api.baxpay.com/v1/ping \
  -H "Authorization: Bearer $BAXPAY_API_KEY"
```

```js
const res = await fetch('https://sandbox-api.baxpay.com/v1/ping', {
  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/ping",
    headers={"Authorization": f"Bearer {os.environ['BAXPAY_API_KEY']}"},
)
res.raise_for_status()
print(json.dumps(res.json(), indent=2))
```

```json
{
  "success": true,
  "data": {
    "ok": true,
    "livemode": false,
    "merchantId": "6f1c2a9e-…"
  },
  "timestamp": "2026-11-06T10:42:00.000Z",
  "path": "/v1/ping"
}
```

Every response is wrapped like this: `data` holds the result, and `success`, `timestamp` and `path`
describe the call.

> **Shown once, stored hashed**
> The full key is displayed exactly once, at creation. BaxPay stores only a hash and genuinely
> cannot show it to you again — if a key is lost or leaked, revoke it in the dashboard and create
> a new one. Rotation is cheap; keep it that way by never hard-coding keys.

> **Server-side only**
> API keys grant full access to your business's data in their mode. They belong in your backend's
> secret store — never in a browser, a mobile app, or a public repository.

## Connection credentials

In-store systems don't use your API key. Each till or pump lane gets its own `bxc_` credential,
provisioned from **Developers → Connections** or the [connections API](https://developers.baxpay.com/connections).
(Accounting ERP OAuth is separate — [Accounting](https://developers.baxpay.com/accounting).)

```bash
Authorization: Bearer bxc_q7Vd2kP9xR4mT1sN…
```

Scope them narrowly. One credential per site or lane costs nothing and means a compromise at one
till doesn't hand an attacker the whole estate — revoking it disturbs exactly one machine.

Like API keys, connection credentials are shown once at creation and stored hashed. A credential has no mode in its prefix: it belongs to the mode of the key or dashboard that created it, and only ever opens sessions in that mode.

> **No user logins in the API**
> Dashboard sign-in (email, password, 2FA) is how *people* access BaxPay — it is not part of the
> developer API and your integration never handles anyone's BaxPay password. If a flow seems to
> need a user to act (approving a payment on their phone, paying an invoice with their account),
> BaxPay's own surfaces handle that interaction and your integration just observes the outcome —
> usually through a [webhook](https://developers.baxpay.com/webhooks).
