---
title: "Respan params & metadata"
subtitle: "Tag gateway requests with a customer, a thread, and your own properties."
---

Respan params are fields you send with a gateway request, next to the standard OpenAI-compatible body. Use them to tag each request with the customer, conversation, and properties you want to find it by in Logs. The full list is in [Create chat completion](/docs/apis/gateway/create-chat-completion).

## Send Respan params

Send them in any of three ways, and combine them if you need to.

<Tabs>
<Tab title="Top-level fields">

Put the fields at the top level of the body. With the OpenAI SDK, pass them in `extra_body`. `client` is the OpenAI client from the [Gateway quickstart](/docs/documentation/features/gateway/gateway-quickstart).

```python
response = client.chat.completions.create(
    model="gpt-5.5",
    messages=[{"role": "user", "content": "Hello!"}],
    extra_body={
        "customer_identifier": "user_8842",
        "thread_identifier": "thread_7f3a",
        "metadata": {"feature": "support-chat", "environment": "production"},
    },
)
```

</Tab>
<Tab title="respan_params object">

Nest the fields in a `respan_params` object:

```json
{
  "model": "gpt-5.5",
  "messages": [{"role": "user", "content": "Hello!"}],
  "respan_params": {
    "customer_identifier": "user_8842",
    "metadata": {"feature": "support-chat"}
  }
}
```

</Tab>
<Tab title="Header">

When you can't change the body, for example when a CLI or third-party SDK builds it, put the params in the `X-Data-Respan-Params` header as base64-encoded JSON:

```bash
PARAMS=$(echo -n '{"customer_identifier":"user_8842","metadata":{"feature":"support-chat"}}' | base64 | tr -d '\n')

curl https://api.respan.ai/api/chat/completions \
  -H "Authorization: Bearer $RESPAN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Data-Respan-Params: $PARAMS" \
  -d '{"model": "gpt-5.5", "messages": [{"role": "user", "content": "Hello!"}]}'
```

If the header isn't valid base64, isn't valid JSON, or doesn't decode to a JSON object, Respan ignores the whole header. The request is still served, and its log carries a `header_parsing_error` warning.

</Tab>
</Tabs>

If the same field arrives more than one way, `respan_params` wins over the header, and both win over a top-level field. The exception is `variables`: top-level `variables` are merged key by key over the `variables` from `respan_params` or the header. The legacy names `keywordsai_params` and `X-Data-Keywordsai-Params` still work.

### Passthrough endpoints

The passthrough endpoints ([Anthropic](/docs/integrations/gateway/anthropic-sdk), OpenRouter, Gemini, and `/{provider}/v1/chat/completions`) forward your body to the provider in its own format. On these endpoints you can also put `respan_params` inside the request's `metadata`. Respan removes it before the provider sees the body, and its fields override the same fields in a top-level `respan_params`.

```python
import os
import anthropic

client = anthropic.Anthropic(
    base_url="https://api.respan.ai/api/anthropic/",
    api_key=os.environ["RESPAN_API_KEY"],
)

message = client.messages.create(
    model="claude-sonnet-4-5-20250929",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Hello!"}],
    metadata={"respan_params": {"customer_identifier": "user_8842"}},
)
```

On `/api/chat/completions`, `metadata` holds your custom properties, so Respan doesn't read a `respan_params` key inside it. Use `respan_params` or `extra_body` there.

## `respan_params` must be a JSON object

<Warning>
`respan_params` must be a JSON object everywhere you send it: at the top level, inside a passthrough's `metadata`, as the legacy `keywordsai_params`, and in the `X-Data-Respan-Params` header. Respan silently ignores any other value, such as a list, a number, or a boolean. A string is ignored too, with one exception: a top-level `respan_params` string that decodes to an object is parsed (see the note below). In `metadata.respan_params` and `keywordsai_params`, a string is always ignored, even one that encodes an object.

The request is still served with no error, but none of the params in the ignored value apply:

- If it holds `customer_identifier`, the request isn't attributed to that customer, so customer budgets and rate limits don't apply to it.
- If it holds `metadata`, `thread_identifier`, or `custom_identifier`, the log doesn't have them.
- If it holds `disable_log`, the request is **logged in full**.
- Prompt, routing, fallback, and credential params in it are ignored.
- A top-level `respan_params` that is a non-empty list, a non-zero number, or `true` also drops the params in the `X-Data-Respan-Params` header.

Fields you send at the top level of a `/api/chat/completions` body, such as a top-level `customer_identifier`, still apply.
</Warning>

The easy way to hit this: the OpenAI SDK types `metadata` as `Dict[str, str]`, so on an OpenAI-compatible passthrough such as OpenRouter it's tempting to `json.dumps` the params. That sends a string, which is ignored. Send an object through `extra_body` instead:

```python
import json
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://api.respan.ai/api/openrouter/v1",
    api_key=os.environ["RESPAN_API_KEY"],
)

# Wrong: metadata.respan_params is a string, so none of it applies
client.chat.completions.create(
    model="openai/gpt-5.5",
    messages=[{"role": "user", "content": "Hello!"}],
    metadata={"respan_params": json.dumps({"customer_identifier": "user_8842"})},
)

# Right: an object, at the top level of the body
client.chat.completions.create(
    model="openai/gpt-5.5",
    messages=[{"role": "user", "content": "Hello!"}],
    extra_body={"respan_params": {"customer_identifier": "user_8842"}},
)
```

<Note>
One exception: a top-level `respan_params` sent as a JSON-encoded string is parsed. It exists for `multipart/form-data` uploads, such as audio transcription, where a form field can't hold an object. The string must still decode to an object. In a JSON body, always send an object.
</Note>

## Common params

| Param | Type | What it does | Shown in Logs as |
|---|---|---|---|
| `customer_identifier` | string | The end user the request belongs to. Drives [Users](/docs/documentation/features/customer-identifier) and per-customer budgets and rate limits. | **Customer ID** |
| `customer_params` | object | Customer details: `customer_identifier` (required), `name`, `email`, and `rate_limit`. See below. | **Customer name**, **Customer email** |
| `thread_identifier` | string | Groups the requests of one conversation. | **Thread ID** |
| `custom_identifier` | string | An indexed tag for a record in your app, like a ticket or an order. | **Custom ID** |
| `metadata` | object | Your own key-value pairs. Values are stored as strings. | **Custom properties** |
| `disable_log` | boolean | `true` keeps the input and output out of the log. Tokens, cost, and latency are still recorded. | |
| `request_breakdown` | boolean | `true` adds a summary of tokens, cost, and latency to the response body. When streaming, it comes in the final chunk. | |

In `customer_params`:

- `customer_identifier` wins over a top-level `customer_identifier`.
- `name` and `email` are saved only the first time Respan sees the customer.
- `rate_limit` caps that customer's requests per minute. Over it, `/api/chat/completions` returns `429`. Other endpoints don't apply it.
- Budget fields are ignored. Set a customer's budget in [Users](/docs/documentation/features/customer-identifier) or with [Update a user](/docs/apis/users/update-user).

Params for routing, fallbacks, caching, and prompts are covered on their own pages, starting with [Routing](/docs/documentation/features/gateway/routing-and-passthrough).

## Find them in Logs

Open **Gateway** › **Logs**, click a span, and open the **Metadata** tab. **Properties** shows the customer, thread, and custom ID, and **Custom properties** lists your `metadata` keys.

<img src={"/docs/images/gateway-features/logs-metadata-tab-light.png"} className="block dark:hidden" alt="A span's Metadata tab in Logs: Properties with Customer ID, Customer name, Customer email, Thread ID, and Custom ID, and Custom properties with feature, environment, and customer_tier" />
<img src={"/docs/images/gateway-features/logs-metadata-tab-dark.png"} className="hidden dark:block" alt="A span's Metadata tab in Logs: Properties with Customer ID, Customer name, Customer email, Thread ID, and Custom ID, and Custom properties with feature, environment, and customer_tier" />

Hover **Customer ID**, **Thread ID**, or **Custom ID** and click the filter icon to show only the spans with that value. To group and filter by these fields, see [Custom IDs & attributes](/docs/documentation/features/observe/custom-ids).

## Beta features

Opt in to experimental features with the `X-Respan-Beta` header. Separate several flags with commas.

```bash
curl https://api.respan.ai/api/chat/completions \
  -H "Authorization: Bearer $RESPAN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Respan-Beta: token-breakdown-2026-03-26" \
  -d '{"model": "gpt-5.5", "messages": [{"role": "user", "content": "Hello!"}]}'
```

| Flag | What it does |
|---|---|
| `token-breakdown-2026-03-26` | Adds best-effort token estimates per part (`system_tokens`, `tools_tokens`, `user_message_tokens`) to the log's `properties`. |
