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

# Forward Proxy & Authentication

> Route AI requests through Lava's proxy with secret key or forward token authentication

This page covers how to authenticate with Lava, construct proxy URLs, and handle responses and errors.

<Info>
  **Prerequisites:** You need a Lava merchant account with a secret key. See [Quickstart: Make Your First Request](/get-started/quickstart-track) to get started.
</Info>

## Authentication

Lava supports two ways to authenticate gateway requests:

| Method            | When to use                             | Auth header                    |
| ----------------- | --------------------------------------- | ------------------------------ |
| **Secret key**    | Gateway access without customer billing | `Bearer aks_live_...`          |
| **Forward token** | Customer billing, metering, or BYOK     | `Bearer <base64-encoded JSON>` |

### Using Your Secret Key (Simplest)

Pass your secret key directly in the `Authorization` header. Costs are charged to your merchant wallet — no token generation needed.

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST 'https://api.lava.so/v1/forward?u=https%3A%2F%2Fapi.openai.com%2Fv1%2Fchat%2Fcompletions' \
      -H 'Content-Type: application/json' \
      -H "Authorization: Bearer $LAVA_SECRET_KEY" \
      -d '{"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "Hello!"}]}'
    ```
  </Tab>

  <Tab title="SDK">
    ```typescript theme={null}
    import { Lava } from '@lavapayments/nodejs';

    const lava = new Lava();

    const data = await lava.gateway('https://api.openai.com/v1/chat/completions', {
      body: {
        model: 'gpt-4o-mini',
        messages: [{ role: 'user', content: 'Hello!' }]
      }
    });
    ```
  </Tab>
</Tabs>

This is all you need to route requests through the gateway with usage tracking and cost logging.

## Forward Tokens

When you need to bill customers, apply metering, or use your own provider keys, use a **forward token** — a base64-encoded JSON that bundles your secret key with billing parameters.

### Token Combinations

The fields you include determine how the request is authenticated and billed:

| Combination                                                     | Mode             | Who pays                          |
| --------------------------------------------------------------- | ---------------- | --------------------------------- |
| `secret_key` only                                               | Gateway          | Merchant                          |
| `secret_key` + `meter_slug`                                     | Meter-only       | Merchant (usage tracked on meter) |
| `secret_key` + `customer_id` + `meter_slug`                     | Customer billing | Customer                          |
| `secret_key` + `customer_id` + `meter_slug` + `disable_billing` | Billing disabled | Merchant (customer still tracked) |

Any combination can include `provider_key` to use your own provider credentials (BYOK) instead of Lava's managed keys.

`disable_billing` is only meaningful together with `customer_id` and `meter_slug` — it preserves full customer context while redirecting charges to your merchant wallet. Use it for free trials, testing, or promotional requests. See [Meter-Only Mode & Disable Billing](/monetize/meter-only-mode) for details.

### Customer Billing Mode

To bill a customer's wallet, include their `customer_id` and your pricing `meter_slug` in the token:

<Tabs>
  <Tab title="SDK">
    ```typescript theme={null}
    import { Lava } from '@lavapayments/nodejs';

    const lava = new Lava();

    const forwardToken = lava.generateForwardToken({
      customer_id: 'conn_xyz789',    // optional — charges this customer's plan
      meter_slug: 'my-meter',        // optional — prices usage with this meter
      provider_key: 'sk-your-key',   // optional — uses your own provider key (BYOK)
      disable_billing: false,        // optional — when true, tracks usage without charging
    });
    ```
  </Tab>

  <Tab title="TypeScript">
    ```typescript theme={null}
    const tokenData = {
      secret_key: 'aks_live_abc123...',  // your Lava secret key
      customer_id: 'conn_xyz789...',     // optional — charges this customer's plan
      meter_slug: 'my-meter',            // optional — prices usage with this meter
      provider_key: null,                // optional — uses your own provider key (BYOK)
      disable_billing: false,            // optional — when true, tracks usage without charging
    };

    const forwardToken = btoa(JSON.stringify(tokenData));
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import base64, json

    token_data = {
        "secret_key": "aks_live_abc123...",  # your Lava secret key
        "customer_id": "conn_xyz789...",     # optional — charges this customer's plan
        "meter_slug": "my-meter",            # optional — prices usage with this meter
        "provider_key": None,                # optional — uses your own provider key (BYOK)
        "disable_billing": False,            # optional — when True, tracks usage without charging
    }

    forward_token = base64.b64encode(json.dumps(token_data).encode()).decode()
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    # secret_key: required. All other fields are optional.
    # customer_id: charges this customer's plan. meter_slug: prices usage with this meter.
    # provider_key: uses your own provider key (BYOK). disable_billing: tracks without charging.
    FORWARD_TOKEN=$(echo -n '{"secret_key":"aks_live_abc123...","customer_id":"conn_xyz789...","meter_slug":"my-meter","provider_key":null,"disable_billing":false}' | base64)
    ```
  </Tab>
</Tabs>

***

## Constructing the Request URL

Lava routes requests by wrapping the provider's API endpoint (e.g., OpenAI's chat completions URL) as a query parameter using Lava's forward endpoint:

```
https://api.lava.so/v1/forward?u=<PROVIDER_URL>
```

See [Supported Providers](/gateway/supported-providers) for the full list of providers you can access with Lava AI Gateway.

<Tip>
  As a best practice, the provider URL should be URL-encoded.
</Tip>

***

## Provider Examples

The pattern is the same for every provider — only the URL, body format, and provider-specific headers change.

<Tabs>
  <Tab title="OpenAI">
    ```javascript theme={null}
    const response = await fetch(
      'https://api.lava.so/v1/forward?u=' +
        encodeURIComponent('https://api.openai.com/v1/chat/completions'),
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${forwardToken}`
        },
        body: JSON.stringify({
          model: 'gpt-4o-mini',
          messages: [{ role: 'user', content: 'Hello!' }]
        })
      }
    );

    const data = await response.json();
    const requestId = response.headers.get('x-lava-request-id');
    ```
  </Tab>

  <Tab title="Anthropic">
    ```javascript theme={null}
    const response = await fetch(
      'https://api.lava.so/v1/forward?u=' +
        encodeURIComponent('https://api.anthropic.com/v1/messages'),
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${forwardToken}`,
          'anthropic-version': '2023-06-01'  // Required by Anthropic
        },
        body: JSON.stringify({
          model: 'claude-haiku-4-5',
          max_tokens: 1024,
          messages: [{ role: 'user', content: 'Hello!' }]
        })
      }
    );

    const data = await response.json();
    const requestId = response.headers.get('x-lava-request-id');
    ```
  </Tab>

  <Tab title="Google Gemini">
    ```javascript theme={null}
    const response = await fetch(
      'https://api.lava.so/v1/forward?u=' +
        encodeURIComponent('https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent'),
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${forwardToken}`
        },
        body: JSON.stringify({
          contents: [{ parts: [{ text: 'Hello!' }] }]
        })
      }
    );

    const data = await response.json();
    const requestId = response.headers.get('x-lava-request-id');
    ```
  </Tab>

  <Tab title="SDK">
    ```typescript theme={null}
    import { Lava } from '@lavapayments/nodejs';

    const lava = new Lava();

    // OpenAI
    const openai = await lava.gateway('https://api.openai.com/v1/chat/completions', {
      body: { model: 'gpt-4o-mini', messages: [{ role: 'user', content: 'Hello!' }] },
      customer_id: 'conn_xyz789',
      meter_slug: 'my-meter',
    });

    // Anthropic
    const anthropic = await lava.gateway('https://api.anthropic.com/v1/messages', {
      body: { model: 'claude-haiku-4-5', max_tokens: 1024, messages: [{ role: 'user', content: 'Hello!' }] },
      headers: { 'anthropic-version': '2023-06-01' },
      customer_id: 'conn_xyz789',
      meter_slug: 'my-meter',
    });

    // Google Gemini
    const gemini = await lava.gateway(
      'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent',
      {
        body: { contents: [{ parts: [{ text: 'Hello!' }] }] },
        customer_id: 'conn_xyz789',
        meter_slug: 'my-meter',
      }
    );
    ```
  </Tab>
</Tabs>

<Note>
  Provider-specific headers (like `anthropic-version`) are passed through unchanged. The request body uses the provider's native format — Lava doesn't modify it.
</Note>

***

## Streaming

Add `"stream": true` to your request body. Lava proxies SSE chunks in real-time without buffering — the response is identical to calling the provider directly. Usage and billing are recorded after the stream completes.

```typescript theme={null}
body: JSON.stringify({
  model: 'gpt-4o-mini',
  messages: [{ role: 'user', content: 'Hello!' }],
  stream: true  // That's it — everything else stays the same
})
```

***

## Error Handling

| Status          | Cause                                         | Retryable                |
| --------------- | --------------------------------------------- | ------------------------ |
| **401**         | Invalid secret key or malformed forward token | No — check credentials   |
| **402**         | Insufficient credit balance                   | No — add funds first     |
| **429**         | Rate limit exceeded                           | Yes — back off and retry |
| **500/502/503** | Provider or Lava server error                 | Yes — retry with backoff |

Provider error responses are forwarded unchanged — check your request body against the provider's API docs if you see provider-level errors.

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="401 Unauthorized" icon="lock">
    Verify your secret key matches the dashboard and the `Bearer` prefix is in the Authorization header. If using a forward token, decode it to inspect:

    ```bash theme={null}
    echo "your_forward_token" | base64 -d
    ```
  </Accordion>

  <Accordion title="CORS errors in browser" icon="shield">
    Lava blocks frontend requests to prevent token exposure. Always call Lava from your backend: `Frontend → Your Backend → Lava → AI Provider`.
  </Accordion>
</AccordionGroup>

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Supported Providers" icon="robot" href="/gateway/supported-providers">
    Full list of providers you can access with Lava AI Gateway
  </Card>

  <Card title="Webhooks" icon="webhook" href="/integration/webhooks">
    Receive real-time notifications for billing and usage events
  </Card>
</CardGroup>
