Unified API
One OpenAI-compatible endpoint for every model, including your own. Request shape, model-name forms, and authentication.
Point your existing OpenAI SDK at https://api.routing.nyuro.ai/v1/chat/completions
and Nyuro routes the request to the best (provider, model) — cloud or your own
VPS — and returns an identical response shape.
Base URL
The gateway is served from a single host:
https://api.routing.nyuro.aiWhether you include /v1 depends on your client
An OpenAI SDK appends the path itself, so it needs the /v1. A raw HTTP
client does not, so it must omit it and write the full path. Getting this
backwards produces /v1/v1/... and a bare 404 that looks exactly like an
endpoint that was never deployed.
| Client | Base URL to configure | Resulting request |
|---|---|---|
| OpenAI SDK (Python, Node, …) | https://api.routing.nyuro.ai/v1 | SDK appends /chat/completions |
| curl / fetch / raw HTTP | https://api.routing.nyuro.ai | you write /v1/chat/completions |
# ✅ 200
curl https://api.routing.nyuro.ai/v1/chat/completions ...
# ❌ 404 — the SDK-style base URL with a hand-written path
curl https://api.routing.nyuro.ai/v1/v1/chat/completions ...Endpoint
POST https://api.routing.nyuro.ai/v1/chat/completions
Authorization: Bearer nyu_live_…
Content-Type: application/jsonThe request and response bodies mirror OpenAI's Chat Completions API. Any field
the underlying provider supports (temperature, max_tokens, tools,
tool_choice, stream, …) is forwarded through.
Two surfaces: /v1 and /api/v1
The API exposes two prefixes, and the distinction is deliberate:
/v1/…— the OpenAI-compatible surface. Five routes, shaped so an unmodified OpenAI client works against them./api/v1/…— the full platform surface, including everything in/v1plus keys, budgets, governance, observability, media, and collections.
These five routes are aliases — identical handlers, reachable under either prefix, so pick whichever suits the client:
/v1/chat/completions ≡ /api/v1/chat/completions
/v1/embeddings ≡ /api/v1/embeddings
/v1/models ≡ /api/v1/models
/v1/industries ≡ /api/v1/industries
/v1/router/preview ≡ /api/v1/router/previewEverything else is /api/v1-only. Notable endpoints beyond chat:
| Endpoint | Purpose |
|---|---|
POST /v1/embeddings | Embeddings, OpenAI-compatible |
GET /v1/models | List routable models |
POST /v1/router/preview | Ask the router what it would pick, without spending |
GET /api/v1/catalog/models | Full model catalog with pricing and capabilities |
GET /api/v1/media/models | Image / video generation models |
POST /api/v1/collections/{collection}/search | Managed RAG search — see Collections |
GET /api/v1/metrics/usage | Usage rollups — see Observability |
The complete, always-current surface is the OpenAPI document at
/openapi.json. It does not yet
declare a servers block, so SDK generators, Postman and Swagger UI will ask
you for the base URL above rather than inferring it.
Authentication
Issue and rotate keys from Settings → API Keys in the console. Keys are
shown once on creation and begin with nyu_live_. Send the key as a bearer
token (recommended) or via X-API-Key:
Authorization: Bearer nyu_live_<suffix>
# or
X-API-Key: nyu_live_<suffix>Scopes
Keys carry per-capability scopes. A scope grants one family of endpoints, and a key only works where it has been granted:
| Scope | Grants |
|---|---|
chat | /v1/chat/completions |
embeddings | /v1/embeddings and the collections endpoints |
models.read | /v1/models |
A working chat key is not automatically an embeddings key
Scopes are granted per key, so a key that routes chat perfectly can still be
rejected on embeddings. The failure is a 403, not a 401:
{"detail": "api key missing required scope 'models.read'"}From the client side that is easy to mistake for an outage or an undeployed
endpoint. If you get a 403, check the key's scopes before you check
anything else.
Scopes are set when a key is created and can be changed on an existing key from
Settings → API Keys. The table above covers the scopes gating the public
OpenAI-compatible endpoints; administrative endpoints under /api/v1 carry
their own scopes.
The model field
You can pass any of these forms — all resolve through the router:
- Concrete alias —
gpt-4o-mini,claude-3-5-sonnet,Qwen/Qwen3-32B-AWQ, … - Industry tag —
industry:legal,industry:code, … - Strategy hint —
strategy:cost,strategy:quality, … - Auto —
auto - Fallback array —
"models": ["gpt-4o", "gpt-4o-mini"](OpenRouter-style)
See Models & aliases for the full reference.
Collections
Collections are a managed RAG API. You send plain text; chunking, embedding and vector storage happen server-side, so there is no vector database to run and no embedding pipeline to build.
All four endpoints require a key with the embeddings scope. A collection name
matches ^[A-Za-z0-9._-]+$ (max 128 chars).
Ingest a document
PUT /api/v1/collections/{collection}/documents/{source_id} — chunk, embed and
store under a source_id you choose. Re-sending the same source_id replaces
that document, which makes re-indexing idempotent.
curl -X PUT https://api.routing.nyuro.ai/api/v1/collections/handbook/documents/onboarding-v2 \
-H "Authorization: Bearer nyu_live_…" \
-H "Content-Type: application/json" \
-d '{"text": "Full plain text of the document…", "chunk_chars": 1200}'chunk_chars is optional (100–8000); omit it to use the server default. The
response reports what was written, and in which vector space:
{"source_id": "onboarding-v2", "chunks_written": 14,
"model": "Qwen/Qwen3-Embedding-0.6B", "dimensions": 1024}Search
POST /api/v1/collections/{collection}/search — semantic search over the
collection.
curl -X POST https://api.routing.nyuro.ai/api/v1/collections/handbook/search \
-H "Authorization: Bearer nyu_live_…" \
-H "Content-Type: application/json" \
-d '{"query": "how do I request leave?", "limit": 5}'limit defaults to 5 (max 50). Each returned passage carries content,
score, source_id and chunk_index — enough to cite the source chunk
directly in a grounded answer.
Leave the instruction alone unless you know why
Queries are embedded with the instruct prefix the embedding model expects,
while documents are embedded bare. That asymmetry is what the model is
trained for. The optional instruction field overrides the query-side
prefix — setting it incorrectly quietly degrades result quality without
raising an error.
Inspect and delete
GET /api/v1/collections/{collection} returns chunk and document counts
grouped by embedding model. Two models in one collection means a migration
is in flight, and this is how you see which rows sit in which vector space.
DELETE /api/v1/collections/{collection} removes the collection. Pass
?model=… to delete only one model's vectors — which is what makes an
embedding-model migration reversible: embed into the new space alongside the
old, cut reads over, then delete only the old.
The 30-second migration from OpenRouter
Same OpenAI SDK, two-line change:
from openai import OpenAI
client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key="sk-or-v1-…",
)
resp = client.chat.completions.create(
model="anthropic/claude-3-5-sonnet",
messages=[{"role": "user", "content": "Hi"}],
)from openai import OpenAI
client = OpenAI(
base_url="https://api.routing.nyuro.ai/v1",
api_key="nyu_live_…",
)
resp = client.chat.completions.create(
model="claude-3-5-sonnet", # plain alias
messages=[{"role": "user", "content": "Hi"}],
extra_body={"metadata": {"industry": "legal"}},
)In return you get a single observability dashboard, a single bill, automatic fallback, industry-aware routing, and the option to run inference on your own VPS without touching client code. Full walkthrough: Migrating from OpenRouter.
Response headers
Routing decisions are surfaced on every response via X-Nyuro-* headers —
see Routing → Transparency headers.