> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pontisglobe.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Every request is signed. Most bodies are encrypted. Here is exactly how.

## The four headers

Every request to the PontisGlobe API must carry these headers:

| Header          | Value                                                                       |
| --------------- | --------------------------------------------------------------------------- |
| `x-api-key`     | Your API key from Developer Tools                                           |
| `x-timestamp`   | Current Unix timestamp in **seconds**, as a string                          |
| `x-signature`   | `HMAC-SHA256(timestamp + "." + encrypted_body, HMAC_SECRET)`, hex-encoded   |
| `authorization` | `Bearer <JWT>` — required on every endpoint **except** `/api/v1/user/login` |

## The request envelope

The HTTP body is **always**:

```json theme={null}
{ "data": "<encrypted-blob>" }
```

The `data` field is your real payload, encrypted with AES-256-GCM using your **Encryption Secret**. The blob format is `iv:tag:ciphertext`, each component base64url-encoded:

```
9hMv3qB7E2x…:Z1jK9pA…:r6sL8nO2…
```

## Sign in 3 steps

<Steps>
  <Step title="Encrypt the body">
    AES-256-GCM with a random 12-byte IV. The key is your **Encryption Secret** (base64url, 32 bytes after decoding).

    ```js theme={null}
    const iv = randomBytes(12)
    const c = createCipheriv('aes-256-gcm', encKey, iv)
    const ct = Buffer.concat([c.update(JSON.stringify(payload), 'utf8'), c.final()])
    const enc = `${iv.toString('base64url')}:${c.getAuthTag().toString('base64url')}:${ct.toString('base64url')}`
    ```
  </Step>

  <Step title="Sign timestamp + encrypted body">
    HMAC-SHA256 over the literal string `"${timestamp}.${enc}"`, hex-encoded.

    ```js theme={null}
    const ts = String(Math.floor(Date.now() / 1000))
    const sig = createHmac('sha256', hmacKey).update(`${ts}.${enc}`).digest('hex')
    ```
  </Step>

  <Step title="Send">
    ```js theme={null}
    fetch(url, {
      method: 'POST',
      headers: {
        'content-type': 'application/json',
        'x-api-key': API_KEY,
        'x-timestamp': ts,
        'x-signature': sig,
        ...(jwt ? { authorization: `Bearer ${jwt}` } : {}),
      },
      body: JSON.stringify({ data: enc }),
    })
    ```
  </Step>
</Steps>

## The 15-minute JWT

`POST /api/v1/user/login` returns:

```json theme={null}
{
  "ok": true,
  "data": {
    "access_token": "eyJhbGciOi…",
    "token_type": "Bearer",
    "expires_in": 900
  }
}
```

Pass the `access_token` as `Authorization: Bearer <token>` on every endpoint that requires authentication. Tokens are bound to:

* Your **consumer account** (regenerating credentials invalidates active tokens immediately)
* The **mode** (a live JWT cannot be used in sandbox and vice versa)

Re-login when you receive `401 unauthorized` with code `jwt_expired`.

## Replay protection

We reject requests where `x-timestamp` drifts more than **5 minutes** from server time. Keep your client's clock in sync (NTP).

## Why all of this?

| Layer                        | Defeats                                      |
| ---------------------------- | -------------------------------------------- |
| AES-256-GCM body encryption  | Passive interception (even if TLS is broken) |
| HMAC-SHA256 signature        | Body tampering and request forgery           |
| Timestamp in signature scope | Replay attacks                               |
| Short-lived JWT              | Token leakage blast radius                   |
