Quickstart

The router speaks the OpenAI Chat Completions API. Use any OpenAI SDK or plain HTTP: change the base URL, use a router key, and pick one of the models below. Your requests go to whichever backend the admins mapped that model to; you never need the backend's own credentials.

Base URL

https://your-router.example/api/public/v1

One endpoint serves everyone on this deployment. It is never provisioned per user and never changes - your API key is what identifies you, carrying your account, approval status, credits and rate limits on every request. On the live site this is https://llm.nyolab.ai/api/public/v1.

Make your first request

  1. 1

    Create an account and get approved

    Sign up with email or Google, and confirm your email if asked. New accounts need an admin's approval before keys work; the dashboard shows whether you are still waiting. If an admin pre-approved your address, access starts as soon as your email is confirmed.

  2. 2

    Create an API key

    In the dashboard, name a key and press New key. Copy it straight away: it starts with rk_live_ and is shown only once. Keep it out of source code, for example in an environment variable:

    export ROUTER_API_KEY="rk_live_..."
  3. 3

    Pick a model

    Loading models…

  4. 4

    Send a chat completion

    curl https://your-router.example/api/public/v1/chat/completions \
      -H "Authorization: Bearer $ROUTER_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "model": "your-model",
        "messages": [{"role": "user", "content": "Say hello in one sentence."}]
      }'

    The request shows up under Recent requests in your dashboard with its status, latency and token count.

Streaming

Set stream: true to receive tokens as server-sent events, exactly as the OpenAI API sends them. NYO always sets stream_options: { include_usage: true }, so the final chunk carries token counts and every stream appears in your usage history. If the connection closes before that chunk, usage is estimated from what was streamed.

curl -N https://your-router.example/api/public/v1/chat/completions \
  -H "Authorization: Bearer $ROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "your-model",
    "stream": true,
    "stream_options": {"include_usage": true},
    "messages": [{"role": "user", "content": "Count from 1 to 5."}]
  }'

Use it from a local coding agent

Coding agents that accept a custom OpenAI-compatible provider work with the router: give them the base URL, your key and a model name. Keep the key in the ROUTER_API_KEY environment variable rather than in a config file you might commit.

  • Agents edit files and run commands through tool calls, so pick a model that supports function calling well, ideally a coding model. Aider is the exception: it edits through plain text and works with any chat model.
  • The router does not report context sizes, so the examples set a 128k context window. Change it to the model's real limit; too high and long sessions fail.
  • Choose the provider type that uses Chat Completions. Options built on the OpenAI Responses API do not work here.

Add a provider to opencode.json in your project, or to ~/.config/opencode/opencode.json. Use the @ai-sdk/openai-compatible package; @ai-sdk/openai uses the Responses API and fails. Then run opencode.

{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "router": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "LLM API Router",
      "options": {
        "baseURL": "https://your-router.example/api/public/v1",
        "apiKey": "{env:ROUTER_API_KEY}"
      },
      "models": {
        "your-model": {
          "name": "your-model",
          "limit": { "context": 128000, "output": 16384 }
        }
      }
    }
  },
  "model": "router/your-model"
}

Reference: OpenCode providers

Not supported

  • OpenAI Codex CLI only speaks the Responses API since early 2026; its old wire_api = "chat" setting was removed.
  • Claude Code needs an Anthropic Messages API endpoint and supports only Claude models through gateways.

MCP agents

Agents that speak the Model Context Protocol skip API keys entirely: point them at https://your-router.example/mcp. They sign in with their account, approve access once, and can then call the ask, models, account and redeem_code tools as that user - the same account, credits, approval status and limits as the REST API.

Everything on this page can be done from that assistant too: creating and revoking API keys (create_api_key shows the key once, in the chat), reading usage per key (usage), creating and attaching guardrails, and updating the profile or plan. Say what you want in plain words - “make me a key for staging with the concierge guardrail” - and the assistant calls the right tool and shows you what it did.

For autonomous discovery, agents can also read /llms.txt, /openapi.json and /.well-known/ai-plugin.json, all served from the same origin.

Credits, promo codes and rate limits

Paid models draw credits from your balance as you use tokens. Each model has a price per million input tokens and per million output tokens, and a request costs input × input_price / 1M + output × output_price / 1M. Your balance, your last top-ups and the cost of each call are in the dashboard.

Loading prices…

  • Failed requests are free. Anything the backend answered with an error costs nothing.
  • Streamed requests are metered too. The router asks the backend for token counts on every paid stream, so a final chunk carrying usage and no choices always arrives. SDKs skip it; hand-written loops should too.
  • Running out gives a 402. The check happens before the call, so a long reply can take the balance slightly below zero; the next request is refused.
  • Promo codes are redeemed in the dashboard and add credits to your balance. Each code works once per account.
  • Free models still count against the rate limit, and no request is ever charged twice.

The limit is a fixed number of requests per minute per account, counted across all of your keys. Over it, the router answers 429 with a Retry-After header in seconds; wait that long rather than retrying immediately.

There is also an hourly token allowance, so trying the gateway out cannot empty a balance in one session. It counts what finished requests actually used, across the API, the playground and MCP alike, and answers 429 when it is spent. The request that crosses the line completes; the next one waits for the hour to roll.

Models

Model names are chosen by the admins and can differ from the provider's own names. List the ones available right now without a key:

curl https://your-router.example/api/public/v1/models

Loading models…

API reference

POST
/chat/completions

Requires Authorization: Bearer <key>. The body is the OpenAI Chat Completions body. The router replaces model with the backend's model name and passes every other field (temperature, max_tokens, tools, response_format and so on) through unchanged, so what is supported depends on the backend's model. The response comes back as the backend sent it.

GET
/models

No key needed. Returns the enabled model names in the OpenAI list format, so SDK calls such as client.models.list() work.

POST
/systemone

System One models only (see below). Typed questions about a state, in TypeSafe's Jev wire format.

POST
/rank

System One models only. Candidate actions or answers ranked against a state.

Embeddings, the legacy completions endpoint and the Responses API are not routed.

System One models: CLM, OpenJev, GPT-6 Luna

clm is CLM, an open contrastive decision model and an alternative to TypeSafe's Jev. It does not write text: it answers typed questions about a state with probabilities, in one pass, in tens of milliseconds. The same key works three ways.

Two more models answer the same requests, so a Jev client can switch by changing the model name: openjev (APUS-OpenJev, an open Jev-compatible model) and gpt-6-luna-decisions, OpenAI's GPT-6 Luna decision model reached through Vercel AI Gateway. Luna returns calibrated probabilities and a confidence for choice and score questions, and is billed on input tokens only. The native endpoints below take any of the three; the OpenAI-compatible retrofit in part 3 is CLM only.

1. Native, Jev-compatible

A request written for TypeSafe replays unchanged (jev-latest is accepted as the model name):

curl https://your-router.example/api/public/v1/systemone \
  -H "Authorization: Bearer $NYO_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "model": "clm",
    "state": "Customer: my invoice was charged twice and nobody answers the phone!",
    "questions": {
      "urgent": { "type": "noul", "instructions": "The customer needs help today" },
      "team": { "type": "choice", "instructions": "Which team should handle this?",
                "criteria": { "billing": "Charges, invoices, refunds", "technical": "Bugs and outages" } },
      "mood": { "type": "score", "instructions": "How frustrated is the customer?",
                "criteria": ["Calm", "Frustrated", "Very angry"] }
    }
  }'

noul answers a probability that the statement is true; choice the best option, every option's probability and a confidence; score the expected level (0 = first) with its distribution. Up to 16 questions, 64 options and 10 score levels per request.

2. Native, world model: rank actions against a state

curl https://your-router.example/api/public/v1/rank -H "Authorization: Bearer $NYO_API_KEY" -H "Content-Type: application/json" \
  -d '{ "model": "clm", "context": "What causes tides on Earth?",
        "answers": ["The Moon\u0027s gravitational pull.", "Photosynthesis in plants."] }'

3. OpenAI-compatible

/chat/completions with model: "clm" works with any OpenAI SDK. The request becomes typed questions by the first of these that applies:

  • clm: { questions } in the body (extra_body in the SDKs): asked as written, answers as JSON.
  • response_format with a JSON schema of booleans, string enums or small integer ranges: JSON matching the schema.
  • tools without required arguments: a tool call to the best tool. CLM chooses the tool; it does not fill arguments.
  • Otherwise the last message: a list of options (one per line) is a choice; a claim or yes/no question gets a probability in words. Open questions get a short guide instead of a guess.

Every response also carries clm.answers with the distributions. Billing is input tokens only. Try it in the playground: pick clm and use Chat or Typed questions.

Errors

Errors use the OpenAI shape, {"error": {"message": "..."}}, so SDKs raise them as normal API errors.

StatusMessageWhat to do
401Missing API keySend the Authorization: Bearer header.
401Invalid API keyThe key is mistyped or was revoked. Create a new one in the dashboard.
400Invalid JSON bodySend a JSON body with Content-Type: application/json.
400`model` is requiredAdd a model name from the list above.
403This account is not approved for API accessWait for an admin to approve your account, or ask them to.
402Out of creditsRedeem a promo code in the dashboard, or ask an admin for a top-up.
429Rate limit reached: N requests per minuteWait the seconds given in the Retry-After header, then retry.
422type: guardrail_rejectedThe key's guardrail refused the request: off scope, a tool the key does not allow, or too large. `error.message` is what to show the end user; `error.code` says which rule. Nothing was charged.
404No enabled backend for model '…'The name is not routed or was disabled. Check the models list.
502type: reasoning_budget_exhaustedThe model used your whole max_tokens thinking and wrote no answer. Raise max_tokens to 2000 or more, remove it, or use gemma-3-27b, which does not reason first.
524type: edge_timeoutThe model did not start answering within 125 seconds — long prompts on slow-to-start models (Kimi K3 above roughly 20k tokens). Shorten the conversation or use a faster model. Nothing was charged.
502Backend … has no API key / fetch failureThe router could not reach the backend. Ask an admin to run Test on it.
503Could not verify account access, try againA temporary problem checking your approval. Retry after a moment.
OtherPassed through from the backendFor example 400 for an unsupported parameter or 429 for a rate limit.

Guardrails

A guardrail limits what a key's end users may ask for. If your app is an information concierge for your website, its visitors should not be able to turn it into a coding assistant or call tools you never provided. You choose a template - information concierge, customer support, coding assistant, or custom - describe what your assistant is for, and attach the guardrail to a key from your account page. Keys without one are unrestricted.

Three things happen to a request on a guarded key, cheapest first:

  1. Rules, checked before anything is sent: a request carrying tools the key does not allow, or larger than your limit, is rejected.
  2. A scope check: a small model reads the user's latest message and answers IN or OUT. OUT is rejected before your model spends anything. You can turn this off.
  3. Instructions placed in front of your messages as a system message, so the model also declines politely if something slips through.

A rejected request returns 422 with error.type = "guardrail_rejected", error.message set to the refusal you wrote for your users, and error.code naming the rule. It costs nothing and shows in your traffic as a 422. The guardrails page shows every guardrail's rules, scope prompt and instructions word for word.

You can also do all of this from a connected assistant: with NYO added to Claude, say “set up a guardrail so my site key only answers questions about Acme Hotels” - it calls set_guardrail, shows you the exact rules and instructions it created, and attaches it to the key you name.

Keys and usage

  • Everyone shares the same base URL shown at the top of this page. Nothing is created at the domain level when you make a key - the key, not an endpoint, is your credential.
  • Create as many keys as you like, for example one per app or machine.
  • The router stores only a hash of each key, so a lost key cannot be recovered. Create a new one and revoke the old one.
  • Revoking a key stops it working immediately. So does an admin removing your account's access, for all of your keys.
  • The observability view shows request volume, success rate, latency, tokens, cost, errors and model-level performance for your own traffic.

For admins

  1. 1

    Connect a backend

    In Backends, add one. The built-in provider presets fill in the base URL for you. Leave Credential empty to use that provider's server secret set in Lovable Cloud; that keeps the provider key out of the database. Any other OpenAI-compatible endpoint works with its own credential.

  2. 2

    Test it

    Press Test on the backend's row. It lists the provider's models; an error here means a wrong base URL or key.

  3. 3

    Route a model name

    In Model routing, choose the public name users will send, the backend, and the provider's model id. After a successful test the model id field suggests the provider's models. Short public names without slashes, such as qwen3-coder, are easiest to use from coding agents, some of which prefix model names with a provider and a slash.

  4. 4

    Set prices, credits and the rate limit

    A route's input and output price per 1M tokens decide what each request costs; leave them at 0 to keep a model free. Under Billing, set the requests-per-minute limit and create promo codes worth a number of credits, optionally limited in redemptions or given an expiry date. In Users, the coin button tops up an account directly; a negative amount takes credits away. Price your models above what the provider charges you, or you will be subsidising the difference.

  5. 5

    Approve users

    In Users, new accounts are listed as pending, with a count on the tab. Approve lets their keys work; Remove access stops all of an account's keys on the next request. Under Pre-approved emails, add addresses you trust: a matching account is approved once its email is confirmed, so signing up with someone else's address is not enough. Pre-approval never overrides a denial.

  6. 6

    Check traffic

    Usage summarises traffic, spend and reliability across accounts. Traffic shows recent requests for operational review.