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

# Migrate from OpenAI

> A checklist for moving an application that already calls OpenAI: what changes, what does not, and the one change that is not optional.

export const FeatureStatus = ({missing, feature, instead, detail, children}) => <Info>
    <strong>Not available yet: {missing ?? feature}.</strong>
    {instead ?? detail ? ` ${instead ?? detail}` : null} This page documents what
    the product does today. When that changes, this page changes with it.
    {children}
  </Info>;

export const NotDocumented = ({subject, reason}) => <Info>
    <strong>{subject} is not documented here.</strong> {reason} We would rather
    say nothing than describe something you cannot use.
  </Info>;

If your application already calls OpenAI, almost all of it survives the move.
This page is the short list of what does not, so you find the edges by reading
rather than by deploying.

## The two settings

Point your existing OpenAI client at `https://api.hopscotchlabs.ai` and give it a
Hopscotch API key. Nothing else about your code changes.

```text Base URL theme={"theme":{"light":"vitesse-light","dark":"vesper"}}
https://api.hopscotchlabs.ai
```

Your client keeps its own name for these. Change the base URL and change the key,
and leave the rest of your client configuration where it is.

<CodeGroup>
  ```python Python theme={"theme":{"light":"vitesse-light","dark":"vesper"}}
  from openai import OpenAI

  client = OpenAI(
      base_url="https://api.hopscotchlabs.ai/v1",   # was: unset, or the OpenAI default
      api_key="ub_live_YOUR_KEY_HERE",         # was: your OpenAI key
  )
  ```

  ```javascript Node theme={"theme":{"light":"vitesse-light","dark":"vesper"}}
  import OpenAI from "openai";

  const client = new OpenAI({
    baseURL: "https://api.hopscotchlabs.ai/v1",  // was: unset, or the OpenAI default
    apiKey: "ub_live_YOUR_KEY_HERE",        // was: your OpenAI key
  });
  ```
</CodeGroup>

## The one change that is not optional

**Every model id gains a provider in front of it.** `gpt-4o-mini` becomes
`openai/gpt-4o-mini`. This is the change that breaks an otherwise untouched
application, and it breaks loudly rather than quietly: a bare id is refused with
`400` and the code `model_id_not_pinned`, naming the forms it will accept.

That refusal is deliberate. A bare id would have to be resolved to some provider
we happen to carry it under, and picking one for you is a decision about price
and about who sees your prompt that we are not willing to make on your behalf.

Find the ids you can call with `GET /v1/models`, or on the Models screen in the
dashboard. Do not assemble them by hand from a provider's own naming. See
[Models](/concepts/models).

```bash theme={"theme":{"light":"vitesse-light","dark":"vesper"}}
curl https://api.hopscotchlabs.ai/v1/models \
  -H "Authorization: Bearer ub_live_YOUR_KEY_HERE"
```

## What carries over untouched

| What                                                                  | Why it survives                                                                                                                                                                                                                                                                                                                                                 |
| --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Your request bodies                                                   | Forwarded as you sent them, with two named exceptions: `model` is replaced with the model being tried when you asked for a route rather than a model, and `max_tokens` is filled in when you named no ceiling and the provider's own API refuses a request without one. Nothing else is parsed or rebuilt. See [Request parameters](/api-reference/parameters). |
| Your response handling                                                | The reply is the OpenAI chat completion shape.                                                                                                                                                                                                                                                                                                                  |
| Your streaming loop                                                   | Server-sent events in the same format, ending in the same `data: [DONE]`.                                                                                                                                                                                                                                                                                       |
| Your error handling                                                   | The same envelope, the same closed set of `type` values, the same statuses.                                                                                                                                                                                                                                                                                     |
| `max_tokens`, `temperature`, `tools`, `response_format`, and the rest | Parameters travel in the body, which is what gets forwarded.                                                                                                                                                                                                                                                                                                    |

## What is different

**Endpoints.** The operations this site documents are
`POST /v1/chat/completions`, `GET /v1/models` and `GET /v1/models/{id}`. That is
the inventory to port against: if your application calls anything else, this site
does not yet describe what it does here, so treat those call sites as unanswered
rather than as either working or refused. See
[OpenAI compatibility](/get-started/openai-compatibility).

**Headers you send.** Only `Authorization` and `Content-Type` are read. Nothing
else you send reaches the provider, because the upstream request is built from
your body and our own credential with a fresh header set. Organisation and
project headers, and anything in the `x-hopscotch-*` namespace, do not travel.

**Headers you receive.** Provider rate-limit headers are not forwarded, so if you
currently read remaining-requests and reset headers to pace yourself, that input
is gone. Your own limits arrive as `429` responses carrying codes you can branch
on, and the per-key one carries `Retry-After`. See
[Rate limits and spend controls](/concepts/rate-limits-and-spend-controls).

**One extra line on a stream.** A streamed reply carries a
`:x-hopscotch-usage` comment line immediately before `data: [DONE]`. The
server-sent events specification requires a parser to ignore a line starting
with a colon, so a conforming client never sees it. See
[Streaming](/concepts/streaming).

**One extra field in an error.** The error object carries `request_id` alongside
the fields OpenAI defines. It is inside `error` rather than beside it so that a
client reading only the fields it knows ignores it safely.

**Billing.** Credit is prepaid and the platform fee is added when you buy credit,
not when you spend it, so a request deducts the provider's own rate with nothing
on top. A zero balance refuses new requests with `402` and ends in-flight ones.
See [Credits and billing](/concepts/credits-and-billing).

**`max_tokens` is worth sending even where you left it out before.** It sizes
what we hold against your balance while a request runs, so a request without it
is held at the model's published maximum output and reaches your account spend
rate cap sooner.

## A checklist

<Steps>
  <Step title="Prefix every model id">
    Search your code for model names and put the provider in front of each one.
    This is the step that fails the first request if you skip it.
  </Step>

  <Step title="Point the client at the base URL and swap the key">
    Two settings, both above. Keep the key in the environment rather than in
    source.
  </Step>

  <Step title="Find any call to an endpoint this site does not document">
    Anything beyond chat completions and the two catalog reads is undocumented
    here. Decide what those call sites do before you cut over rather than after.
  </Step>

  <Step title="Stop reading provider rate-limit headers">
    If your pacing depends on them, move it onto `429` codes and `Retry-After`.
  </Step>

  <Step title="Send max_tokens">
    Especially on any call path that currently omits it.
  </Step>

  <Step title="Log the request id">
    `x-request-id` is the header the official SDKs already surface on their error
    objects, so this is usually a one-line change. Without it, a support
    conversation starts from a description rather than from a request.
  </Step>

  <Step title="Buy credit before you cut over">
    A verified account with a zero balance refuses every request, which is a
    confusing first result if you were not expecting it.
  </Step>
</Steps>

## What this page does not cover

**Moving from another router.** The differences that matter would be differences
from that product's behaviour, and we do not document other people's products
from the outside.

**Anything about speed or availability.** No figure for latency added by the hop,
for throughput, or for availability has been measured here in a way we are
willing to publish, so this page makes no claim about how the move affects any of
them.
