---
title: Product-user metadata
description: Pass custom customer identifiers, names, emails, and tracking tags inside inference requests.
---

Send product-user metadata with an OpenAI-compatible request to attribute usage, track customer analytics, and enforce spending caps and credits.

<KeyInserter type="api" label="API Key Tester" placeholder="zrv_your_api_key" />

---

## Supported request fields

For ID-only attribution, send `X-Zorveus-External-User-ID` in the request headers. OpenAI clients accept this through `extra_headers`; clients scoped to one user can set a default header. Do not change a shared client's default identity between concurrent requests.

For subscription cycles, send `X-Zorveus-Allowance-Starts-At` or `metadata.product_user.allowance_starts_at`. Follow [allowance periods and anchors](/startups/allowance-periods) before changing this timestamp.

Zorveus accepts user attribution directly through the standard OpenAI request body:

| Field | Type | Description |
| :--- | :--- | :--- |
| `user` | string | Standard OpenAI end-user ID from your application (such as `"usr_123"`). Mapped directly to `external_user_id`. |
| `metadata.external_user_id` | string | Alternative way to pass your application customer ID inside the metadata object. |
| `metadata.product_end_user_id` | string | Internal Zorveus product-user ID (`"peu_..."`) if already resolved. |
| `metadata.product_user.display_name` | string | Customer display name shown in the dashboard. |
| `metadata.product_user.email` | string | Customer email address. |
| `metadata.product_user.metadata` | object | Custom key-value pairs (such as `{"plan": "pro"}`). |

---

## Attribution examples

You can supply attribution using the native OpenAI `user` field or the nested `metadata` dictionary:

<CodeGroup titles={["Python (Standard OpenAI user field)", "Python (extra_body metadata)", "TypeScript (OpenAI SDK)"]}>

```python
from openai import OpenAI

client = OpenAI(
    base_url="https://api.zorveus.com/v1",
    api_key="zrv_your_api_key",
)

# Pass customer ID directly using OpenAI's standard user parameter
response = client.chat.completions.create(
    model="openai/gpt-4.1-mini",
    messages=[{"role": "user", "content": "Generate 3 taglines for an electric bicycle."}],
    user="cust_uuid_9921",
)

print(response.choices[0].message.content)
```

```python
from openai import OpenAI

client = OpenAI(
    base_url="https://api.zorveus.com/v1",
    api_key="zrv_your_api_key",
)

# Pass rich profile metadata inside extra_body
response = client.chat.completions.create(
    model="openai/gpt-4.1-mini",
    messages=[{"role": "user", "content": "Generate 3 taglines for an electric bicycle."}],
    user="cust_uuid_9921",
    extra_body={
        "metadata": {
            "product_user": {
                "display_name": "Alex Mercer",
                "email": "alex.mercer@company.io",
                "metadata": {"plan": "pro_tier"},
            },
        }
    },
)

print(response.choices[0].message.content)
```

```typescript
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "https://api.zorveus.com/v1",
  apiKey: "zrv_your_api_key",
});

async function main() {
  const completion = await client.chat.completions.create({
    model: "openai/gpt-4.1-mini",
    messages: [{ role: "user", content: "Generate 3 taglines for an electric bicycle." }],
    user: "cust_uuid_9921",
    ...({
      metadata: {
        product_user: {
          display_name: "Alex Mercer",
          email: "alex.mercer@company.io",
          metadata: { plan: "pro_tier" },
        },
      },
    } as any),
  });

  console.log(completion.choices[0].message.content);
}

main();
```

</CodeGroup>

---

## Identifier resolution and scoping

- **`user` and `external_user_id`**: The gateway maps the top-level `user` field directly to `external_user_id`. If you provide both `user` and `metadata.external_user_id`, ensure they match.
- **`product_end_user_id`**: If you pass both the internal Zorveus ID and an external ID, Zorveus verifies they refer to the same record.
- **Key-bound attribution**: When an API key is provisioned specifically for one end user, the gateway binds the product user automatically. Requests using that key inherit the product user without passing any body fields.
- **Invalid identifiers**: If a supplied product-user identifier cannot be resolved, Zorveus returns HTTP `403 Forbidden`. It will never treat an invalid user ID as an unmetered call.

---

## Response tracking headers

Zorveus returns tracking headers in every inference response:

| Header | Description |
| :--- | :--- |
| `x-zorveus-request-id` | Unique request identifier (`zreq_...`) for audit tracing and debugging. |
| `x-zorveus-reservation-id` | Unique reservation identifier (`resv_...`) held for the request. |
