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

# Server Instrumentation

> Auto-stamp journey ids onto your AI traces with dataframer-journey

`dataframer-journey` is a small Python library. It reads the journey id off each incoming request and stamps it onto every Langfuse trace or LangSmith run your service creates, so you never pass the id around by hand. The id itself comes from the [browser SDK](/docs/user-signals/browser-sdk).

## Install

```bash theme={null}
pip install "dataframer-journey>=0.2.0"
```

It has no dependencies of its own. It hooks into `requests`, `httpx`, `langfuse` and `langsmith` when they are installed.

## Set up

<Tabs>
  <Tab title="FastAPI / Starlette">
    ```python theme={null}
    from dataframer_journey import instrument

    app = FastAPI()
    instrument(app)
    ```
  </Tab>

  <Tab title="Django">
    Add the middleware, then call `instrument()` once at process start (an `AppConfig.ready()` is a good place):

    ```python theme={null}
    MIDDLEWARE = [
        ...,
        'dataframer_journey.django.JourneyIdMiddleware',
    ]

    import dataframer_journey
    dataframer_journey.instrument()
    ```
  </Tab>
</Tabs>

From then on, three things happen on every request:

1. **Read in**: the journey id is taken from the request (the `X-Journey-Id` header or the SDK's cookie) and kept in a Python context variable for the rest of that request.
2. **Passed along**: every outgoing `requests` or `httpx` call carries the id forward, so a chain of services stays connected.
3. **Stamped on traces**: every Langfuse trace and every LangSmith run created during the request gets `metadata.journey_id`, plus a `journey:<id>` tag.

## Non-HTTP entry points

Queue consumers, scheduled jobs, raw `ThreadPoolExecutor` or `run_in_executor` work and WebSocket handlers do not pass through the middleware, so hand the id over from your payload:

```python theme={null}
from dataframer_journey import with_journey

with with_journey(payload["journey_id"]):
    ...  # traces created here get stamped; outbound calls carry the id
```

<Warning>
  **Langfuse SDK 3.0 to 3.8 is not stamped.** Those releases have no way to attach attributes to every trace, so the library logs a warning and skips them; upgrade to 3.9.0 or newer. Langfuse v2 and v4 and every LangSmith release are stamped. If you cannot upgrade, call `get_journey_id()` and stamp the trace yourself, as in [Without this library](#without-this-library).
</Warning>

## Without this library

For a backend in another language, or a tracing setup this library does not stamp, write the id onto the trace yourself. DataFramer needs one thing: the value of the request's `X-Journey-Id` header, stored under the key `journey_id` in:

* **Langfuse**: the trace's metadata, not only a span's
* **LangSmith**: the root run's metadata

The browser SDK sends that header only to the URLs in `propagateHeaderTo`, so set it even when your backend shares the page's origin, for example `propagateHeaderTo: ['/api']`. Work that leaves the request, such as a queue job or a call to another service, needs the id passed along with it.

## CORS

This library only reads the id it is given. If your frontend and backend are on different origins, the browser SDK must send the id as a header; see [Getting the journey id to your own backend](/docs/user-signals/browser-sdk#getting-the-journey-id-to-your-own-backend).

## Next steps

<Card title="Browser SDK" icon="browser" href="/docs/user-signals/browser-sdk">
  Generate the journey id in your frontend and send user events.
</Card>


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