> ## 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.

> [!NOTE]
>
> **Prerequisites:** You need a Lava merchant account with a secret key. See [Quickstart: Make Your First Request](https://lava.so/docs/get-started/quickstart-track.md) to get started.

## 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.

> [tab] 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] 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!' }]
>   }
> });
> ```

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](https://lava.so/docs/monetize/meter-only-mode.md) for details.

### Customer Billing Mode

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

> [tab] 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] 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] 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] 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)
> ```

***

## 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](https://lava.so/docs/gateway/supported-providers.md) 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.

***

## Provider Examples

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

> [tab] 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] 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] Google Gemini
>
> ```javascript theme={null}
> const response = await fetch(
>   'https://api.lava.so/v1/forward?u=' +
>     encodeURIComponent('https://generativelanguage.googleapis.com/v1beta/models/gemini-3.5-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] 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-3.5-flash:generateContent',
>   {
>     body: { contents: [{ parts: [{ text: 'Hello!' }] }] },
>     customer_id: 'conn_xyz789',
>     meter_slug: 'my-meter',
>   }
> );
> ```

> [!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.

***

## Connector Accounts

A provider can hold several connected accounts, for example a personal and a work Gmail. A request that names none uses the default account, the one connected first (marked `default: true` in `connections`). To use another, send its id in the `x-lava-connector-account` header; the ids are the `connections` that `get_provider_details` returns.

When a provider holds several accounts, or the request named one, the response carries `x-lava-connector-account` (the account's id) and `x-lava-connector-account-label` (its URI-encoded name), so you can tell which account answered.

| Status | Code | Cause |
| - | - | - |
| **400** | `connector_account_invalid` | The header is not a valid account id |
| **404** | `connector_account_not_found` | No connected account of this provider has that id |

***

## 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

> [accordion] 401 Unauthorized
>
> 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] CORS errors in browser
>
> Lava blocks frontend requests to prevent token exposure. Always call Lava from your backend: `Frontend → Your Backend → Lava → AI Provider`.

***

## Next Steps

> [card] [Supported Providers](https://lava.so/docs/gateway/supported-providers.md)
>
> Full list of providers you can access with Lava AI Gateway

> [card] [Webhooks](https://lava.so/docs/integration/webhooks.md)
>
> Receive real-time notifications for billing and usage events


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.