Skip to content
Back to skills

Openproxy Chat

CSecurity

Send chat completions through OpenProxy's OpenAI-compatible API — POST /v1/chat/completions, the `provider/model` addressing convention, combos as bare model names, `stream: true` SSE framing, tools / tool_calls passthrough, and reading combo fallback out of a response. Use whenever the user asks to call a model through the proxy, pick a model from GET /v1/models, wire an agent loop to the router, or debug a chat call that 404s with `No active credentials for provider: …`, 400s with `Unknown ...

  • 26 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 2, 2026
ai-agentsrustgobashnodedebuggingapi

Works with

  • claude code
  • cursor
  • cli
  • api

Security analysis

C71/100
  • mediumUses curl or wget to download content
  • criticalExfiltrates credentials via HTTP — exact pattern from Snyk ToxicSkills study
  • criticalSends environment variables or credentials to an external URL

Pro shows the line behind each finding and how to fix it

Scanned October 2, 2026

npx -y skills add quangdang46/openproxy --skill openproxy-chat --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Openproxy Chat?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Openproxy Chat
[![Security: C — Skills Directory](https://www.skillsdirectory.com/api/skills/quangdang46-openproxy-chat/badge)](https://www.skillsdirectory.com/skills/quangdang46-openproxy-chat)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: openproxy-chat
description: Send chat completions through OpenProxy's OpenAI-compatible API — POST /v1/chat/completions, the `provider/model` addressing convention, combos as bare model names, `stream: true` SSE framing, tools / tool_calls passthrough, and reading combo fallback out of a response. Use whenever the user asks to call a model through the proxy, pick a model from GET /v1/models, wire an agent loop to the router, or debug a chat call that 404s with `No active credentials for provider: …`, 400s with `Unknown combo model`, or returns an unexpected `model` field.
---

# openproxy-chat — chat completions through `/v1/chat/completions`

OpenProxy exposes an OpenAI-compatible chat surface on `127.0.0.1:4623`. One endpoint —
`POST /v1/chat/completions` — fronts 40+ providers, with model-level account fallback, combos,
and request/response format translation underneath.

This skill is about getting a chat call right: addressing a model, streaming it, using tools, and
reading back what the router actually did.

## 0 · Preconditions

The server must already be running. Check before anything else:

```bash
curl -sS http://127.0.0.1:4623/health
```

`/health` needs no auth and returns `{"status":"ok", …}`. If it is not `ok`, start the server first
— see the `openproxy` skill (`.agents/skills/openproxy/SKILL.md`) for install and start.

Two different credentials, and this trips people up:

| Surface | Credential | Notes |
|---|---|---|
| `/v1/*` (`/v1/chat/completions`, `/v1/models`) | Proxy API key | `Authorization: Bearer $OPENPROXY_API_KEY`. Mint with `openproxy key add <name> --auto`. |
| `/api/*` (dashboard, settings, combos, usage, logs) | Admin / management key | The key printed once by `openproxy server init`. A proxy key gets `401 invalid_api_key` here. |

For the `curl` examples in this skill you need the **proxy** key:

```bash
export BASE=http://127.0.0.1:4623
export OPENPROXY_API_KEY="$(openproxy key list --robot | jq -r '.data.keys[0].key')"
```

`openproxy key list` masks secrets in human output; `--robot` emits the full `key` value (handle it
as a secret — do not paste it into a commit, a log, or a shared transcript). To mint a fresh one
instead: `openproxy key add my-agent --auto`. The admin key from `server init` also authenticates
`/v1/*`, so one credential can cover both surfaces.

Sections that touch `/api/*` — the `openproxy chat send|stream`, `openproxy usage …`, and
`openproxy logs …` commands — need the **admin** key specifically.

If `/v1/*` answers an unauthenticated request with `404` instead of `401`, the install has
`requireApiKey` off — `/v1` is open on loopback. That is a settings choice, not a bug; see
"Auth" in §8.

## 1 · Pick a model from `GET /v1/models`

Never guess a model id. Read the list the router is actually advertising:

```bash
curl -sS "$BASE/v1/models" -H "Authorization: Bearer $OPENPROXY_API_KEY"
```

```json
{
  "object": "list",
  "data": [
    {"id": "ollama/glm-4.7-flash", "object": "model", "owned_by": "ollama", "root": "glm-4.7-flash"},
    {"id": "dbg-combo",           "object": "model", "owned_by": "combo",  "root": "dbg-combo"}
  ]
}
```

`owned_by` tells you which of the two addressing forms applies:

- `owned_by: "combo"` → a **combo**. Use the id **bare**: `"model": "dbg-combo"`.
- anything else → a **provider model**. Use it with its prefix: `"model": "ollama/glm-4.7-flash"`.

Only models that are *active* and *not disabled* on the Providers page appear here, so a model you
know exists but do not see is disabled — enable it in the dashboard under
`http://127.0.0.1:4623/dashboard/providers`.

Resolve any single id to see exactly what it routes to:

```bash
curl -sS "$BASE/v1/models/info?id=ollama/glm-4.7-flash" -H "Authorization: Bearer $OPENPROXY_API_KEY"
# {"id":"ollama/glm-4.7-flash","provider":"ollama","model":"glm-4.7-flash",
#  "routeKind":"Direct","owned_by":"ollama","kind":"llm","endpoint":"/v1/chat/completions", …}

curl -sS "$BASE/v1/models/info?id=dbg-combo" -H "Authorization: Bearer $OPENPROXY_API_KEY"
# {"id":"dbg-combo","provider":null,"model":"dbg-combo","routeKind":"Combo","owned_by":""}
```

`routeKind` is the ground truth for §3. `endpoint` is the kind→path table: an `llm` or
`imageToText` model posts to `/v1/chat/completions`; an `embedding` model posts to `/v1/embeddings`;
`tts` → `/v1/audio/speech`; `image` → `/v1/images/generations`.

Filter by kind with `GET /v1/models/<kind>` — `image`, `tts`, `stt`, `embedding`,
`image-to-text`, `web`. There is deliberately **no `llm` arm** in `list_models_by_kind`
(`src/server/api/v1_models.rs`), so `GET /v1/models/llm` 404s: an unrecognised kind is
resolved as a model *id* lookup. Use `GET /v1/models` (no kind), which returns `llm` only.

## 2 · Model addressing — the prefix convention

The `model` string is resolved by `get_model_info` (`src/core/model/mod.rs`). The rules, in order:

1. **`combo:<name>`** → always routed as a combo, even if `<name>` is also a provider model. This is
   the unambiguous form; use it whenever a name is ambiguous.
2. **`<provider>/<model>`** → routed directly. The prefix is the provider id (e.g. `ollama`,
   `anthropic`, `openai`). For a custom provider node the prefix is the node's **prefix**, and
   credentials are keyed by the node's **UUID** — see `get_model_info` and the note in the
   `openproxy` skill §4.
3. **bare name** → resolved as, in order:
   - a **combo** with that exact name, or
   - a **model alias** from settings, or
   - a built-in alias (`grok-build`), or
   - a last-resort provider guess from the family prefix: `claude-*` → `anthropic`,
     `gemini-*` → `gemini`, `gpt-*` / `o1` / `o3` / `o4` → `openai`, `deepseek-*` → `openrouter`,
     everything else → `openai`.

Step 3's fallback is why an unqualified `gpt-4o-mini` reaches `openai` even when you only have
credentials for something else. **Always pass the `provider/model` form** unless you mean a combo.

The model string also strips a few annotations before resolution: Claude Code's `<model>[1m]`
context marker, and a `(level)` / `-level` thinking suffix. The real 1M capability travels in the
`anthropic-beta` header, which is forwarded untouched.

## 3 · The request

Minimum body:

```bash
curl -sS "$BASE/v1/chat/completions" \
  -H "Authorization: Bearer $OPENPROXY_API_KEY" \
  -H 'content-type: application/json' \
  -d '{
        "model": "ollama/glm-4.7-flash",
        "messages": [{"role": "user", "content": "Reply with exactly: pong"}]
      }'
```

```json
{
  "id": "chatcmpl-…",
  "object": "chat.completion",
  "model": "glm-4.7-flash",
  "choices": [
    {"index": 0, "message": {"role": "assistant", "content": "pong"}, "finish_reason": "stop"}
  ],
  "usage": {"prompt_tokens": 12, "completion_tokens": 2, "total_tokens": 14}
}
```

A missing or empty `model` is a `400 {"error":{"code":"bad_request","message":"Missing model"}}`.

The same handler is also mounted at `/chat/completions` and `/v1/v1/chat/completions`, so a client
whose base URL is already the bare host (or already ends in `/v1`) still lands on one code path.
`/v1/chat/completions` is the canonical path — use it.

### The same call from the CLI

`openproxy chat` drives the running server. It requires a live server — there is no offline mode.

```bash
openproxy chat models                              # GET /v1/models, first 20 ids + count
openproxy chat send   --model ollama/glm-4.7-flash --prompt "ping"
echo "ping" | openproxy chat send --model my-combo --prompt -
openproxy chat send   --model my-combo --prompt "hi" --system "be terse"
openproxy chat stream --model my-combo --prompt "hi"   # streams until Ctrl+C
```

`--prompt` defaults to `-`, so piping on stdin is the default path. Add `--robot` to any of these
for a stable `openproxy.v1.*` envelope instead of human output — `chat stream --robot` emits one
NDJSON line per chunk with schema `openproxy.v1.chat.event`, and `chat send --robot` emits
`openproxy.v1.chat.response`.

**Auth differs by subcommand, which is a real trap.** `chat models` hits `/v1/models` and works
with a proxy API key. `chat send` and `chat stream` post to `/api/dashboard/chat/completions`, which
is guarded by the **admin/management key** — with only a proxy key they fail
`401 invalid_api_key`. That dashboard surface also normalizes a bare model name by prefixing it when
exactly one active connection advertises that model. For scripted work against a different host, or
to control the exact bytes on the wire, use `curl` against `/v1/chat/completions` with a proxy key.

## 4 · Streaming

Set `"stream": true`. The response comes back as `text/event-stream` with OpenAI SSE framing:

```
data: {"choices":[{"delta":{"content":"hel"},"index":0}]}

data: {"choices":[{"delta":{"content":"lo"},"index":0}]}

data: [DONE]

```

```bash
curl -N -sS "$BASE/v1/chat/completions" \
  -H "Authorization: Bearer $OPENPROXY_API_KEY" \
  -H 'content-type: application/json' \
  -d '{"model":"ollama/glm-4.7-flash","messages":[{"role":"user","content":"count to 3"}],"stream":true}'
```

Things that bite:

- **Frames are `data: ` prefixed and blank-line separated; the stream ends with a literal
  `data: [DONE]`.** A client that splits on newlines instead of blank lines will break.
- **Streaming is the default when `stream` is absent.** `resolve_stream_flags` sets
  `stream = (body.stream != false)`. Send `"stream": false` explicitly for a buffered JSON reply.
- **Some providers force streaming upstream even when you did not ask.** When that happens the
  router aggregates the SSE and returns a single JSON body (`sse_to_json`), so the shape you get
  back still matches what you requested.
- **`Accept: application/json` flips a non-`stream:true` request to non-streaming** — but an
  explicit `"stream": true` always wins.
- **Gemini / Antigravity / GeminiCli sources always stream**, whatever `stream` says.
- Image-generation models on `antigravity` / `gemini-cli` are forced non-streaming.

## 5 · Tools and `tool_calls`

`tools` and `tool_calls` pass through the OpenAI dialect unchanged, and are **translated** when the
target provider speaks a different dialect (Claude `tools` / `tool_use`, Gemini `functionDeclarations`,
Kiro, Cursor, Codex, Ollama, CommandCode). You send the OpenAI shape; the router handles the rest.

Declare tools:

```bash
curl -sS "$BASE/v1/chat/completions" \
  -H "Authorization: Bearer $OPENPROXY_API_KEY" \
  -H 'content-type: application/json' \
  -d '{
        "model": "ollama/glm-4.7-flash",
        "messages": [{"role": "user", "content": "What is the weather in Osaka?"}],
        "tools": [{
          "type": "function",
          "function": {
            "name": "get_weather",
            "description": "Get current weather for a city",
            "parameters": {
              "type": "object",
              "properties": {"city": {"type": "string"}},
              "required": ["city"]
            }
          }
        }],
        "tool_choice": "auto"
      }'
```

The assistant replies with `choices[0].message.tool_calls` — an array of
`{id, type: "function", function: {name, arguments}}` where `arguments` is a **JSON string**, not a
parsed object. Parse it before using it.

Feed the result back as a `role: "tool"` message carrying the matching `tool_call_id`:

```json
{
  "model": "ollama/glm-4.7-flash",
  "messages": [
    {"role": "user", "content": "What is the weather in Osaka?"},
    {"role": "assistant", "tool_calls": [
      {"id": "call_abc", "type": "function",
       "function": {"name": "get_weather", "arguments": "{\"city\":\"Osaka\"}"}}
    ]},
    {"role": "tool", "tool_call_id": "call_abc", "content": "18C, light rain"}
  ],
  "tools": [ /* re-send the same tools array */ ]
}
```

Re-sending `tools` on the follow-up turn is required — the router has no memory of the previous
request.

In a stream, tool arguments arrive fragmented across `delta.tool_calls[].function.arguments`; the
`index` field pairs fragments across chunks. Concatenate before parsing. Empty
`delta.tool_calls: []` arrays — which some providers emit in every chunk — are stripped before
reaching you, so a `!= null` check on `delta.tool_calls` behaves as OpenAI SDKs expect.

Two provider-specific behaviors, both intentional:

- **TTS models drop `tools` and every `role: "tool"` message** before dispatch.
- **Claude-dialect clients get their tools de-duplicated** before dispatch.

## 6 · Combos and fallback

A **combo** is a named list of `provider/model` entries dispatched as one. It is addressed as a bare
model name, and is what makes a chat call survive a provider going down mid-session.

```bash
openproxy combo list                    # existing combos
openproxy combo get my-combo            # membership
openproxy combo test my-combo           # dry-run the expansion, verify reachability
openproxy combo create --name my-stack \
  --models "openai/gpt-4o,anthropic/claude-3-5-sonnet" \
  --strategy fallback                   # fallback (default) | round-robin | sticky-round-robin
```

Call it with the combo name, bare or explicit:

```bash
-d '{"model":"my-stack","messages":[{"role":"user","content":"ping"}]}'
-d '{"model":"combo:my-stack","messages":[{"role":"user","content":"ping"}]}'
```

A name that is neither a combo, an alias, nor a prefixed provider model does **not** produce
"Unknown combo model" by accident — that 400 only comes from the `combo:` form naming a combo that
does not exist or is disabled:

```bash
# 400 {"error":{"code":"bad_request","message":"Unknown combo model"}}
-d '{"model":"combo:no-such-combo", …}'
```

### How fallback shows up in the response

When a combo member fails, the router tries the next member. **The response body's `model` field
names the member that actually answered**, not the combo you asked for. That is the primary signal:

```
request  "model": "my-stack"   →  response  "model": "gpt-4o"     (second member served it)
```

There is no separate "which member served" field or header — read `model`.

To see the full picture per request, check usage. The history rows carry `provider` and `model`
per request, so a combo's members appear as separate rows:

```bash
openproxy usage history --limit 20           # model / provider / cost per row
openproxy usage history --limit 20 --robot   # same, as openproxy.v1.usage.history
openproxy usage logs                         # paginated combined activity log
openproxy logs tail --follow                 # live NDJSON log stream
```

These talk to `/api/usage/*` and `/api/observability/*`, so they need the **admin/management key**,
not a proxy key — with only a proxy key they return `401 invalid_api_key`. Export the right one
first: `export OPENPROXY_API_KEY="$(openproxy --robot server init | jq -r '.data.admin_key.key')"`. (No code writes `~/.openproxy/admin.key`; only the bootstrap skill tells you to create it by hand, so reading it silently yields an empty key and a cascade of 401s.)

`GET /v1/usage` and `GET /api/usage/history` serve the same data over HTTP. A response that carries
no token usage at all (most SSE streams) records **no** usage row — absence of a row is not proof
the call did not happen.

When every member of a combo fails, the members that errored are auto-quarantined with a cooldown
drawn from the failure class, so the *next* request skips them rather than re-attempting a known-broken
member. That is why a failing combo gets *faster* to error, not slower, on repeat calls.

## 7 · Error reference

Every error is the OpenAI envelope: `{"error": {"message", "type", "code"}}`. Chat-specific arms:

| Status | `error.code` | `error.message` | Meaning |
|---|---|---|---|
| 400 | `bad_request` | `Missing model` | `model` absent or empty. |
| 400 | `bad_request` | `Unknown combo model` | `combo:<name>` names a combo that does not exist or is disabled. |
| 401 | `invalid_api_key` | — | Bad/expired bearer on a `/api/*` surface. |
| 403 | _(upstream's)_ | provider's own wording | Passed through from the provider — quota, entitlement, or account ban. |
| 404 | `model_not_found` | `No active credentials for provider: <provider>` | See below. |
| 429 | _(upstream's)_ | provider's own wording | Passed through from the provider. |
| 502 | _(upstream's)_ | provider's own wording | The provider **has** a credential and failed while serving. |
| 503 | `service_unavailable` | `[<provider>/<model>] <last error> (reset after 4m 12s)` | Every account for that provider is rate-limited / cooling down. `Retry-After` header is set. |
| 503 | `service_unavailable` | `All accounts for <provider>/<model> are unavailable` | Accounts were tried and all exhausted. |

Note the split: the `400` / `404` / `503` rows are **built by OpenProxy**, so their `code` and
`message` are stable. The `403` / `429` / `502` rows carry the **upstream provider's** body verbatim
(served back as JSON), so their `code` and `message` are whatever that provider emits and will not
match this table.

### `404 No active credentials for provider: X` — what it actually means

Verified verbatim against a running server:

```bash
$ curl -sS -w '\nHTTP %{http_code}\n' "$BASE/v1/chat/completions" \
    -H "Authorization: Bearer $OPENPROXY_API_KEY" -H 'content-type: application/json' \
    -d '{"model":"totally-unknown-provider/gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'
{"error":{"code":"model_not_found","message":"No active credentials for provider: totally-unknown-provider","type":"invalid_request_error"}}
HTTP 404
```

Despite the 404 and the `model_not_found` code, **this is not a routing or URL problem** — the model
string resolved fine. The precise meaning is: *for that provider, no connection was ever attempted.*

The router picks the status from what the credential lookup reported, not from the model name:

- **nothing excluded** (no account was ever tried) → `404 No active credentials for provider: X`.
  There is no usable credential on file for `X` at all.
- **every account rate-limited / cooling down** → `503` + `Retry-After`, message
  `[<provider>/<model>] <last error> (reset after 4m 12s)`.
- **accounts were tried and all failed** → `503 All accounts for <provider>/<model> are unavailable`.

So when you see the 404, check the *provider*, not the model:

1. Is the prefix spelled right? A typo'd prefix is a provider that does not exist — same 404. Use
   `GET /v1/models` and copy an id verbatim.
2. Does that provider have a connection? `openproxy provider list`, or
   `GET /api/providers` with the **admin** key.
3. Is the connection active and does it hold a credential? A connection with `api_key: null` —
   an OAuth provider whose token has not been refreshed, or an API-key provider never filled in —
   is exactly this case. The request never reaches the upstream; the provider receives zero traffic.
4. Was the model disabled on the Providers page? That removes it from `/v1/models`, but a directly
   addressed `provider/model` still resolves — so a disabled model gives a *provider* error, not a
   404 about the model.

Note the asymmetry: **the same provider with a credential that then fails gives a `502`, not this
404.** If you are getting 404, adding credentials is the fix; debugging the upstream key is not.

## 8 · Auth

`/v1/chat/completions` requires a valid, **active** proxy API key only when the `requireApiKey`
setting is on — which is the default on a fresh install, and which falls back to `requireLogin` on
installs predating the field. When it is off, `/v1` accepts unauthenticated loopback requests (and
returns `404` for an unconfigured provider rather than `401`, since there is no auth check to fail).

```bash
openproxy key list                 # existing keys (secrets masked)
openproxy key add my-agent --auto  # mint a fresh op-… secret, prints it once
openproxy key disable my-agent     # revoke without deleting
```

Auth is read from `Authorization: Bearer <key>`, or `x-api-key: <key>`, or `x-goog-api-key: <key>`
(Gemini-style clients). `Authorization: Bearer …` is the one to use.

Forwarded headers (`X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto`, …) are **stripped** unless
`TRUST_PROXY=true`, because on a default single-process deploy they are spoofable and are used for
rate limiting and IP logging.

## 9 · Verifications

Run these in order. Each one isolates a different layer.

```bash
# 1. Server is up (no auth)
curl -sS "$BASE/health" | head -c 80

# 2. Endpoint advertises itself (no auth)
curl -sS "$BASE/v1"        # → {"version":"v1","endpoints":["/v1/chat/completions", …]}

# 3. Your bearer works on /v1
curl -sS -o /dev/null -w '%{http_code}\n' \
  "$BASE/v1/models" -H "Authorization: Bearer $OPENPROXY_API_KEY"

# 4. The model you picked is real, and you know what it resolves to
curl -sS "$BASE/v1/models/info?id=my-model-or-combo" \
  -H "Authorization: Bearer $OPENPROXY_API_KEY"

# 5. Non-streaming round trip
curl -sS "$BASE/v1/chat/completions" \
  -H "Authorization: Bearer $OPENPROXY_API_KEY" -H 'content-type: application/json' \
  -d '{"model":"my-model","messages":[{"role":"user","content":"ping"}],"stream":false}'

# 6. Streaming round trip — must end with data: [DONE]
curl -N -sS "$BASE/v1/chat/completions" \
  -H "Authorization: Bearer $OPENPROXY_API_KEY" -H 'content-type: application/json' \
  -d '{"model":"my-model","messages":[{"role":"user","content":"ping"}],"stream":true}'

# 7. The request was actually recorded
openproxy usage history --limit 5
```

If step 5 returns a body but no `choices`, the failure is upstream — read `error.message`, it carries
the provider's own wording with the router's prefixes (`Error from provider:`, `Upstream error:`)
already stripped. If step 5 returns `404 No active credentials for provider: X`, go to §7.

To exercise the whole path **without a live provider**, the `x-openproxy-sim: mock` request header
short-circuits dispatch and renders a canned response in your request's own dialect. Any
`x-openproxy-sim-*` header bypasses the response cache, so a simulated reply can never be served to
a real request:

```bash
curl -sS "$BASE/v1/chat/completions" \
  -H "Authorization: Bearer $OPENPROXY_API_KEY" -H 'content-type: application/json' \
  -H 'x-openproxy-sim: mock' \
  -d '{"model":"ollama/glm-4.7-flash","messages":[{"role":"user","content":"hi"}]}'
```

Note the mock still resolves credentials and the model, so it will not mask a `404` or a `400`.

## 10 · Debugging an unexpected `model` field

The `model` you sent and the `model` you got back can differ for four distinct reasons. Check them
in this order:

1. **You sent a bare name and it resolved by inference.** `gpt-4o-mini` → `openai`; `claude-*` →
   `anthropic`. Send `provider/model` explicitly to remove the guesswork.
2. **You sent a combo.** The response names the member that served. This is correct behavior — see §6.
3. **The catalog remaps the model upstream.** A model with an `upstreamModelId` in the provider
   catalog is dispatched under that id, and the provider echoes it. `GET /v1/models/info?id=…`
   returns `upstreamModelId` when one is set.
4. **The provider echoed a different name than the one you sent** — common with OpenRouter-style
   gateways that report a canonical or dated model id. Nothing to fix.

## When _not_ to use this skill

- **Wiring a CLI tool** (Claude Code, Codex, Cursor, Cline) to the proxy — that is base-URL + bearer
  config, not chat semantics. See `.agents/skills/openproxy-cli-tools/SKILL.md`.
- **Combo construction, membership, strategies, round-robin vs fallback** — this skill covers calling
  a combo, not designing one. See `.agents/skills/openproxy-combos/SKILL.md`, and use
  `openproxy combo test <name>` to verify an expansion end to end.
- **Non-chat media** — images, TTS, STT, embeddings, web search, web fetch each have their own
  endpoint and skill under `.agents/skills/`.
- **Installing or starting the server** — see `.agents/skills/openproxy/SKILL.md`.
- **Anthropic-native (`/v1/messages`) or Responses-API (`/v1/responses`) clients** — those are
  separate handlers; the OpenAI dialect documented here is translated to whatever the target
  provider needs, so an OpenAI-shaped body is the portable choice.

## See also

- `.agents/skills/openproxy/SKILL.md` — install, start, auth bootstrap
- `.agents/skills/openproxy-combos/SKILL.md` — building and editing combos
- `docs/parity-9router.md` — intentional divergences from 9router, and the pipeline order
- `GET $BASE/v1` — the live endpoint list, on the running server
- `openproxy chat --help` / `openproxy chat <cmd> --help`

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.