# Portkey gateway

> Keep Portkey as your gateway and add sference as a provider: one control plane for keys, budgets, and routing, with sference serving the models over its OpenAI- and Anthropic-compatible surfaces.

Source: https://sference.com/docs/sference-code/portkey

If your team already runs a [Portkey](https://portkey.ai) gateway, add sference as a provider instead of standing up a second control plane. Portkey keeps virtual keys, budgets, guardrails, and logs; sference serves the models behind it.

The two shapes line up:

| Client speaks | Portkey provider | sference surface |
| --- | --- | --- |
| OpenAI Chat Completions | `OpenAI` | `POST /v1/chat/completions` |
| Anthropic Messages (Claude Code, `anthropic` SDKs) | `Anthropic` | `POST /v1/messages` |

Requests flow **client → Portkey → sference**. Portkey authenticates to sference with a `sk_...` key stored in the provider; clients authenticate to Portkey with a Portkey API key and never see the sference key.

> **Model ids are catalog ids**
>
> sference has no `gpt-*` or `claude-*` aliases. Every request names a catalog id such as `zai-org/GLM-5.3`, `moonshotai/Kimi-K3`, or `deepseek-ai/DeepSeek-V4-Flash-0731`. `GET https://api.sference.com/v1/models` lists what your key can call.

## Add sference as a provider

In the Portkey dashboard, go to **Model Catalog → Add Provider**. Add one provider per surface:

| Field | OpenAI surface | Anthropic surface |
| --- | --- | --- |
| **Provider** | `OpenAI` | `Anthropic` |
| **Custom Host** | `https://api.sference.com/v1` | `https://api.sference.com/v1` |
| **API Key** | your sference `sk_...` key | your sference `sk_...` key |
| **Slug** | `sference` | `sference-anthropic` |

> **The custom host must include /v1**
>
> Portkey appends the endpoint path to the custom host — `/chat/completions` for the OpenAI provider, `/messages` for the Anthropic provider. A host without `/v1` produces a `404` on every request. This is the opposite of the client-side convention, where sference's Anthropic surface takes the bare host and the client adds `/v1/messages` itself.

## Provision the models

Under **Model Provisioning → Add Model**, leave **Model Type** on **Custom Model** and add the catalog ids you intend to call, using each id exactly as `GET /v1/models` returns it:

```text
zai-org/GLM-5.3
moonshotai/Kimi-K3
deepseek-ai/DeepSeek-V4-Flash-0731
```

Portkey combines the provider slug and the model id in the request's `model` field. Only the first segment is the provider selector; a catalog id may contain another `/`:

```text
@sference/zai-org/GLM-5.3
@sference-anthropic/zai-org/GLM-5.3
```

The first selects the OpenAI provider, the second the Anthropic provider.

## Create a Portkey API key

Go to **API Keys → Generate** and scope the key to the sference provider(s). Clients use this key as their bearer token; Portkey uses the stored sference key upstream.

## Forward client headers

Portkey does not forward client headers by default. Two matter on the Anthropic path:

- `anthropic-beta` — gates beta features on the Messages surface, including extended thinking.
- Any `x-`-prefixed header your clients send.

Forward them with a saved **Config** (**Configs → Create**):

```json
{
  "provider": "@sference-anthropic",
  "forward_headers": ["anthropic-beta"]
}
```

Save it and copy the ID (Portkey Config IDs start with `pc-`).

> **A pinned Config routes every request carrying it to sference**
>
> `provider` names one fixed target and there is no `strategy`, so Portkey makes no routing decision: an `@slug/model` string or an `x-portkey-provider` header on the request is discarded. That is what you want for Claude Code, which sends bare model names — but it must not be the default Config on a Portkey key shared with other applications. Use a dedicated key, or the `passthrough` form below.

If you need one key to serve several providers, use an unpinned Config instead:

```json
{
  "passthrough": true,
  "forward_headers": ["anthropic-beta"]
}
```

`passthrough` makes Portkey resolve the provider from the request, so callers must name it (`@sference-anthropic/zai-org/GLM-5.3`). Claude Code cannot name a provider in its bare model string, so the unpinned form also needs `x-portkey-provider: @sference-anthropic` alongside the Config — inline config, which some workspaces block (`block_inline_config`). The pinned Config is the supported setup for Claude Code.

## Connect Claude Code

Point Claude Code at Portkey. The base URL is the Portkey host **without** `/v1` (Claude Code appends `/v1/messages`), the auth token is the Portkey API key, and the Config ID goes in the custom headers:

```json
{
  "env": {
    "ANTHROPIC_BASE_URL": "https://api.portkey.ai",
    "ANTHROPIC_AUTH_TOKEN": "<portkey-api-key>",
    "ANTHROPIC_CUSTOM_HEADERS": "x-portkey-config: <portkey-config-id>",
    "ANTHROPIC_DEFAULT_OPUS_MODEL": "zai-org/GLM-5.3",
    "ANTHROPIC_DEFAULT_SONNET_MODEL": "zai-org/GLM-5.3",
    "ANTHROPIC_DEFAULT_HAIKU_MODEL": "zai-org/GLM-5.3",
    "ANTHROPIC_DEFAULT_FABLE_MODEL": "zai-org/GLM-5.3"
  }
}
```

The tier variables matter here: Claude Code otherwise sends `claude-*` names, and sference serves catalog ids. With the pinned Config, the model string is forwarded as written, so a catalog id reaches sference unchanged.

Do not also set `x-portkey-provider` when using a Config — the Config already selects the provider.

## OpenAI-shaped clients

Clients that use the OpenAI SDK go through the `@sference/` provider:

```python
from openai import OpenAI

client = OpenAI(
    base_url="https://api.portkey.ai/v1",
    api_key=PORTKEY_API_KEY,
    default_headers={"x-portkey-config": PORTKEY_CONFIG_ID},
)

resp = client.chat.completions.create(
    model="@sference/zai-org/GLM-5.3",
    messages=[{"role": "user", "content": "Summarize this thread."}],
)
```

Request parameters are forwarded to sference, so sference's [priority and flex tiers](https://sference.com/docs/guides/priority-tier) are set in the request body as usual:

```python
resp = client.chat.completions.create(
    model="@sference/zai-org/GLM-5.3",
    service_tier="priority",
    messages=[{"role": "user", "content": "Summarize this thread."}],
)
```

As with any client, priority is org-entitled (reseller organizations get a `400`), `/v1/messages` takes no `service_tier`, and batch rejects priority.

## Verify the hop

- **Portkey logs** show the request, the Config ID, and the provider; a successful response means the custom host and key are right.
- **sference usage** shows the traffic under your `sk_...` key, broken down by model, endpoint, and tier.
- **`GET /v1/models`** through the proxy lists the provisioned entries; call one with `max_tokens: 1` before rolling it out.

## Limits and caveats

- **Custom host includes `/v1`.** Portkey appends the endpoint path; a host without it 404s.
- **Catalog ids only.** Provision and call sference catalog ids; map Claude Code's tier names with `ANTHROPIC_DEFAULT_*_MODEL`.
- **A pinned Config force-routes its key.** Keep it on a dedicated Portkey key, or use `passthrough`.
- **Anthropic surface is realtime only.** Flex, priority, background responses, and batches are OpenAI-shaped.
- **Header forwarding is opt-in.** Portkey drops client headers unless `forward_headers` (or `x-portkey-forward-headers`) names them; a missing or misspelled entry is silent.
- **Inline config may be blocked.** Workspaces with `block_inline_config` require a saved Config.
- **Batches, streams, and background responses are sference surfaces**, not Portkey ones. They are reachable directly with the same `sk_...` key; see [Batch inference](https://sference.com/docs/guides/batches) and [Streams](https://sference.com/docs/guides/streams).
