> ## 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 joins the cost of every LLM call to the revenue it produced, per customer and per feature. To do that it needs three things:

1. The SDK started once per process, with your AI provider SDKs auto-instrumented.
2. Every LLM call running inside a scope that carries a `feature_name` (why the call happens) 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.

Everything on the dashboard is derived from those three: cost per feature, margin per customer, and the optimisation engine, which is where most of the value is. That engine works from the real prompts your app sends: it replays them against cheaper models to find one that answers as well, and looks for prefixes your provider could have cached but did not.

Get the naming right and the rest follows.

## 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.** No model swap, no prompt edit, no parameter change. You are adding telemetry around existing calls.
* **Name the features 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 `auto_instrument` call at startup, 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`, `ai`) | Picks the `ai_sdks` values and the packages to install (step 3)                           |
| AI framework, if any (LangChain, LlamaIndex, Haystack, CrewAI, LangGraph, Mastra, Vercel AI SDK)                                                                 | Changes what you instrument (step 3)                                                      |
| Every process that makes LLM calls: web server, background workers, cron jobs, serverless functions, scripts                                                     | Each one needs its own startup call (step 3) and short-lived ones need `flush()` (step 7) |
| Whether calls go to a provider's own endpoint or through a gateway / OpenAI-compatible base URL                                                                  | Decides `provider_name_override` (step 7)                                                 |
| Where the request's user, tenant, organisation or workspace is available                                                                                         | Fills `customer_name` (step 6)                                                            |
| How secrets are provided (`.env`, secret manager, platform config)                                                                                               | Where `WEFLAYR_API_KEY` goes (step 2)                                                     |
| 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 - API key

The user creates a project API key at `https://app.weflayr.com/api-keys/`.

Expose it as `WEFLAYR_API_KEY` using whatever mechanism the codebase already uses for secrets. Add it to `.env.example` (or the equivalent) so the next developer knows it exists. The SDK reads it from the environment; only pass `api_key` explicitly if the codebase never uses environment variables.

```bash .env theme={null}
WEFLAYR_API_KEY=your-api-key
```

Use a dedicated `WEFLAYR_API_KEY` per environment. One API key means one Weflayr project, so a staging deployment sharing the production key mixes its costs into the same dashboard and skews what the optimisation engine sees. If the codebase gives you no way to tell environments apart, set the key for production only.

## Step 3 - Install and auto-instrument

Both SDKs run server-side only, on Node.js or Python.

### Pick the packages

| 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                       | Python only: `mistral`         | see "OpenAI-compatible endpoints" below                                      | `weflayr mistralai`                                               |
| Vercel AI SDK                 | Node only: `vercel_ai_gateway` | `weflayr ai @ai-sdk/otel`                                                    | see "OpenAI-compatible endpoints" below                           |

Install with the tool the repository already uses (`npm`/`pnpm`/`yarn`, `pip`/`uv`/`poetry`).

### If the codebase uses an AI framework

**Instrument the provider SDK underneath the framework, not the framework.** LangChain, LlamaIndex, Haystack, CrewAI, LangGraph and Mastra all call the provider SDK in the end, so the provider instrumentation catches every call the chain or agent makes, including tool-calling loops. There is nothing framework-specific to install.

The one exception is the **Vercel AI SDK** in Node.js, which Weflayr instruments natively:

* `ai` v7: `autoInstrument({ aiSdks: 'vercel_ai_gateway' })` and install `@ai-sdk/otel`.
* `ai` v5 or v6: `autoInstrument({ aiSdks: [] })` and pass `experimental_telemetry: { isEnabled: true }` on every call.
* Calls made through a provider package with the user's own key (for example `@ai-sdk/openai`) are attributed to that provider and priced at its rates. Calls through the gateway are priced at the gateway's listed price plus its \~3% markup.

### Where to put the call

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

### If calls go through an OpenAI-compatible endpoint

Some providers are reached through the OpenAI SDK with a custom `base_url`. Instrument `openai`, then set `provider_name_override` on the metadata scope so the call is priced with the right provider's rates (step 7):

* **Mistral in Node.js**: OpenAI SDK against `https://api.mistral.ai/v1`, with `providerNameOverride: weflayr.AIProviderName.MISTRAL`.
* **Vercel AI Gateway in Python**: OpenAI SDK against `https://ai-gateway.vercel.sh/v1`, with `provider_name_override=weflayr.AIProviderName.VERCEL_AI_GATEWAY`.
* **Azure OpenAI**: use the OpenAI SDK's `AzureOpenAI` client. Weflayr detects the Azure endpoint on its own, so no override is needed.

### 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.5',
    messages: [{ role: 'user', content: 'Hello!' }],
    stream: true,
    stream_options: { include_usage: true },
  });
  ```

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

## Step 4 - Name the features

A **feature** is the product-side job that caused the LLM call: something the user would recognise, something you would put on a pricing page or a roadmap line. It is the row you will read on the 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 Cost page tells you nothing |
| Right    | `support-chat`, `invoice-extraction`, `weekly-digest`, `onboarding-summary` | One row per thing the product does                           |
| Too low  | `summarise-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 split the steps with `extra_tags` (step 7). 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-summariser-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.

## Step 5 - Place the metadata scope

`propagate_metadata` / `propagateMetadata` opens a scope. Every instrumented LLM call made inside it, 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>

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`

`customer_name` is required. 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: organisation, 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 organisations, do not use the individual user: the 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 3). 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](/internal-projects), where the equivalent field is your own user rather than a paying customer.

## Step 7 - The other parameters, and when they matter

### `provider_name_override` / `providerNameOverride`

Tells Weflayr which provider to bill the call to when the SDK on the wire is not the provider being paid. Get this wrong and the call is priced at the wrong rates, so cost and margin are wrong.

Accepted values: `OPENAI`, `AZURE_OPENAI`, `ANTHROPIC`, `BEDROCK`, `COHERE`, `ELEVENLABS`, `MISTRAL`, `VERCEL_AI_GATEWAY`, `GEMINI`, `VERTEX`.

| Situation                                                  | Set it?                                                                                                                         |
| ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Provider's own SDK against its own endpoint                | No. Detected automatically                                                                                                      |
| Azure OpenAI through the OpenAI SDK                        | No. The Azure endpoint is detected                                                                                              |
| LangChain / LlamaIndex / CrewAI on top of a provider SDK   | No. The underlying SDK is detected                                                                                              |
| Mistral through the OpenAI SDK (Node.js)                   | Yes: `MISTRAL`                                                                                                                  |
| Vercel AI Gateway through the OpenAI SDK (Python)          | Yes: `VERCEL_AI_GATEWAY`                                                                                                        |
| Another OpenAI-compatible endpoint that is not in the list | Not supported. Those calls are priced as OpenAI, which is wrong. Flag it in your TODO list and tell the user to contact Weflayr |

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

### `default_tags` / `defaultTags`

Set once in `auto_instrument`, stamped on every call from that process. Use them for what is constant for the whole process: environment, service name, region, release version. The environment tag is the one that pays off first, because it lets you keep staging traffic out of your production numbers.

### `export_mode` / `exportMode` and `flush()`

`"batch"` (the default) buffers spans and is right for long-running servers. In short-lived runtimes (Lambda, scripts, one-off jobs) buffered spans can be lost on exit, so use `"immediate"` and call `flush()` before the process ends.

<CodeGroup>
  ```js Node.js theme={null}
  await weflayr.flush();
  ```

  ```python Python theme={null}
  weflayr.flush()
  ```
</CodeGroup>

## Step 8 - Verify

Run the app and make one real LLM call through each feature you instrumented, then check the data arrived:

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

Your feature names and customer names should be listed. It may take 1 to 2 minutes for the first data to go through. Then open the [Cost](/product/cost) page in the dashboard to see the spend.

If nothing arrives:

| Symptom                                               | Cause to check                                                                                                                                                                        |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No calls at all                                       | The provider SDK was loaded before `auto_instrument`, `auto_instrument` never ran in that process, or the Node.js app is ESM and the `import-in-the-middle` hook was never registered |
| 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`                                                                                                                               |
| 401 on the curl                                       | Wrong or revoked API key                                                                                                                                                              |

## Step 9 - Questions to ask the user

Four 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: ask in plain words, without the Weflayr terms, and use the explanations below only where the user needs one to answer. Do not guess your way past questions 2 and 3. Once they are answered the setup is complete and the user is live.

**1. Is this a product you sell, or an internal tool?**
A normal project tracks AI cost against revenue. An [internal project](/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. The project type is chosen in the dashboard, under **Switch project**.

**2. 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](/configure/sync-revenue).
* **Another billing system**: push revenue with the [Revenue API](/api/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 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/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.

**3. 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`).

**4. Review the feature 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](/configure/free-ai-credits).

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

- Installed `weflayr` + <instrumentation packages> (<package manager>)
- Added `<file>`: `auto_instrument` for <providers>, `default_tags` = <tags>
- Wrapped <N> features:
  - `support-chat` - `api/routes/chat.py:42`, customer from `request.user.organisation.name`
  - `invoice-extraction` - `workers/invoices.py:88`, customer from `job.tenant_name`
- Added `flush()` in <short-lived entry points>
- 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/ and set `WEFLAYR_API_KEY` in production
- [ ] Set the project type to <normal / internal>
- [ ] Connect Stripe under Costs & 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 names above and tell me if you want any renamed

## Known gaps

- <calls that could not be instrumented, and why>
- <providers not supported by provider_name_override>
- <anything left unverified>

## After the first day of traffic

- Check the Cost page: every feature you expect should have a row
- Check the Margin page: every customer should have revenue against its cost
- Open the Optimisation engine once enough traffic has accumulated: it will propose cheaper models and prompt-caching changes on its own

## 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 Cost and Margin pages by them

Tell me and I will help with either.
```

## Reference

### `auto_instrument()` / `autoInstrument()`

| Parameter                                           | Type                       | Default                   | Notes                                                                                                                                                                           |
| --------------------------------------------------- | -------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ai_sdks` / `aiSdks`                                | value or list              | required                  | `openai`, `anthropic`, `bedrock`, `cohere`, `google-genai`, `elevenlabs`; `mistral` in Python only; `vercel_ai_gateway` in Node.js only. In Python use the `weflayr.AiSdk` enum |
| `api_key` / `apiKey`                                | string                     | `WEFLAYR_API_KEY`         | Pass explicitly only to override the environment                                                                                                                                |
| `default_tags` / `defaultTags`                      | object                     | none                      | Stamped on every call from this process                                                                                                                                         |
| `export_mode` / `exportMode`                        | `"batch"` or `"immediate"` | `"batch"`                 | Use `"immediate"` in short-lived runtimes                                                                                                                                       |
| `capture_message_content` / `captureMessageContent` | boolean                    | `true`                    | Off strips prompts and completions, and disables the optimisation engine                                                                                                        |
| `mask`                                              | function                   | none                      | Rewrite or drop span attributes before export                                                                                                                                   |
| `warn_on_export_error` / `warnOnExportError`        | boolean                    | `true`                    | Off silences the first-export-failure warning                                                                                                                                   |
| `base_url` / `baseUrl`                              | string                     | `https://api.weflayr.com` | Only for a self-hosted or dedicated endpoint                                                                                                                                    |

### `propagate_metadata()` / `propagateMetadata()`

| Parameter                                         | Type              | Notes                                                              |
| ------------------------------------------------- | ----------------- | ------------------------------------------------------------------ |
| `feature_name` / `featureName`                    | string, required  | The product-side feature (step 4)                                  |
| `customer_name` / `customerName`                  | string, required  | The end customer or tenant (step 6)                                |
| `provider_name_override` / `providerNameOverride` | enum member       | Only when the SDK on the wire is not the provider to bill (step 7) |
| `extra_tags` / `extraTags`                        | object of scalars | Extra dimensions to filter on (step 7)                             |

### Pages to read for more detail

| Topic                                | Page                                                                                                |
| ------------------------------------ | --------------------------------------------------------------------------------------------------- |
| Instrumentation, all options         | [Track your LLM calls](/track-your-llm-calls)                                                       |
| Two providers in one app             | [Multiple AI providers](/examples/multi-provider)                                                   |
| LangChain and other frameworks       | [AI framework](/examples/ai-framework)                                                              |
| Revenue: Stripe, CSV, API            | [Sync Revenue](/configure/sync-revenue), [Revenue API](/api/revenue)                                |
| Key metrics for internal tools       | [Internal projects](/internal-projects), [Key metrics API](/api/key-metrics)                        |
| Customer tags                        | [Customer tags](/configure/customer-tags), [Customer tags API](/api/customer-tags)                  |
| Free AI credits                      | [Free AI credits](/configure/free-ai-credits)                                                       |
| What the dashboard shows             | [Cost](/product/cost), [Margin](/product/margin), [Optimisation engine](/product/cost-optimisation) |
| Reading data back programmatically   | [Get costs](/api/costs), [Get margin](/api/margin), [Weflayr MCP](/mcp-server)                      |
| Every page, as one file for an agent | `https://docs.weflayr.com/llms-full.txt`                                                            |
