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

# Weflayr integration playbook

> Step-by-step instructions for an AI coding agent integrating Weflayr into an application. Written to be read by the agent.

<Note>
  This page is written for an AI coding agent working inside a user's codebase. If you are a human, read the [Quickstart](/quickstart) instead.
</Note>

You are integrating Weflayr into this codebase. Follow the steps below in order. Steps 1 to 8 are code work, step 9 is what you have to ask the user, step 10 is the hand-off you must produce. Decide everything you can decide from the code yourself; only ask about what the code cannot tell you.

## What Weflayr needs

Weflayr benchmarks other models on the application's real LLM calls, recommends the ones that answer as well for less, and routes the calls to them. It also joins the cost of every call to the revenue it produced, per customer and per feature. To do that it needs:

1. **The LLM calls.** Preferably sent through the **Weflayr gateway**: an OpenAI-compatible endpoint that calls any model of any provider, logs the call, and can later follow a routing strategy. When the calls must keep going to the providers directly, the **telemetry SDK** records them instead.
2. **What each call is for**: a `feature_name` (the product job), a `call_name` (the LLM call inside it) and a `customer_name` (who it is for).
3. **Revenue**, or key metrics for an internal tool, booked against the same `customer_name`. This part is configured outside the code, in the dashboard or through the API.

Get the naming right and the rest follows: [benchmarks](/product/model-benchmarks) are set up per call, [routing strategies](/product/routing-strategies) switch models per customer, and the dashboard shows [cost per feature](/product/ai-cost) and [margin per customer](/product/ai-margin).

## Rules

* **Assume the user has never used Weflayr.** Prefer leaving Weflayr-specific concepts out of your questions entirely: ask the plain question ("Is this a product you sell, or an internal tool?") and map the answer to the Weflayr concept yourself. Only name and explain a concept when the user needs it to answer or to act, and then in one plain sentence.
* **Never change what an LLM call does.** Keep the same model, prompt and parameters. Moving a call to the gateway changes where it is sent, not what it asks.
* **Do not move calls to the gateway without the user's go-ahead** (step 2): gateway calls are paid from the user's Weflayr credits instead of their provider accounts.
* **Name the features and calls yourself.** They come from the code, so do not make the user do it: read what the application does, propose the names, wire them up, and list them in your hand-off for review. If you are in doubt about where a feature starts and stops, or about what to call it, ask rather than guess.
* **Never invent a `customer_name`.** It has to match the identifier on the user's revenue side, which is outside the code. If the codebase does not tell you, ask (step 9).
* **Keep the diff small.** One client change or one startup call per process, one metadata scope per feature entry point, no refactor of the surrounding code.
* **Do not commit the API key.** It goes in the environment, like the user's other secrets.
* Ask before adding a new process, service or background job. Prefer hooking into something that already exists.

## Step 1 - Survey the codebase

Do this before writing anything, and report what you found.

| What to find | Why it matters |
| - | - |
| Language(s): Python, Node.js, or both | Picks the SDK |
| AI provider SDKs actually imported (`openai`, `anthropic`, `boto3`/`@aws-sdk/client-bedrock-runtime`, `google-genai`, `cohere`, `mistralai`, `elevenlabs`) | Calls made with the OpenAI client move to the gateway with a base URL change; the others need more (step 2) |
| AI framework, if any (LangChain, LlamaIndex, Haystack, CrewAI, LangGraph) | Where the client is built (step 3) |
| Every process that makes LLM calls: web server, background workers, cron jobs, serverless functions, scripts | Each one needs the change (step 3); short-lived ones need `flush()` on the SDK path |
| The models each call uses | They must be available through the gateway (step 3) |
| Where the request's user, tenant, organization or workspace is available | Fills `customer_name` (step 6) |
| How secrets are provided (`.env`, secret manager, platform config) | Where `WEFLAYR_API_KEY` goes (step 3) |
| Whether the app is sold to customers or used internally | Decides the project type (step 9) |
| Any existing billing code (Stripe, invoices, subscriptions, usage export) | Decides how revenue reaches Weflayr (step 9) |

## Step 2 - Choose how calls reach Weflayr

| | Gateway (recommended) | Telemetry SDK |
| - | - | - |
| Code change | Point the OpenAI client at `https://gateway.weflayr.com/v1` | One startup call plus a provider instrumentation package |
| Who bills the model calls | Weflayr, from the organization's [credits](/product/credits) | The user's own provider accounts |
| Routing strategies | Yes: send `strategy/<id>` as the model | No |
| Benchmarks, cost, margin | Yes | Yes |
| Works with | The OpenAI client (Chat Completions and Responses), for every provider | OpenAI, Anthropic, Bedrock, Google GenAI, Cohere, Mistral, ElevenLabs SDKs |

Propose the gateway, and ask the user before switching (step 9, question 1). Calls made with a provider's own SDK (Anthropic, Bedrock, Gemini...) reach the gateway only once rewritten for the OpenAI client: propose that as a separate change, or keep those calls on the SDK path. A codebase can mix both.

## Step 3 - Send the calls

### Gateway

The user creates an API key with the **Gateway** scope at `https://app.weflayr.com/api-keys/` and adds credits at `https://app.weflayr.com/credits/`. Expose the key as `WEFLAYR_API_KEY` with the mechanism the codebase already uses for secrets, and add it to `.env.example` (or the equivalent).

Change only the client's base URL and key. Keep every call as it is:

<CodeGroup>
  ```python Python theme={null}
  import os

  from openai import OpenAI

  client = OpenAI(base_url="https://gateway.weflayr.com/v1", api_key=os.environ["WEFLAYR_API_KEY"])
  ```

  ```js Node.js theme={null}
  const OpenAI = require('openai');

  const client = new OpenAI({ baseURL: 'https://gateway.weflayr.com/v1', apiKey: process.env.WEFLAYR_API_KEY });
  ```
</CodeGroup>

* **Model names**: the gateway takes the catalog names (e.g. `gpt-5.6`, `claude-opus-5-5`) and picks the provider; pass `provider` in the body to pin one. A name the catalog does not know returns `400 unknown_model`: list it in your hand-off.
* **Fields a model does not take** (e.g. `temperature` on a reasoning model) are removed and listed in `weflayr_warnings` by default (`self_heal`). See [Responses](/api/gateway/responses) for every field.
* **Metadata** (step 5): turn the header on once at startup, then wrap calls in `propagate_metadata` as for the SDK:

<CodeGroup>
  ```python Python theme={null}
  import weflayr

  weflayr.enable_propagate_metadata_to_gateway()
  ```

  ```js Node.js theme={null}
  const weflayr = require('weflayr');

  // setup() lets the metadata follow the code into the OpenAI call; it is required.
  weflayr.setup();
  weflayr.enablePropagateMetadataToGateway();
  ```
</CodeGroup>

Without the SDK, send the metadata on the request: `x-weflayr-metadata: {"feature_name": "support-chat"}` as a header, and `"weflayr": {"customer_name": "...", "call_name": "..."}` in the body (`extra_body` in Python).

### Telemetry SDK

Expose `WEFLAYR_API_KEY` from a key with the **Management** scope, then install the packages and call `auto_instrument` once per process.

| Provider | `ai_sdks` value | Node.js packages | Python packages |
| - | - | - | - |
| OpenAI | `openai` | `weflayr openai @traceloop/instrumentation-openai` | `weflayr openai opentelemetry-instrumentation-openai` |
| Anthropic | `anthropic` | `weflayr @anthropic-ai/sdk @traceloop/instrumentation-anthropic` | `weflayr anthropic opentelemetry-instrumentation-anthropic` |
| AWS Bedrock | `bedrock` | `weflayr @aws-sdk/client-bedrock-runtime @traceloop/instrumentation-bedrock` | `weflayr boto3 opentelemetry-instrumentation-bedrock` |
| Google GenAI (Gemini, Vertex) | `google-genai` | `weflayr @google/genai @traceloop/instrumentation-google-generativeai` | `weflayr google-genai opentelemetry-instrumentation-google-genai` |
| Cohere | `cohere` | `weflayr cohere-ai @traceloop/instrumentation-cohere` | `weflayr cohere opentelemetry-instrumentation-cohere` |
| ElevenLabs | `elevenlabs` | `weflayr @elevenlabs/elevenlabs-js` | `weflayr elevenlabs` |
| Mistral | `mistral` | `weflayr @mistralai/mistralai @opentelemetry/api` (plus `@mistralai/mistralai-gcp` for Vertex AI) | `weflayr mistralai` |

Install with the tool the repository already uses (`npm`/`pnpm`/`yarn`, `pip`/`uv`/`poetry`). With an AI framework, **instrument the provider SDK underneath it**: LangChain, LlamaIndex, Haystack, CrewAI and LangGraph call the provider SDK in the end, so every call of the chain is caught.

One `auto_instrument` call per process, at startup, **before the provider SDK is loaded**. The instrumentation patches the provider module as it is imported, so a client created earlier is not traced.

Put the call in the entry point that already exists. Give it a file of its own only when you have to: several processes that would otherwise each carry a copy of the configuration, or a Node.js ESM entry point, which has to be preloaded (see below).

<CodeGroup>
  ```js Node.js theme={null}
  // At the top of your entry point, before anything that touches an AI SDK.
  const weflayr = require('weflayr');

  async function main() {
    await weflayr.autoInstrument({
      aiSdks: ['openai', 'anthropic'],
      defaultTags: { env: process.env.NODE_ENV, service: 'api' },
    });

    // Only now load the provider SDKs.
    const OpenAI = require('openai');
  }

  main();
  ```

  ```python Python theme={null}
  # At the top of your entry point.
  import os

  import weflayr

  weflayr.auto_instrument(
      ai_sdks=[weflayr.AiSdk.OPENAI, weflayr.AiSdk.ANTHROPIC],
      default_tags={"env": os.environ.get("ENV", "production"), "service": "api"},
  )
  ```
</CodeGroup>

Practical placement:

* **Node.js, CommonJS**: `await autoInstrument` inside an `async` entry function, then `require` the provider SDKs.
* **Node.js, ESM**: put the call in a preload module that also registers OpenTelemetry's ESM loader hook, and start the app with `node --import ./instrumentation.mjs`:

  ```js instrumentation.mjs theme={null}
  import { register } from 'node:module';
  import { autoInstrument } from 'weflayr';

  register('import-in-the-middle/hook.mjs', import.meta.url);

  await autoInstrument({ aiSdks: ['openai', 'anthropic'] });
  ```

  Install `import-in-the-middle` alongside the instrumentation packages.
* **TypeScript**: follow the CommonJS or the ESM rule depending on what `module` in `tsconfig.json` compiles to.
* **Python**: at the top of the entry point, before the module that builds the provider client is imported.
* **Several processes** (server + workers + cron): this is when a file of its own earns its place. Put the call in one shared module (`observability.py`, `instrumentation.ts`) and import it first from each entry point, rather than copying the configuration into each.
* **Serverless / Lambda**: same call at module scope, plus `export_mode="immediate"` and `flush()` (step 7).

Providers reached through the OpenAI client with another base URL: Azure OpenAI, Alibaba (DashScope) and xAI are detected on their own. For Mistral through the OpenAI client, set `provider_name_override_if_telemetry_instrumentation=MISTRAL` on the scope (step 7). Any other OpenAI-compatible endpoint is priced as OpenAI: flag it in your hand-off.

### OpenAI streaming

If the codebase streams responses from the OpenAI SDK directly, token usage is only returned when it is asked for. Without it, those calls arrive with no token counts and no cost. Add it:

<CodeGroup>
  ```js Node.js theme={null}
  await client.chat.completions.create({
    model: 'gpt-5.6',
    messages: [{ role: 'user', content: 'Hello!' }],
    stream: true,
    stream_options: { include_usage: true },
  });
  ```

  ```python Python theme={null}
  client.chat.completions.create(
      model="gpt-5.6",
      messages=[{"role": "user", "content": "Hello!"}],
      stream=True,
      stream_options={"include_usage": True},
  )
  ```
</CodeGroup>

## Step 4 - Name the features and calls

A **feature** is the product-side job that caused the LLM call: something the user would recognize, something you would put on a pricing page or a roadmap line. It is the row you will read on the AI Cost page.

Naming features this way keeps the business view separate from the technical implementation. One run of a feature may make 1 LLM call or 10, and that can change next month, but the dashboard keeps answering the same question: one run of this feature costs this much. Every call it makes is summed under it, so refactoring the code does not move the number.

A feature is **not** a function name, **not** a single LLM call, and **not** a model name.

### Pick the level

Wrap the outermost function that corresponds to one user-visible job. Every LLM call nested inside it, at any depth, inherits the metadata, so you only need one scope per feature and not one per call.

| Level | Example | Verdict |
| - | - | - |
| Too high | `api-request`, `backend`, `agent` | Every call lands in one row. The AI Cost page tells you nothing |
| Right | `support-chat`, `invoice-extraction`, `weekly-digest`, `onboarding-summary` | One row per thing the product does |
| Too low | `summarize-chunk`, `retry-2`, `gpt-5-call`, `tool-call-loop` | Hundreds of rows, none of which map to the product |

If a feature has internal steps worth separating (retrieval, drafting, judging), keep one feature and give each step its own call name (see below). Do not create a feature per step.

Most applications end up with somewhere between a handful and a few dozen features. If your list is much longer, you picked too low a level.

### Naming rules

* kebab-case, lowercase, stable over time.
* Describes the job, not the implementation: `document-summary`, not `openai-summarizer-v2`.
* No ids, dates, versions, model names or environment names in the name. Those belong in tags.
* Short enough to read in a dashboard row, self-explanatory to someone who has not read the code.
* Once a name is live it becomes a dashboard row and a benchmark unit. Renaming it splits its history, so choose a name that will still be right in six months.

| Bad | Good | Why |
| - | - | - |
| `handle_message` | `support-chat` | Function name, not a product feature |
| `gpt4-summary` | `document-summary` | Names the model, which changes |
| `prod-chat` | `support-chat` | Environment belongs in `default_tags` |
| `chat-user-8123` | `support-chat` | Customer belongs in `customer_name` |

Name the features yourself from what the code does and wire them up. You only need to ask the user when the code genuinely does not reveal the product boundary: a generic `run_agent()` entry point that serves several unrelated jobs, or a name that hinges on how they sell the product. Otherwise, list the names in your hand-off (step 10) so they can rename any of them before traffic accumulates.

### Name the calls too

A **call name** is the LLM call inside a feature: `classify-ticket`, `draft-reply`, `summarize-thread`. A [benchmark](/product/model-benchmarks) is set up per call name, so give each distinct prompt its own name, in kebab-case, with the same rules as features. A feature with a single LLM call still gets a call name.

## Step 5 - Place the metadata scope

`propagate_metadata` / `propagateMetadata` opens a scope. Every LLM call made inside it (sent through the gateway with the header turned on, or recorded by the SDK), including nested and awaited calls, is stamped with the same metadata and the same correlation id.

### Decorator or context manager

Pick with one question: **does any value change per request?**

* `customer_name` comes from the request in almost every multi-tenant application, so **use the context manager / callback form**. The decorator fixes its metadata when the function is defined, which cannot express a per-request customer.
* Use the **decorator** only when every value is a constant: single-tenant applications, internal tools, batch jobs that always run for the same entity.

<CodeGroup>
  ```js Node.js theme={null}
  // Per-request values: callable form.
  async function handleSupportTicket(customerName, ticket) {
    return weflayr.propagateMetadata(
      { featureName: 'support-chat', customerName },
      async () => {
        // Every LLM call in here, at any depth, is tagged.
        return await answerTicket(ticket);
      }
    );
  }

  // Constant values: wrap the function once.
  const runDigest = weflayr.propagateMetadata({
    featureName: 'weekly-digest',
    customerName: 'internal',
  })(async () => { /* ... */ });
  ```

  ```python Python theme={null}
  # Per-request values: context manager.
  def handle_support_ticket(customer_name: str, ticket: str):
      with weflayr.propagate_metadata(
          feature_name="support-chat",
          customer_name=customer_name,
      ):
          # Every LLM call in here, at any depth, is tagged.
          return answer_ticket(ticket)


  # Constant values: decorator.
  @weflayr.propagate_metadata(feature_name="weekly-digest", customer_name="internal")
  def run_digest():
      ...
  ```
</CodeGroup>

Name each call inside the scope with `set_call_name` / `setCallName` (same forms: context manager or decorator in Python, callable or decorator in Node.js). It keeps the feature's scope and does not start a new run.

In TypeScript, the `@propagateMetadata(...)` decorator syntax only works on class methods with `experimentalDecorators` enabled. Everywhere else use `propagateMetadata(options, fn)` or `propagateMetadata(options)(fn)`.

### Where to open the scope

* **Web handler**: inside the route handler or the service function it calls, once the authenticated user or tenant is resolved. Middleware works too if the tenant is known there and the feature name can be derived from the route.
* **Background job / queue consumer**: inside the job function, around the whole job body.
* **Agent or chain**: around the top-level `invoke` / `run` call. Every model call the loop makes inherits the metadata.
* **Script or one-off task**: around `main()`.

### Async and concurrency

The scope is carried by `contextvars` (Python) and OpenTelemetry context (Node.js), so it survives `await` and isolates correctly across concurrent tasks and threads. One thing it does not cover: a call fired into a detached background task that outlives the block. If the codebase does that, open the scope **inside** the task instead.

## Step 6 - Fill `customer_name`

Always set `customer_name`, on the metadata scope or, for a gateway call, in the `weflayr` block of the request body. It is the string Weflayr uses to join AI cost to revenue, so it has to match the identifier on the revenue side exactly.

### Find it in the codebase, in this order

1. The billing entity: organization, workspace, account, tenant. **Prefer this**, because it is what gets invoiced.
2. The authenticated user, if the product bills per user.
3. The owner of the API key, for a machine-facing API.
4. An existing Stripe customer id or name, if there is billing code.

Prefer the identifier that already appears in the billing system. If the product bills organizations, do not use the individual user: the AI Margin page would then show one row per seat and no revenue against most of them.

### If you cannot find it

Stop and ask the user (step 9, question 4). Present the candidates you found and where they live in the code. Do not invent a value, do not fall back to `"unknown"`, and do not use email addresses or raw database ids unless the user confirms that is what their revenue rows will carry.

If the application genuinely serves one customer only (an internal tool, a single-tenant deployment), a constant is fine, but ask first: an internal tool is usually better tracked as an [internal project](/product/organizations-and-projects#internal-projects), where the equivalent field is your own user rather than a paying customer.

It also selects the customer overrides of a [routing strategy](/product/routing-strategies) (by margin, spend or a list of customers), which only work when the call carries its customer.

## Step 7 - The other parameters

### `extra_tags` / `extraTags`

Per-scope dimensions that become filters on the dashboard. Values must be strings, numbers or booleans.

Use them for anything you want to slice cost by that is neither the feature nor the customer: the step inside a feature (`step: "retrieval"`), the plan tier, the request source (`web` / `api` / `mobile`), a prompt version, an experiment arm.

Do not put high-cardinality values in tags: user ids, request ids, timestamps, raw prompt text. A tag should have a handful of distinct values, not thousands. `customer_name` already covers the per-customer breakdown.

### SDK path only

* `provider_name_override_if_telemetry_instrumentation` / `providerNameOverrideIfTelemetryInstrumentation`: the provider to bill when the client on the wire is not the provider being paid (`OPENAI`, `AZURE_OPENAI`, `ANTHROPIC`, `BEDROCK`, `COHERE`, `ELEVENLABS`, `MISTRAL`, `GOOGLE`, `VERTEX`, `ALIBABA`, `SPACEXAI`). Ignored for gateway calls.
* `default_tags` / `defaultTags` in `auto_instrument`: stamped on every call of the process, e.g. environment and service name.
* `export_mode="immediate"` and `flush()` in short-lived runtimes (Lambda, scripts, one-off jobs), so the last calls are sent before the process ends.

## Step 8 - Verify

Run the app and make one real LLM call through each feature you wired, then check the data arrived (with a key that has the Management scope):

```bash theme={null}
curl "https://app.weflayr.com/api/features/" -H "Authorization: Bearer $WEFLAYR_MANAGEMENT_KEY"
curl "https://app.weflayr.com/api/customers/" -H "Authorization: Bearer $WEFLAYR_MANAGEMENT_KEY"
```

Your feature names and customer names should be listed after a minute or two. The calls also show up live on the [Overview](/product/overview).

| Symptom | Cause to check |
| - | - |
| `401` from the gateway | Wrong, expired or revoked key |
| `403 gateway_scope_required` | The key lacks the Gateway scope |
| `402 insufficient_credits` | The organization has no credits left |
| `400 unknown_model` | The model name is not in the gateway catalog |
| Gateway calls arrive without a feature (Node.js) | `weflayr.setup()` was not called before `enablePropagateMetadataToGateway()` |
| No SDK calls at all | The provider SDK was loaded before `auto_instrument`, or `auto_instrument` never ran in that process |
| SDK calls from the server but not from a script or Lambda | Missing `flush()` and `export_mode="immediate"` |
| Feature appears, customer is missing | The LLM call runs outside the metadata scope, most likely in a detached background task |
| Calls with no token counts or no cost | OpenAI streaming without `stream_options.include_usage` (SDK path) |

## Step 9 - Questions to ask the user

Five questions, asked in one message, with your proposed answers filled in where the codebase already told you. Phrase them for someone who has never opened Weflayr. Do not guess your way past questions 1, 3 and 4.

**1. Can the LLM calls go through Weflayr?**
Explain in one sentence: Weflayr then calls the models itself, billed from prepaid credits instead of their provider accounts, and can later switch models without a deploy. If they say no, use the telemetry SDK.

**2. Is this a product you sell, or an internal tool?**
A normal project tracks AI cost against revenue. An [internal project](/product/organizations-and-projects#internal-projects) tracks it against the key metrics the tool drives (meetings booked, tickets resolved) and its accounts are your own users instead of customers.

**3. Where does your revenue come from?**
This decides how revenue reaches Weflayr, and it is the half of the picture the code cannot provide.

* **Stripe**: nothing to code. They connect Stripe from **Costs & revenue** in the dashboard sidebar and issued invoices sync automatically, credit notes deducted. The one thing to check is the join key: the `customer_name` you stamp on calls must equal the Stripe customer **name**, or they set a `weflayr_customer_name` metadata field on each Stripe customer. See [Sync Revenue](/product/sync-revenue).
* **Another billing system**: push revenue with the [Revenue API](/api/management/set-revenue). Find the cleanest hook in what already exists rather than building something new, in this order of preference: an existing invoice or subscription webhook handler, an existing nightly billing job, an existing data export or reporting job, and only as a last resort a small standalone script. Also propose a one-off backfill of past months so the AI Margin page is not empty. Show them the code before writing it.
* **No revenue yet, or revenue tracked in a spreadsheet**: CSV upload from **Costs & revenue**, no code.
* **Internal project**: key metrics instead of revenue, by CSV or through the [Key metrics API](/api/management/set-key-metrics).

<CodeGroup>
  ```python Python theme={null}
  import weflayr
  from weflayr.openapi.models import SetRevenueInput

  client = weflayr.client()  # reads WEFLAYR_API_KEY

  client.set_revenue(body=SetRevenueInput.from_dict({
      "rows": [
          {"customer_name": "Acme Corp", "amount": 1200.50, "month": "2026-06"},
      ]
  }))
  ```

  ```js Node.js theme={null}
  const weflayr = require('weflayr');

  const client = weflayr.client({ apiKey: process.env.WEFLAYR_API_KEY });

  await weflayr.api.setRevenue({
    client,
    throwOnError: true,
    body: {
      rows: [{ customerName: 'Acme Corp', amount: 1200.5, month: '2026-06' }],
    },
  });
  ```
</CodeGroup>

Revenue sources add up, so tell them to use one source only, otherwise a customer-month counted in both Stripe and the API is double counted.

**4. Confirm the customer identifier.**
Show what you found in the codebase and which one you propose, and state explicitly that it must match the identifier on their revenue rows or in Stripe. Ask them to confirm the exact string format (for example `Acme Corp` or `org_8123`).

**5. Review the feature and call names.**
Not a blocking question: you named them in step 4 and wired them up. Show the list, one line each with the code path it wraps, and say that renaming later splits the history of that dashboard row, so now is the cheap moment to object.

### Then mention these two, and offer to help

Not needed to get value out of Weflayr, so raise them after the four questions above are settled rather than in the same breath. Offer to do the work if the user wants either.

**Free AI credits.** If a provider granted them credits, their invoiced cost is lower than list prices until the credits run out. Each grant (provider, amount, effective from date) is declared under **Free AI credits** in **Costs & revenue**, and every dashboard page then offers a **Show real costs with free tokens** toggle: off shows list prices, which is what you want to judge unit economics, on shows the real net cost. There is no API for this yet, so it is a dashboard action. See [Free AI credits](/product/free-ai-credits).

**Customer tags.** Their own dimensions on customers: plan tier, segment, region, industry, account manager. They become filters on the AI Cost and AI Margin pages, so they can ask what a segment costs them. Uploaded as a CSV or pushed with the [Customer tags API](/api/management/set-customer-tags). If the codebase already holds these fields, offer to push them from the same place the revenue is pushed. See [Customer tags](/product/customer-tags).

## Step 10 - Hand off a TODO list

End with a list in this shape. Be specific: name files, features and customers, not categories.

```markdown theme={null}
## Weflayr integration - what I did

- Pointed <N> OpenAI clients at the Weflayr gateway (<files>) / installed `weflayr` + <instrumentation packages>
- Wrapped <N> features:
  - `support-chat` - `api/routes/chat.py:42`, customer from `request.user.organization.name`, calls `answer-question`, `classify-ticket`
  - `invoice-extraction` - `workers/invoices.py:88`, customer from `job.tenant_name`, call `extract-fields`
- Added `WEFLAYR_API_KEY` to `.env.example` and <secret store>

## You need to do this in the dashboard

- [ ] Create the API key at https://app.weflayr.com/api-keys/ (<Gateway / Management> scope) and set `WEFLAYR_API_KEY` in production
- [ ] Add credits at https://app.weflayr.com/credits/ (gateway only)
- [ ] Set the project type to <normal / internal>
- [ ] Connect Stripe under Cost & revenue (or upload the revenue CSV)

## I need a decision from you

- [ ] Confirm `customer_name` = <the identifier> matches your Stripe customer names
- [ ] Review the feature and call names above and tell me if you want any renamed

## Known gaps

- <calls left on the SDK path, and why>
- <model names the gateway does not know>
- <anything left unverified>

## Once traffic is in

- Benchmark your costliest calls in Model benchmarks: they show which models keep your quality for less
- Turn a benchmark into a routing strategy, and I will change the calls' model to `strategy/<id>`

## Optional, whenever you want it

- Declare free AI credits, if a provider granted you any, to see your real net cost
- Upload customer tags (plan, segment, region) to filter the AI Cost and AI Margin pages by them

Tell me and I will help with any of these.
```

## Pages to read for more detail

| Topic | Page |
| - | - |
| Gateway, every field and error | [Responses](/api/gateway/responses), [Chat completions](/api/gateway/chat-completions), [Errors](/api/introduction#errors) |
| SDK functions | [Python SDK](/sdks/python), [Node.js SDK](/sdks/node) |
| Telemetry SDK, all options | [Telemetry](/sdks/telemetry) |
| Benchmarks and routing | [Model benchmarks](/product/model-benchmarks), [Routing strategies](/product/routing-strategies) |
| Revenue: Stripe, CSV, API | [Sync revenue](/product/sync-revenue), [Set revenue](/api/management/set-revenue) |
| Key metrics for internal tools | [Internal projects](/product/organizations-and-projects#internal-projects), [Set key metrics](/api/management/set-key-metrics) |
| Customer tags | [Customer tags](/product/customer-tags), [Set customer tags](/api/management/set-customer-tags) |
| Reading data back programmatically | [Get costs](/api/management/get-costs), [Get margin](/api/management/get-margin), [MCP server](/api/management/mcp-server) |
| Every page, as one file for an agent | `https://docs.weflayr.com/llms-full.txt` |


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