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

# Authentication

> Three auth modes — API key for your backend, HMAC for agents, session JWT for the dashboard

The Relayer API supports **three authentication modes**. Which one you use depends on who is making the call.

<CardGroup cols={3}>
  <Card title="API Key" icon="key">
    **Caller**: your backend.
    Header: `Authorization: ApiKey rk_...`
  </Card>

  <Card title="Agent HMAC" icon="robot">
    **Caller**: an agent at runtime.
    Four headers, HMAC-SHA256 signed.
  </Card>

  <Card title="Session JWT" icon="user">
    **Caller**: browser session in the Relayer dashboard.
    Header: `Authorization: Bearer <jwt>`
  </Card>
</CardGroup>

For most B2B integrations you only need the **API Key** mode. Use HMAC if you're deploying an AI agent that calls the API at runtime. Session JWT is for the Relayer-hosted dashboard.

## 1. API Key (backend → Relayer)

This is the primary mode for any server-side integration. Your backend authenticates to Relayer with a long-lived API key tied to your workspace.

### Key format

```
rk_client_key_v1_<random>
```

Generate keys from the dashboard. You can issue multiple keys per workspace (e.g., one per environment), each with its own IP allowlist and scope.

### Making a request

Pass the key in the `Authorization` header with the `ApiKey` scheme:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X GET https://testnet.relayer.fi/v1/signing/wallets \
    -H "Authorization: ApiKey rk_client_key_v1_your_key_here" \
    -H "Content-Type: application/json"
  ```

  ```typescript Node.js theme={null}
  const response = await fetch(
    "https://testnet.relayer.fi/v1/signing/wallets",
    {
      method: "GET",
      headers: {
        Authorization: `ApiKey ${process.env.RELAYER_API_KEY}`,
        "Content-Type": "application/json",
      },
    },
  );
  ```

  ```python Python theme={null}
  import os, requests

  response = requests.get(
      "https://testnet.relayer.fi/v1/signing/wallets",
      headers={
          "Authorization": f"ApiKey {os.environ['RELAYER_API_KEY']}",
          "Content-Type": "application/json",
      },
  )
  ```
</CodeGroup>

<Warning>
  Keep your API key secret. Never expose it in frontend code, public repositories, or client-side JavaScript. Store it in environment variables or your platform's secret manager.
</Warning>

### Scopes

API keys have scopes that limit what they can call:

| Scope              | What it accesses                                                        |
| ------------------ | ----------------------------------------------------------------------- |
| `integrator`       | Standard SDK-grade endpoints (signing, payouts, agents, widgets)        |
| `integrator:read`  | Read-only subset                                                        |
| `integrator:write` | Write-only subset                                                       |
| `internal`         | Workspace-management endpoints (issued to Relayer's own dashboard only) |

If you receive `403 Forbidden — Insufficient permissions`, your key's scope doesn't cover the endpoint you called.

***

## 2. Agent HMAC (agent SDK → Relayer)

Agents authenticate with a **per-agent secret** that is provisioned at agent-creation time. The Relayer Agent SDK (`@relayerfi/agent-sdk`) signs every request automatically — you should not handcraft these headers unless you're writing your own client.

### Headers

Every agent request must include:

<ParamField header="x-agent-id" type="string" required>
  The agent's UUID (issued by `POST /v1/agents/confirm-policies`).
</ParamField>

<ParamField header="x-agent-auth" type="string (hex)" required>
  The HMAC-SHA256 signature of the canonical request, hex-encoded.
</ParamField>

<ParamField header="x-request-timestamp" type="string" required>
  Unix epoch seconds. Must be within **±60 seconds** of server time.
</ParamField>

<ParamField header="x-sdk-version" type="string">
  Optional. SDK version string (e.g. `@relayerfi/agent-sdk@0.4.1`) for telemetry.
</ParamField>

### Signature payload

The signed string is:

```
${METHOD}${path}${timestamp}${sha256(body)}
```

* `METHOD` — uppercase HTTP method (`POST`, `GET`, …)
* `path` — request path including `/v1` prefix (e.g. `/v1/agents/{id}/x402-pay`)
* `timestamp` — same value as `x-request-timestamp`
* `sha256(body)` — hex-encoded SHA-256 of the JSON body. Use `sha256('')` for empty bodies.

Sign with HMAC-SHA256 using the agent secret. Result is the value of `x-agent-auth`.

### Reference implementation (for client authors)

<CodeGroup>
  ```typescript Node.js theme={null}
  import crypto from "node:crypto";

  function buildAgentAuthHeaders(
    agentId: string,
    agentSecret: string,
    method: string,
    path: string,
    body?: unknown,
  ) {
    const timestamp = Math.floor(Date.now() / 1000).toString();
    const bodyHash = crypto
      .createHash("sha256")
      .update(body && Object.keys(body).length > 0 ? JSON.stringify(body) : "")
      .digest("hex");
    const payload = `${method.toUpperCase()}${path}${timestamp}${bodyHash}`;
    const signature = crypto.createHmac("sha256", agentSecret).update(payload).digest("hex");
    return {
      "x-agent-id": agentId,
      "x-agent-auth": signature,
      "x-request-timestamp": timestamp,
      "x-sdk-version": "custom/1.0",
    };
  }
  ```

  ```python Python theme={null}
  import hashlib, hmac, json, time

  def build_agent_auth_headers(agent_id: str, agent_secret: str, method: str, path: str, body: dict | None):
      timestamp = str(int(time.time()))
      body_str = json.dumps(body) if body else ""
      body_hash = hashlib.sha256(body_str.encode()).hexdigest()
      payload = f"{method.upper()}{path}{timestamp}{body_hash}"
      signature = hmac.new(agent_secret.encode(), payload.encode(), hashlib.sha256).hexdigest()
      return {
          "x-agent-id": agent_id,
          "x-agent-auth": signature,
          "x-request-timestamp": timestamp,
          "x-sdk-version": "custom/1.0",
      }
  ```
</CodeGroup>

### Failure modes

| Response                | Cause                                                                       |
| ----------------------- | --------------------------------------------------------------------------- |
| `401 invalid_auth`      | Missing one of the required headers, agent not found, or signature mismatch |
| `401 expired_timestamp` | Timestamp drift exceeds 60 seconds                                          |
| `401 agent_killed`      | The agent's status is `killed` — re-provision before retrying               |

<Tip>
  In production, use `@relayerfi/agent-sdk` instead of building the headers yourself. The SDK handles signing, retries, kill-switch polling, and budget enforcement for you.
</Tip>

***

## 3. Session JWT (browser → Relayer dashboard)

The Relayer dashboard (relayer.fi/app) authenticates browser sessions with a JWT in the `Authorization: Bearer` header. This mode is **for the dashboard product itself** — you only need it if you're building a UI that proxies the user's session JWT.

```
Authorization: Bearer <jwt>
```

For external integrations, prefer the API Key mode above.

***

## Environments

Relayer provides two environments:

| Environment    | Base URL                        | Purpose                 |
| -------------- | ------------------------------- | ----------------------- |
| **Sandbox**    | `https://testnet.relayer.fi/v1` | Testing and development |
| **Production** | `https://api.relayer.fi/v1`     | Live operations         |

Both environments use the same auth scheme and endpoints. Workspace IDs, API keys, and agents are environment-scoped — a sandbox key won't authenticate against production.

## Response envelope

Every response — success or error — uses the same envelope:

<Tabs>
  <Tab title="Success">
    ```json theme={null}
    {
      "success": true,
      "message": "Success",
      "data": { },
      "statusCode": 200,
      "timestamp": "2026-05-15T12:00:00.000Z",
      "path": "/v1/signing/wallets"
    }
    ```
  </Tab>

  <Tab title="Error">
    ```json theme={null}
    {
      "success": false,
      "message": "Authorization header is missing",
      "statusCode": 401,
      "timestamp": "2026-05-15T12:00:00.000Z",
      "path": "/v1/signing/wallets"
    }
    ```
  </Tab>
</Tabs>

## Common auth errors

| Status                  | Meaning                                            | Fix                                                                                  |
| ----------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------ |
| `401 Unauthorized`      | Missing or malformed key/JWT/HMAC                  | Verify header format. ApiKey requires the `ApiKey` prefix — `Bearer` is for JWT only |
| `403 Forbidden`         | Key is valid but scope doesn't cover this endpoint | Use a key with the correct scope, or check that the endpoint isn't internal-only     |
| `404 Not Found`         | API key does not exist in this environment         | Sandbox keys don't work against production and vice versa                            |
| `401 expired_timestamp` | Agent HMAC timestamp drift > 60s                   | Sync your clock (NTP); regenerate timestamp on retry                                 |

See [Error Reference](/shared/error-reference) for the full catalog.

## Security best practices

<Steps>
  <Step title="Use environment variables">
    Store keys and agent secrets in `.env` files or your platform's secret manager (Vercel, AWS Secrets Manager, Doppler). Never hardcode them.
  </Step>

  <Step title="Server-side only for ApiKey">
    Never include an `ApiKey` in client-side JavaScript, mobile apps, or browser requests. For browser-side use, generate a short-lived client key from the dashboard with limited scope.
  </Step>

  <Step title="Rotate regularly">
    Rotate API keys every 90 days, or immediately if a leak is suspected. Old keys can be revoked from the dashboard.
  </Step>

  <Step title="Pin an IP allowlist">
    Per-key IP allowlists are configurable from the dashboard. Restrict production keys to your backend's egress IPs.
  </Step>

  <Step title="Rotate agent secrets too">
    Agent secrets can be rotated with `POST /v1/agents/{id}/rotate` — the old secret is revoked instantly. Run this on a schedule for long-running agents.
  </Step>
</Steps>

## Next Steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/get-started/quickstart">
    Make your first API call in under 5 minutes.
  </Card>

  <Card title="AI Assistants" icon="sparkles" href="/get-started/ai-assistants">
    Connect Relayer docs to Claude Code, Cursor, ChatGPT, and other AI tools via MCP.
  </Card>
</CardGroup>
