# Authentication

> How to authenticate API requests to Nexpay.

Nexpay accepts two authentication schemes, depending on who is calling the API:

- **`X-API-Key`** — for server-to-server (M2M) traffic. This is what you'll use for almost every integration. See [API keys](/docs/api-keys.md) for how to issue, list, and revoke keys.
- **`Authorization: Bearer <token>`** — for browser sessions, where a human user has logged into the Nexpay Dashboard. Tokens are issued by `POST /v2/auth/login` and short-lived. Not typically used by integrations.

Requests without one of these headers, or with an invalid value, return `401`.

---

## Server-to-server (API key)

Combine your `clientId` and `secret` with a colon and send them on the `X-API-Key` header:

**cURL**

```bash
curl 'https://api.nexpay.com.au/v2/users/me' \
  -H 'X-API-Key: nxp_ck_your-client-id:nxp_sk_your-secret'
```

**JavaScript**

```js
const headers = {
  'Content-Type': 'application/json',
  'X-API-Key': `${process.env.NEXPAY_CLIENT_ID}:${process.env.NEXPAY_SECRET}`,
};

const response = await fetch('https://api.nexpay.com.au/v2/users/me', {
  method: 'GET',
  headers,
});

if (response.ok) {
  const { data } = await response.json();
  console.log(data.email);   // "api-key:nxp_ck_787f5b97469f65c4ca97da31"
  console.log(data.roles);   // ["APIKey"]
  console.log(data.tenantId);
} else {
  // 401 [AUT0001] — missing / wrong scheme
  // 401 [AUT0003] — present but invalid key (typo, revoked, or wrong format)
  throw new Error(`Auth failed: ${response.status}`);
}
```

**PHP (Laravel)**

```php
use Illuminate\Support\Facades\Http;

$response = Http::withHeaders([
    'X-API-Key' => 'nxp_ck_your-client-id:nxp_sk_your-secret',
])->get('https://api.nexpay.com.au/v2/users/me');

if ($response->successful()) {
    $data = $response->json('data');
    echo $data['email'];    // "api-key:nxp_ck_787f5b97469f65c4ca97da31"
    echo $data['roles'][0]; // "APIKey"
    echo $data['tenantId'];
} else {
    // 401 [AUT0001] — missing / wrong scheme
    // 401 [AUT0003] — present but invalid key (typo, revoked, or wrong format)
    throw new Exception("Auth failed: {$response->status()}");
}
```

The `clientId:secret` separator is a single colon. Don't URL-encode it, don't base64-encode it — send the raw pair.

---

## Browser sessions (Bearer token)

Browser apps that go through `POST /v2/auth/login` receive an encrypted, short-lived Bearer token. Subsequent requests include it on the `Authorization` header:

```js
const response = await fetch('https://api.nexpay.com.au/v2/users/me', {
  method: 'GET',
  headers: {
    'Authorization': `Bearer ${sessionToken}`,
  },
});
```

You'll only need this scheme if you're building against the Nexpay Dashboard's session model (for example, an admin tool that acts on behalf of a logged-in human). For machine integrations, use [API keys](/docs/api-keys.md).

---

## Sandbox vs production

Both base URLs accept the same auth schemes, but keys are environment-scoped:

- **Production** — `https://api.nexpay.com.au`
- **Sandbox** — `https://sandbox-api.nexpay.com.au`

A `clientId:secret` pair issued in sandbox **does not work** in production and vice-versa. Issue separate keys per environment from the matching dashboard.

---

## Identity types

A request authenticates as one of:

### Human user

A natural person who signed into the dashboard. Carries roles such as `Agent`, `Admin`, etc. Used for browser sessions and for issuing/revoking API keys.

### API key (shadow user)

Each API key is backed by a dedicated "shadow" user on the legacy platform. Calls authenticated via `X-API-Key` carry the role `APIKey` and the `tenantId` of the issuing organisation. Machine actions are attributed to the key (not the human who created it) in the audit log — `lastUsedAt` updates on every call.

API keys cannot create or revoke other API keys; only human-session users can.

---

## Roles and permissions

Permissions are derived from the user's roles and the tenant's payment policy. The effective set is published on `GET /v2/users/me` under `paymentAccess` / `organizationPaymentPolicy` — query this once during integration setup to discover what your key is authorised to do (for example, whether it can create split payments or run multi-payee quotes).

API keys do **not** automatically have super-admin access — their effective permissions follow the roles passed at creation time and the tenant policy. Confirm by inspecting the `paymentAccess` block on `/v2/users/me`.

---

## Authentication errors

| HTTP | Code | When it fires |
| --- | --- | --- |
| `401` | `[AUT0001]` | No auth header, or an unrecognised scheme. Verify you're sending `X-API-Key` (M2M) or `Authorization: Bearer` (session). |
| `401` | `[AUT0002]` | Bearer session expired. Re-authenticate via `POST /v2/auth/login`. |
| `401` | `[AUT0003]` | `X-API-Key` was present but invalid — wrong format (no colon, empty halves), revoked key, or typo. |

Error responses use the standard envelope: `{ statusCode, code, message, timestamp }`. See [Errors](/docs/errors.md).
