Guides

Migrating from OpenRouter

Both are OpenAI-compatible, so the code does not change. What does change is the model ids, the routing behaviour, and how you pay.

Both endpoints speak the OpenAI protocol, so your request and response handling, your streaming loop and your tool-calling round trip all carry over untouched. Three things do change: the model ids, the routing, and the payment.

The two lines

from openai import OpenAI

client = OpenAI(
    api_key=os.environ["DAWNSTACK_API_KEY"],
    base_url="https://api.dawnstackai.com/v1",
)
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.DAWNSTACK_API_KEY,
  baseURL: "https://api.dawnstackai.com/v1",
});

Model ids drop the provider prefix

OpenRouter namespaces every model by the company that trained it. We use the id from our own catalogue, which you can read without an API key at GET /v1/models or on the models page.

OpenRouterDawnstack
meta-llama/llama-3.1-8b-instructllama-3.1-8b
mistralai/mistral-nemomistral-nemo
google/gemma-3-4b-itgemma-3-4b
deepseek/deepseek-chatdeepseek-v3.2
openai/gpt-oss-20bgpt-oss-20b

An unknown id is a 404 with the id echoed back, not a silent substitution.

Routing is explicit, and there is no fallback

This is the substantive behavioural difference, and it cuts both ways.

OpenRouter routes a request across several upstream providers and can fail over between them. We do not: a model id maps to one backend, and if that backend is failing your request fails rather than being served by somebody else at a different price and a different quantisation.

That means:

  • provider preferences, route: "fallback" and models: [...] arrays do nothing here. They are not part of the OpenAI protocol, and we reject an unsupported parameter by name rather than ignoring it, so you will see a 400 naming the field instead of a quietly different result.
  • Reproducibility is the trade. The same id gets you the same weights at the same quantisation every time, and the quantisation is published per model.
  • Retries are yours. If you relied on fallback for availability, keep a second model id in your own config and switch on error.

Aliases, if you want a choice made for you

dawn-swahili-best and dawn-multilingual resolve to whichever model currently measures best for that job. They are opt-in by name only: nothing inspects your prompt and quietly picks a different model, because that would change your output, your bill and your reproducibility with no way for you to know why.

The usage row records both the id you asked for and the id that served, so a bill stays explicable after an alias is repointed.

Paying

OpenRouter takes a dollar-denominated card. We take a prepaid balance, topped up in local currency through mobile money and cards, from $1.00. There is no monthly commitment and no minimum spend.

New accounts get a starter balance, which is enough to port your code and see real output on the cheaper models before paying anything.

Three things to check before you switch traffic

  1. Every model id you call exists here. GET /v1/models is public, so this is one script.
  2. The capabilities you use are declared. Tool calling and structured output are measured per model rather than copied from a supplier’s catalogue, and asking for one a model does not declare is a 400 before any paid work. See Tool calling and Structured output.
  3. Your error handling reads error.type. Our envelope is OpenAI’s, and an empty balance is a 402 with insufficient_quota rather than a 429.

What is not here yet

Written plainly, because finding out at request time is worse: no closed-weight models, no provider-preference routing, no prompt-caching discount, and no request logging you can browse. The current list is in Not supported yet.