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

# Migrating

> Two lines to route through Protégé, one field to make it useful.

## From OpenAI

Change the base URL and the key. The request and response bodies are unchanged.

<CodeGroup>
  ```python Python theme={null}
    client = OpenAI(
  -     api_key=os.environ["OPENAI_API_KEY"],
  +     base_url="https://api.protege.sh/v1",
  +     api_key=os.environ["PROTEGE_API_KEY"],
    )
  ```

  ```typescript TypeScript theme={null}
    const client = new OpenAI({
  -   apiKey: process.env.OPENAI_API_KEY,
  +   baseURL: "https://api.protege.sh/v1",
  +   apiKey: process.env.PROTEGE_API_KEY,
    });
  ```
</CodeGroup>

At this point everything works and nothing is measured. Add `task` to make the
calls attributable.

## From Anthropic

The Anthropic SDK does not speak the OpenAI message format, so this is a client
swap rather than a URL swap. Install the OpenAI SDK, point it at Protégé, and pin
the Anthropic model you were already using:

```python theme={null}
resp = client.chat.completions.create(
    model="anthropic/claude-opus-5",
    messages=[{"role": "user", "content": prompt}],
    extra_body={"task": "contract_review"},
)
```

Two differences to expect. Anthropic's top-level `system` argument becomes a
message with `role: "system"`, and `max_tokens` is optional here rather than
required.

Pinning keeps behaviour identical while the task accumulates enough history to
be worth routing.

## From a gateway

If you already front your providers with a gateway, you are most of the way
there: keep your provider-prefixed model strings, change the base URL, and add
`task`.

The difference is what the label buys. A gateway routes on rules you write.
Protégé routes on whether a cheaper model clears that task's eval, which is a
question you cannot answer from request metadata alone.

## Passing `task` through an SDK

The OpenAI SDKs validate request bodies and drop unknown fields, so `task` needs
an explicit escape hatch.

<CodeGroup>
  ```python Python theme={null}
  client.chat.completions.create(
      model="deepseek-v4-flash",
      messages=messages,
      extra_body={"task": "invoice_extraction"},
  )
  ```

  ```typescript TypeScript theme={null}
  await client.chat.completions.create({
    model: "deepseek-v4-flash",
    messages,
    // @ts-expect-error - `task` is a Protégé extension
    task: "invoice_extraction",
  });
  ```

  ```python LangChain theme={null}
  ChatOpenAI(
      base_url="https://api.protege.sh/v1",
      api_key=os.environ["PROTEGE_API_KEY"],
      model="deepseek-v4-flash",
      extra_body={"task": "invoice_extraction"},
  )
  ```

  ```typescript Vercel AI SDK theme={null}
  const protege = createOpenAI({
    baseURL: "https://api.protege.sh/v1",
    apiKey: process.env.PROTEGE_API_KEY,
  });

  await generateText({
    model: protege("auto"),
    prompt,
    providerOptions: { openai: { task: "invoice_extraction" } },
  });
  ```
</CodeGroup>

<Note>
  If `protege.task` comes back absent on a response, the field was stripped
  before it reached us. Check the escape hatch for your SDK above.
</Note>

## A sensible rollout

<Steps>
  <Step title="Shadow one path">
    Point a single low-risk call path at Protégé with the model pinned to whatever
    it uses today. Behaviour is identical; you are only proving the hop works.
  </Step>

  <Step title="Label everything on that path">
    Add `task` to each distinct call. Now the path's spend splits by workload.
  </Step>

  <Step title="Read the split">
    We report spend back per task. The expensive ones are the candidates, and they
    are usually not the ones people guess before they measure.
  </Step>

  <Step title="Write a contract for the top task">
    Define what correct means for that one task, then let routing move it.
  </Step>
</Steps>

<Card title="Getting the task boundaries right" icon="scissors" href="/concepts/projects-and-workloads">
  The step that decides whether any of the numbers mean anything.
</Card>
