# Authentication

How to authenticate with the AuxCORE Public API and dashboard.

---

## Overview

| Integration type | Auth |
|------------------|------|
| **Server / ERP** | `x-api-key` on `/api/v1/public/*` (`X-Merchant-ID` too for ERP keys) |
| **Website embed (SDK)** | `lpk_live_…` public key in `data-api-key` — no secret in HTML |
| **Dashboard admin** | JWT via login or `POST /api/v1/public/accesstoken` |

---

## API key types

Create keys at **Dashboard → Settings → Developer & Integrations → API Keys**:

| Type | Prefix | Use |
|------|--------|-----|
| **Secret** | `api-…` | Server-side REST (`x-api-key` header) |
| **SDK embed** | `lpk_live_…` | Client-side `auxVault.js` only |
| **ERP** | `lv_erp_…` | Tenant-wide ERP (send `X-Merchant-ID` per call) |
| **Processing** | `lv_pk_…` | Per-merchant, per-gateway payment key (created by an administrator) |
| **Token processing** | `ltk_…` | Server-side PCI token processing on the PCI server; requires an allow-list of server IPs (created by an administrator) |

Merchant users can create **Secret** and **SDK embed** keys for their own merchant (and its locations); the other types need an administrator. Secret keys are shown **once**; SDK embed keys can be viewed again on `/settings/developer/api-keys`.

**Tenant ID** is a slug like `auxvault-prod` or a UUID — visible in Settings.

---

## Public API headers

Every **server** request to `/api/v1/public/*` (except customer-facing payment-link pages) needs:

```http
x-api-key: YOUR_SECRET_API_KEY
Content-Type: application/json
```

- `x-api-key` is the primary header. The gateway also accepts `api-key`, `Authorization: <key>` / `Authorization: Bearer <key>`, or `apiKey` / `api_key` in the JSON body, for older plugins.
- Merchant-scoped keys resolve tenant and merchant from the key. `X-Tenant-ID` is optional and ignored on `/api/v1/public/*` — the key's tenant always wins. (On Admin API routes an `X-Tenant-ID` you send must be valid, or the request fails with `404 TENANT_NOT_FOUND`.)
- ERP / tenant-scoped keys (`lv_erp_…`) take the merchant from `X-Merchant-ID`, or `merchantId` in the body or query string.
- SDK embed keys (`lpk_live_…`) are for the browser checkout SDK only — never for plugins or server code. Every request must carry an `Origin` or `X-Merchant-Origin` header that is on the key's allowed origins (the dashboard requires at least one).

### API key errors

These are returned by `/api/v1/public/*` before the request reaches a service. Note that `error` is a **string** here:

| Status | `error` | Cause |
|--------|---------|-------|
| 401 | `API key required` | No key in any accepted location |
| 401 | `API key validation failed` | Key unknown, deactivated or mistyped — this is what an invalid key returns today. Also returned if the key service is unreachable |
| 401 | `Invalid API key` | Key rejected without an error status (rare) |
| 403 | `Origin required` | SDK key with allowed origins, no `Origin` / `X-Merchant-Origin` header |
| 403 | `Origin not allowed` | Origin not on the SDK key's allow-list |
| 403 | `Insufficient permissions` | SDK key used on an internal/admin path |

```json
{
  "success": false,
  "error": "API key validation failed",
  "message": "Unable to validate API key. Please try again."
}
```

---

## Step 1: Test connection

```bash
curl -X POST https://uat.auxcore.net/api/v1/public/testConnection \
  -H "x-api-key: YOUR_API_KEY" \
  -H "X-Tenant-ID: YOUR_TENANT_ID"
```

---

## Step 2: Process a payment

Card data never goes to this endpoint directly: collect it with the PCI SDK (`https://vault.auxcore.net/sdk/`) or `POST /api/v1/public/tokenize-card` and send the returned `pciToken`. Raw `CardNumber` / `CVV` is rejected on Sandbox and Production with `400 PCI_SCOPE_VIOLATION`.

```bash
curl -X POST https://uat.auxcore.net/api/v1/public/transaction \
  -H "x-api-key: YOUR_API_KEY" \
  -H "X-Tenant-ID: YOUR_TENANT_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "Amount": 10.00,
    "SuggestedMode": "Card",
    "pciToken": "pci_tok_8f2c1a9b7e4d",
    "BillingCustomerName": "Test Customer"
  }'
```

---

## Create API keys (dashboard)

1. Log in to the dashboard (Sandbox or Production host)
2. Go to **Settings → Developer & Integrations → API Keys**
3. Click **New Key** and pick what you are connecting: **WooCommerce plugin** or **Your server or backend** create a Secret key (`api-…`); **Website checkout (No-Code SDK)** creates an SDK Embed key (`lpk_live_…`) and asks for the allowed origins
4. Copy the value — secret keys are shown **once**

---

## JWT for dashboard operations

To obtain a JWT for Admin API routes (`/api/v1/*`), call accesstoken with **both** your API key and user credentials. The user is looked up in the API key's tenant.

### Get an access token

```http
POST /api/v1/public/accesstoken
```

| Body field | Type | Required | Description |
|------------|------|----------|-------------|
| `email` | string | Yes | User email (must be a valid email) |
| `password` | string | Yes | User password |
| `otpCode` | string | No | MFA code, on the second call when MFA is required |
| `deviceId` | string | No | Device identifier, used for trusted-device MFA |
| `trustDevice` | boolean | No | With `deviceId`, skip MFA on this device next time |

```bash
curl -X POST https://uat.auxcore.net/api/v1/public/accesstoken \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "admin@yourcompany.com",
    "password": "your-password"
  }'
```

```json
{
  "success": true,
  "data": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "3f9c2b7e0d...",
    "tokenType": "Bearer",
    "expiresIn": 86400,
    "user": {
      "id": "550e8400-e29b-41d4-a716-446655440010",
      "email": "admin@yourcompany.com",
      "fullName": "Jane Admin",
      "role": "merchant_owner",
      "merchantId": "550e8400-e29b-41d4-a716-446655440000",
      "mfaEnabled": false
    },
    "features": { "invoicing": true },
    "navigation": []
  }
}
```

- `expiresIn` is in seconds (24 hours by default). Refresh tokens last 30 days.
- If MFA applies, the first call returns `{ "mfaRequired": true, "mfaMethod": "email", "message": "...", "userId": "..." }` instead of a token; repeat the call with `otpCode`.
- If the user belongs to several merchants, it returns `{ "merchantSelectionRequired": true, "selectionToken": "...", "merchants": [...] }` — see Multi-merchant sessions below.
- Wrong credentials return `401 UNAUTHORIZED` (`Invalid credentials`). After 10 failed attempts for one account in 15 minutes the call returns `429 RATE_LIMITED`.

### Refresh an access token

```http
POST /api/v1/public/refreshToken
```

```bash
curl -X POST https://uat.auxcore.net/api/v1/public/refreshToken \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "refreshToken": "YOUR_REFRESH_TOKEN" }'
```

Returns `{ "success": true, "data": { "token", "refreshToken", "tokenType": "Bearer", "expiresIn" } }`. Refresh tokens are single-use: store the new `refreshToken` every time. Presenting an already-used refresh token returns `401` and signs out all of that user's sessions. A missing `refreshToken` returns `400 VALIDATION_ERROR`.

### Use the JWT on admin routes

```http
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

The tenant comes from the token. An expired token returns `401 TOKEN_EXPIRED`; a malformed or forged one returns `401 INVALID_TOKEN`.

Admin routes also accept a secret or ERP API key in `x-api-key` instead of a JWT. SDK embed keys are refused there with `403 FORBIDDEN`.

---

## SDK client tokens

Short-lived tokens for client-side code that should not hold an API key.

### Create a client token

```http
POST /api/v1/public/client-token
```

Requires `x-api-key`. The merchant is taken from the key (or `X-Merchant-ID` for ERP keys); no body is needed.

```json
{
  "success": true,
  "data": {
    "clientToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expiresIn": 900,
    "type": "client_token"
  }
}
```

### Validate a client token

```http
POST /api/v1/public/validate-client-token
```

No API key required. Body: `{ "token": "<clientToken>" }`.

```json
{
  "success": true,
  "data": {
    "valid": true,
    "tenantId": "your-tenant",
    "merchantId": "550e8400-e29b-41d4-a716-446655440000",
    "expiresAt": "2026-08-10T09:29:02.000Z"
  }
}
```

- `400 VALIDATION_ERROR` — `token` missing.
- `401 TOKEN_EXPIRED` — invalid or expired (`data.valid` is `false`).
- `401 INVALID_TOKEN` — a valid JWT that is not a client token.

The four token endpoints (`accesstoken`, `refreshToken`, `client-token`, `validate-client-token`) share one limit of 30 requests per 15 minutes per IP with `field-config` and the checkout-session endpoints; over it they return `429 RATE_LIMITED`.

---

## Multi-merchant sessions

A user can belong to several merchants. The access token carries exactly one `merchantId`, and every
merchant-scoped call is answered for that merchant.

**At login.** When the user belongs to more than one merchant, `POST /api/v1/auth/login` returns
`{ merchantSelectionRequired: true, selectionToken, merchants[] }` instead of a token. Complete the
login with `POST /api/v1/auth/select-merchant` (body `{ selectionToken, merchantId }`) — the
`selectionToken` is valid for 10 minutes.

**After login.** Use these two routes; both require `Authorization: Bearer <jwt>` and `X-Tenant-ID`.

### `GET /api/v1/auth/my-merchants`

Merchants the signed-in user can work in.

| Query | Default | Description |
|-------|---------|-------------|
| `includeDeviceStatus` | `true` | Set `false` to skip the per-merchant device lookup |

```json
{
  "success": true,
  "data": {
    "currentMerchantId": "merchant_123",
    "merchants": [
      {
        "merchantId": "merchant_123",
        "companyName": "Acme Coffee",
        "role": "merchant_admin",
        "status": "active",
        "deviceRegistration": {
          "hasActiveDevice": true,
          "activeDeviceCount": 1,
          "device": {
            "deviceId": "550e8400-e29b-41d4-a716-446655440001",
            "deviceLabel": "Counter 1",
            "tpnLast4": "4651",
            "status": "ACTIVE",
            "lastActiveAt": "2026-08-10T09:14:02.000Z"
          }
        }
      }
    ]
  }
}
```

`deviceRegistration` reports whether **this user** already has an `ACTIVE` device registered at that
merchant — devices registered by other staff never count. It is a display hint for POS/mobile apps;
[`GET /api/v1/device/session`](../mobile/device-integration.md#check-device-session) decides whether a
stored device token still works.

### `POST /api/v1/auth/switch-merchant`

Switch the active merchant without logging out. Body: `{ "merchantId": "merchant_456" }`.

Returns the same shape as `select-merchant`: a new `token`, `refreshToken`, `user`, `features` and
`navigation`, scoped to the new merchant. Replace the stored access token with it.

- `401` — the user has no access to that merchant, or the merchant is closed.
- `400` — `merchantId` missing.

### Refresh keeps the selected merchant

`POST /api/v1/auth/refresh` re-issues the token for the merchant the session was scoped to, and
`GET /api/v1/auth/me` reports that same merchant. Refreshing no longer reverts a multi-merchant user
to their primary merchant.

---

## Authentication summary

| Use case | Headers |
|----------|---------|
| Public API (ERP, plugins) | `x-api-key` (+ `X-Merchant-ID` for ERP keys) |
| SDK embed in browser | `data-api-key` on script tag only |
| Admin API | `Authorization: Bearer JWT` (or `x-api-key` with a secret/ERP key) |
| accesstoken / refreshToken / client-token | `x-api-key`, JSON body |
| validate-client-token | JSON body only |
| my-merchants / switch-merchant | `Authorization: Bearer JWT`, `X-Tenant-ID` |

---

## Customer-facing routes (no API key)

These are called from hosted pay pages in the browser — **no** `x-api-key`:

- `GET /api/v1/public/payment-links/:linkId`
- `POST /api/v1/public/payment-links/:linkId/pay`
- `GET /api/v1/public/invoices/link/:linkId`
- `POST /api/v1/public/checkout-sessions/:sessionId/pay`

---

## Security

- Never commit API keys to git
- Never put `api-…` secret keys in frontend JavaScript
- Restrict SDK public keys with **allowed origins** on `/settings/developer/api-keys`
- Rotate keys after staff changes

---

**Need help?** Contact support@auxcore.net
