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

# Python SDK

> Install, initialize, and attribute calls with the Orbitrage Python SDK.

The Python SDK is a thin **convenience layer**: it points your OpenAI client at
the Orbitrage gateway and tags every request with a trace id. No runtime
dependencies, no background threads, no span exporters.

Orbitrage is **OpenAI-format only** — to use Claude, Gemini, Grok, etc., just
name the model; you don't use that provider's SDK.

<CardGroup cols={2}>
  <Card title="PyPI" icon="python" href="https://pypi.org/project/orbitrage/">
    `pip install orbitrage`
  </Card>

  <Card title="License" icon="scale-balanced">
    Apache-2.0 · Python 3.9–3.13
  </Card>
</CardGroup>

## Install

```bash theme={null}
pip install -U orbitrage
```

`openai` (>=1.0) is the only peer you need — Orbitrage speaks OpenAI format:

```bash theme={null}
pip install -U orbitrage openai
```

## Initialize

Call `init()` **once, at the very top of your program, before importing your LLM
client.**

```python theme={null}
import os
import orbitrage

orbitrage.init(os.environ["ORBITRAGE_API_KEY"])

from openai import OpenAI
client = OpenAI()        # base_url + trace headers are set for you
client.chat.completions.create(
    model="minimax-m3",
    messages=[{"role": "user", "content": "hi"}],
)
```

### What `init()` does

1. Generates a stable **trace id** for the process and adds it to every request
   as `x-orbitrage-run-id`, so all your calls group into one run.
2. Patches the `OpenAI` and `AsyncOpenAI` constructors to default `base_url` to
   `https://api.orbitrage.ai/v1` and inject the trace headers. **Your explicit
   `base_url` or `api_key` always wins.**
3. Sets `OPENAI_BASE_URL` as a safety net for clients constructed with no
   arguments.

It does **not** load OpenTelemetry or Traceloop, and it exports nothing — the
gateway is the source of truth.

### `init()` parameters

<ParamField path="api_key" type="str" required>
  Your `orb_` key. Falls back to `ORBITRAGE_API_KEY` if omitted.
</ParamField>

<ParamField path="user_id" type="str">
  End-user id to attribute calls to (sets `x-orbitrage-end-user-id`).
</ParamField>

<ParamField path="base_url" type="str">
  Override the gateway URL. Defaults to `https://api.orbitrage.ai/v1` (or
  `ORBITRAGE_BASE_URL`).
</ParamField>

<ParamField path="enabled" type="bool" default="True">
  Set `False` to make `init()` a no-op (e.g. in tests).
</ParamField>

<ParamField path="quiet" type="bool" default="False">
  Suppress the startup log line.
</ParamField>

<Note>
  `init()` is idempotent — calling it more than once is safe and has no effect
  after the first call. Legacy v0.4 keyword arguments (`disable_batch`,
  `capture_content`, `instruments`, …) are accepted and ignored.
</Note>

## Per-end-user attribution

```python theme={null}
# One user for the whole process:
orbitrage.init(api_key, user_id=current_user.id)

# Switch users per request in a long-running server.
# The next OpenAI() client you construct picks up the new id:
orbitrage.set_user(current_user.id)
```

<Warning>
  After `set_user()`, construct a **new** client for the change to take effect —
  already-built clients have already copied their headers.
</Warning>

## Forcing a model

```python theme={null}
# Pin a model (no scoring):
client.chat.completions.create(model="glm-5.2", messages=[...])

# Route to the cheapest capable model:
client.chat.completions.create(model="auto", messages=[...])
```

`auto`, `router`, `default`, and `orbitrage` all trigger routing. See
[Routing](/concepts/routing).

<Note>
  Pinning a frontier model (`claude-*`, `gpt-*`, `gemini-*`, `grok-*`) requires an
  enabled key for that vendor — the call runs on your provider account and
  Orbitrage bills \$0. Without one it raises a `403` with code `byok_key_required`.
  See [BYOK](/concepts/byok).
</Note>

## API surface

| Symbol                                                                     | Purpose                                                |
| -------------------------------------------------------------------------- | ------------------------------------------------------ |
| `init(api_key, *, user_id=None, base_url=None, enabled=True, quiet=False)` | Set up the SDK.                                        |
| `set_user(user_id)`                                                        | Bind/unbind the end-user id for subsequent clients.    |
| `set_association_properties(props)`                                        | v0.4 compat — extracts `user_id` and calls `set_user`. |
| `flush(timeout_ms=5000)` / `shutdown()`                                    | No-ops — the gateway persists synchronously.           |
| `workflow`, `task`, `tool`, `agent`                                        | No-op decorators kept for v0.4 compatibility.          |

<Note>
  The decorators and lifecycle functions exist so code written for v0.4 keeps
  importing without changes. The gateway captures the same data regardless of
  decorator markup.
</Note>

## Observability-only mode

You don't have to let Orbitrage change your model. Pin explicit models (or use
[BYOK](/concepts/byok)) and you still get full observability — every call is
traced because it flows through the gateway:

```python theme={null}
import orbitrage
orbitrage.init(os.environ["ORBITRAGE_API_KEY"])  # trace everything, route nothing
```

## Using a framework?

LangChain, LangGraph, CrewAI, Agno, and others use the OpenAI client under the
hood, so they're captured transparently. See
[Integrations](/integrations/openai) for per-framework setup.
