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

# Provider Attribution

> Every outbound call to a paid provider names its environment, service and host, so spend splits by dev / staging / production / local in each provider's own dashboard.

## Overview

Every request Brainstormer makes to a provider we hold an account with carries
the environment, the calling service and the host that made it. The provider's
own dashboard can then split spend by environment instead of reporting one
undifferentiated total.

Before this, OpenRouter reported **148M of 150M tokens in a 24-hour window
against an app named "Unknown"**. Two causes:

* `services/knowledge` sent no attribution at all, and it owns graph
  extraction, visual entity extraction and document summarisation — most of the
  platform's token volume.
* The services that did send headers hardcoded `HTTP-Referer:
  https://brainstormer.ai` and `X-Title: Brainstormer V2`, identically on every
  environment, so dev, staging, production and a laptop were one app.

## Where attribution may go

<Warning>
  Attribution goes to **provider APIs we hold an account with**. It never goes
  to a site we scrape or to a customer-controlled endpoint.
</Warning>

| Destination | Attribution | Why |
| - | - | - |
| OpenRouter, Gemini, Stripe, Meta Graph, Apify, Cloudflare API, LlamaParse, Jina, ElevenLabs | **Yes** | We hold the account and pay the bill |
| Sites we scrape (`url-fetcher.service.ts`, the Cloudflare render payload) | **No** | They must keep their browser-like / bot User-Agent, or targets block us |
| Customer-configured custom-tool URLs, webhooks | **No** | Our environment name and container hostname are internal detail |
| Meta's media CDN (`lookaside.fbsbx.com` and friends) | **No** | A Meta-supplied URL on an allowlisted host, not an endpoint we call by account |

## What is sent

One helper builds every value: `packages/shared/src/attribution.ts` (pure — no
IO, no `node:*`), wrapped per service by `src/utils/attribution.ts`, which reads
`os.hostname()` and the environment.

### Every paid provider

```
User-Agent: Brainstormer/1.0.0 (dev-server; knowledge; 4f2b9c1a7e30)
```

`Brainstormer/<version> (<environment>; <service>; <host>)`.

### OpenRouter, additionally

```
X-Title:      Brainstormer dev-server / knowledge
HTTP-Referer: https://bsio2.brainstormer.io/svc/knowledge
```

OpenRouter documents `HTTP-Referer` as an app's **identity** ("your app's URL
becomes its unique identifier") and `X-Title` as its **display label**, which
cannot create an app on its own. The referer is built from `WEB_APP_URL`, which
already differs per environment, with a `/svc/<service>` path so the identity
agrees one-to-one with the title.

<Note>
  OpenRouter's docs do not say whether a path is part of the app key or whether
  the referer is normalized to its origin. If paths key separately, the
  dashboard shows one row per environment **and** service. If not, several
  titles collapse onto one origin row — which is what a path-less referer would
  have produced anyway.
</Note>

### Stripe, additionally

Stripe has first-class attribution, so it gets that instead of only a
User-Agent: the SDK is constructed with `appInfo: { name, version, url }`,
which Stripe surfaces in its request logs.

```ts theme={null}
new Stripe(key, {
  apiVersion: "2026-04-22.dahlia",
  appInfo: stripeAppInfo(), // { name: "Brainstormer production / auth", … }
});
```

### Gemini

Gemini offers no first-class app attribution — its console splits by project
and credential, not by request — so the embedding client sends the User-Agent
through `httpOptions.headers`. The SDK's `x-goog-api-client` header, which
Google parses for client telemetry, is deliberately left untouched.

## Where the environment comes from

`SENTRY_ENVIRONMENT`. It is already set per environment and differs correctly
(`dev-server` on the dev box; `docker-compose.yml` defaults it to `production`).

<Warning>
  Never use `NODE_ENV`. It is the literal string `production` on dev, staging
  **and** production, so it discriminates nothing.
</Warning>

An unset `SENTRY_ENVIRONMENT` resolves to `local`. Compose supplies a value to
every deployed container, so "unset" means the process is not running under our
compose files — a developer's laptop or an offline eval, which is exactly what
`local` should mean.

Offline eval harnesses in `services/knowledge/eval/` report their own service
names (`knowledge-eval`, `embedding-eval`), so an eval run is never counted as
production traffic.

## Failure behaviour

The helper never throws. An unreadable hostname, a missing environment, a
malformed `WEB_APP_URL` and an empty service name each degrade to a documented
default — no attribution is far better than a failed provider call. Every
segment is reduced to printable ASCII and bounded at 64 characters, so a value
cannot break a header.

| Input | Absent / unusable value becomes |
| - | - |
| `SENTRY_ENVIRONMENT` | `local` |
| `os.hostname()` | `unknown-host` |
| `WEB_APP_URL` | `https://brainstormer.ai` (the historical hardcoded value) |
| service name | `unknown-service` |

## Privacy

`os.hostname()` is the container id in Docker, which is what distinguishes two
replicas and a laptop from a deployed box. On a customer-hosted deployment a
hostname can be mildly identifying. It carries no secret and is sent only to the
paid providers above, never to a customer endpoint or a scrape target.

## Adding a call site

1. Import your service's wrapper: `import { openRouterHeaders, providerAttributionHeaders } from "../utils/attribution"`.
2. Spread it into the request headers — `...openRouterHeaders()` for OpenRouter, `...providerAttributionHeaders()` for anything else.
3. If the destination is a scrape target or a customer-controlled URL, send nothing and say why in a comment.

Each service carries a guard test (`attribution-call-sites.test.ts`) listing its
call sites; removing a spread from one of them fails the build.


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