> ## 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.

# Routing profiles

> An ordered list of your own models, requested as profile/<identifier> in the model field, so a fallback order lives in one place.

A routing profile is your own ordered list of models, saved once and requested
as if it were a single model. Put its identifier in the `model` field and the
order you set is the order that gets tried.

## What a profile is

* A **name**, which is for you. It is what you pick it out of a list by, and
  you can change it whenever you like.
* An **identifier**, which is what your code sends. See below for why it never
  changes.
* An **ordered list of models**: one primary, and up to four backups.

A profile is created, renamed, and archived from your workspace's Profiles
screen.

## Requesting a profile

Put `profile/` and the identifier in the `model` field, exactly where a model
id would otherwise go:

```bash theme={"theme":{"light":"vitesse-light","dark":"vesper"}}
curl https://api.hopscotchlabs.ai/v1/chat/completions \
  -H "Authorization: Bearer ub_live_XXXXXXXX" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "profile/customer-support",
    "messages": [{"role": "user", "content": "Say hello."}]
  }'
```

```python Python theme={"theme":{"light":"vitesse-light","dark":"vesper"}}
completion = client.chat.completions.create(
    model="profile/customer-support",
    messages=[{"role": "user", "content": "Say hello."}],
)
```

Nothing else about the request changes. A profile is a value in the same
field a model id occupies, not a different kind of call. See
[Models](/concepts/models) for how a plain model id is shaped.

## The identifier does not change

A profile's name is yours to change at any time. Its identifier is not: once a
profile is created, its identifier is fixed for the life of that profile.

The reason is what each one is for. The name is how you find a profile in a
list, and nothing downstream should care if you rename it as your own
vocabulary changes. The identifier is what is already sitting in your code, in
a CI config, in a teammate's script you will never read. If `profile/nightly-batch`
could be pointed at a different identifier later, the same three words your
code sends today could come to mean a different list of models tomorrow,
with nothing in your code telling you it happened. Fixing the identifier is
what keeps that from being possible: the name can move, so the meaning can be
kept current; the identifier cannot, so the meaning can be trusted.

Archiving a profile does not free its identifier, either. Once an identifier
has been used in a workspace, a new profile cannot claim it again.

## What the order means

A profile's models are tried strictly in the order you set. A request goes to
the first model in the list. Only when that attempt answers with a status
that means the model itself failed to serve it, rather than a fault in the
request, does the request move on to the next model in your order, and so on.
Any other failure, such as a malformed request, comes back to you as is:
trying the same bad request against a different model would not fix it.

A model in your list that cannot be served at all right now is skipped rather
than counted as a failed attempt, as long as at least one model in the list
still can be. Only when every model you named is unreachable does the request
come back as a refusal, covered next.

## An archived or unknown identifier

Requesting `profile/<identifier>` for a profile that has been archived, or one
that never existed, is refused identically either way: HTTP 503, code
`no_provider_configured`.

```json theme={"theme":{"light":"vitesse-light","dark":"vesper"}}
{
  "error": {
    "message": "Nobody serves \"profile/customer-support\": this workspace has no routing profile with that slug, or it has been archived. Its Profiles screen lists the ones that exist.",
    "type": "api_error",
    "param": null,
    "code": "no_provider_configured",
    "request_id": "00000000-0000-4000-8000-000000000000"
  }
}
```

There is no separate error for "archived" versus "never created", and there is
no way to restore an archived profile: only to create a new one, under a new
identifier. See [Errors and refusals](/concepts/errors) for the envelope every
failing route shares.

## When no model in the profile can be served

A profile whose every member is out is refused rather than served, and the code
says which kind of "out" it was. Every one of these names the profile slug you
sent and no member of it: the list behind the slug is your own document, and your
Profiles screen shows it in order.

| Status | `code`                         | Meaning                                                                                                                                                                                                                                                                                                                                 |
| ------ | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 403    | `model_not_permitted`          | Of the members that could otherwise have been served, this workspace's permitted set names none. A workspace owner has to widen the set; retrying will not help. This is also the answer when the profile additionally holds a member we could not serve ourselves, because lifting the set is still what makes the request go through. |
| 503    | `no_provider_configured`       | None of the members can be served right now, or none of them could be routed at all. Ours to sort out where it is a routing gap. Retry with backoff.                                                                                                                                                                                    |
| 503    | `model_permissions_unreadable` | The document saying which models this workspace may call could not be read, so no member could be admitted rather than the set being treated as unrestricted. Ours. Retry shortly.                                                                                                                                                      |

```json theme={"theme":{"light":"vitesse-light","dark":"vesper"}}
{
  "error": {
    "message": "Every model in \"profile/customer-support\" that could be served is outside this workspace's permitted set, so the profile has nothing left to try. Its access is limited to a named set of models; a workspace owner can say which.",
    "type": "permission_error",
    "param": null,
    "code": "model_not_permitted",
    "request_id": "00000000-0000-4000-8000-000000000000"
  }
}
```

A member skipped along the way is not reported individually. The refusal is about
the profile as a whole, and the request id is what a support conversation starts
from.

## Limits

|                        |                                                                                                                    |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Models per profile     | 1 to 5. One primary, up to four backups. Each must be a model you can currently call, and none may be named twice. |
| Identifier             | 1 to 40 characters: lowercase letters, digits, and dashes, and it cannot start or end with a dash.                 |
| Name                   | 1 to 120 characters.                                                                                               |
| Profiles per workspace | 20 live at a time. Archiving one frees a slot, not its identifier.                                                 |

## The fixed id `hopscotch/auto`

`hopscotch/auto` is one string you can put in a client's configuration and leave
there. It names whichever of your routes you have made the default, so the route
behind it changes on the Routing screen without touching the code that calls us.
Once it resolves, everything after that is the ordinary route path: the same
order, the same failover, and the same charge for the step that answered.

Until you have a default route it still answers, from a list we publish for the
**cost tier** your workspace is on. There are three, `low`, `balanced` and
`high`, and a workspace that has never chosen is on `balanced`. The tier is a
shortlist and not a ceiling: `low` is cheaper because the models on it are
cheaper, and nothing about the tier limits what a request may spend. Setting a
default route of your own takes the id off our lists entirely, and yours answers
instead.

Two things it will not do. It never quietly serves you from a dearer tier than
the one you picked: if the tier you are on has no list, or nothing on it can be
served at that moment, the request is refused with `404`
`model_not_available` and the message names your tier and both ways out. And it
never falls back to a tier or a route you did not choose, so a refusal here
means the same thing every time.

## Which key a route runs on

A route can also say whose provider key its steps run on, which matters only if
you have [brought your own key](/concepts/bring-your-own-key) for a provider we
carry. The choice is per route and it has three values: follow your workspace's
own rule, which is what every route does unless you say otherwise; run on your
own key; or run on ours. It is the way to keep one route off your own provider
quota while the rest of your traffic stays on it. A step that cannot honour the
choice is skipped, with the reason recorded, rather than served on the other
key.

## Who can do what

Creating a profile, renaming it, replacing its model list, or archiving it all
take the same permission: only the workspace owner and admins may do them. Any
member of the workspace, including a developer or a viewer, may see the
profiles that exist and request them, the same as any key in the workspace
already can. See [Workspaces and members](/concepts/workspaces-and-members) for
the full role table.

## Next

<CardGroup cols={2}>
  <Card title="Models" href="/concepts/models">
    How a model id is shaped, and how to discover which ones you can call.
  </Card>

  <Card title="Errors and refusals" href="/concepts/errors">
    The one error body the whole API uses, and how to read it.
  </Card>
</CardGroup>
