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

# Authentication

> Generate API keys and HMAC-sign partner API requests.

Create API keys in the [partner portal](https://dashboard.liquidramp.com) after you sign up and complete KYB.

| Credential         | Prefix                      | Where to use it                                                  | Signing       |
| ------------------ | --------------------------- | ---------------------------------------------------------------- | ------------- |
| **Public key**     | `pk_test_*` / `pk_live_*`   | Read requests (reference data, rate preview)                     | None          |
| **Secret key**     | `sk_test_*` / `sk_live_*`   | **Backend only** — reads and writes (quotes, orders, fulfilment) | HMAC required |
| **Encryption key** | `enc_test_*` / `enc_live_*` | HMAC signing — never sent as Bearer                              | —             |

Your **partner reference** (`liquidramp-client-id`) is shown in the portal when you generate keys. Test hosts issue `*_test_*` keys; production issues `*_live_*`. See [Sandbox vs production](/getting-started/sandbox-vs-production).

<Warning>
  The full `secret_key` and `encryption_key` are shown **once** when you create keys. Store them in a secrets manager before you leave the page. Creating a new key set **revokes** the previous set for your partner.
</Warning>

* `public_key` → `Authorization: Bearer …` on read endpoints (no HMAC)
* `secret_key` → `Authorization: Bearer …` on your **backend** for reads and writes (HMAC required)
* `encryption_key` → HMAC signing only (never a request header)
* Partner reference → `liquidramp-client-id`

```mermaid theme={null}
sequenceDiagram
    participant Client as Your backend
    participant API as Liquidramp API

    Client->>Client: Build canonical string
    Note over Client: clientId|timestamp|METHOD|path|rawBody
    Client->>Client: HMAC-SHA256 with enc_* key
    Client->>API: Request + headers
    Note over Client,API: Authorization Bearer sk_*<br/>liquidramp-client-id<br/>liquidramp-timestamp<br/>liquidramp-signature
    alt Invalid key, signature, or IP
        API-->>Client: 401 or 403
    else Valid
        API-->>Client: 200 + data
    end
```

`pk_*` requests do not require HMAC. Every `sk_*` request — including reads — requires `liquidramp-timestamp` and `liquidramp-signature`. Sign with `enc_*`, not with the secret key.

## Public key (`pk_*`)

Use the public key for read routes such as `GET /v1/institutions`, `GET /v1/networks`, and `GET /v1/exchange-rate`. You can call the same reads with `sk_*` from your backend instead (with HMAC).

### Required headers

| Header                 | Value                                    |
| ---------------------- | ---------------------------------------- |
| `liquidramp-client-id` | Partner reference (e.g. `LR-ABC123`)     |
| `Authorization`        | `Bearer pk_test_…` or `Bearer pk_live_…` |

```bash theme={null}
curl https://vibe-api.liquidramp.com/v1/institutions \
  -H "liquidramp-client-id: LR-ABC123" \
  -H "Authorization: Bearer pk_test_a1b2c3d4e5f67890"
```

## Secret key (`sk_*`)

Use the secret key **only on your backend**. It can perform read requests and all write routes (quotes, orders, fulfilments). Never embed `sk_*` in a mobile app, browser, or other client-side code.

### Required headers

| Header                 | Value                                    |
| ---------------------- | ---------------------------------------- |
| `liquidramp-client-id` | Partner reference                        |
| `Authorization`        | `Bearer sk_test_…` or `Bearer sk_live_…` |
| `liquidramp-timestamp` | Unix time in **milliseconds** (string)   |
| `liquidramp-signature` | `sha256=<hmac_sha256_hex>`               |

Sign with your **`enc_*` encryption key**.

### Canonical string

```
clientId|timestamp|METHOD|path|rawBody
```

| Segment     | Rule                                                                            |
| ----------- | ------------------------------------------------------------------------------- |
| `clientId`  | Same value as `liquidramp-client-id`                                            |
| `timestamp` | `liquidramp-timestamp` header                                                   |
| `METHOD`    | Uppercase HTTP method                                                           |
| `path`      | Full path including `/v1` and query string (e.g. `/v1/orders?status=completed`) |
| `rawBody`   | Exact request body string; empty string when there is no body                   |

### Node.js signing example

```javascript theme={null}
import crypto from "node:crypto";

function signRequest({ clientId, encryptionKey, timestamp, method, path, body = "" }) {
  const canonical = [clientId, timestamp, method.toUpperCase(), path, body].join("|");
  const hash = crypto.createHmac("sha256", encryptionKey).update(canonical).digest("hex");
  return `sha256=${hash}`;
}

const timestamp = Date.now().toString();
const signature = signRequest({
  clientId: "LR-ABC123",
  encryptionKey: process.env.LIQUIDRAMP_ENC_KEY,
  timestamp,
  method: "POST",
  path: "/v1/orders",
  body: JSON.stringify({ /* order payload */ }),
});
```

## Common mistakes

* Signing with `sk_*` instead of `enc_*`
* Omitting `/v1` or the query string from `path`
* Re-serializing JSON (key order changes the body)
* Using seconds instead of milliseconds for the timestamp
* Sending `sk_*` or `enc_*` from a browser or mobile app

For outbound webhook verification, see [Webhook verification](/guides/webhook-verification).

## Key security

* Store keys in a secrets manager — never commit them to source control.
* Keep `sk_*` and `enc_*` on the backend only. Treat `pk_*` as sensitive as well.
* Rotate keys in the portal if credentials may have leaked. Rotate `enc_*` and `sk_*` together.

## IP whitelist

If your partner profile has an IP whitelist, requests from other IPs receive `403` with code `E_IP_BLOCKED`. Manage the whitelist in the portal.

## Error responses

Authentication failures typically return:

```json theme={null}
{
  "status": false,
  "code": "E_UNAUTHORIZED",
  "message": "Invalid credentials"
}
```

Some auth failures use `{ "status": "failed", "message": "...", "data": null }` with HTTP 401 or 403.

## Next steps

* [Sandbox vs production](/getting-started/sandbox-vs-production)
* [Onramp quickstart](/quickstarts/onramp)
* [Webhook verification](/guides/webhook-verification)
