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.
| OpenRouter | Dawnstack |
|---|---|
meta-llama/llama-3.1-8b-instruct | llama-3.1-8b |
mistralai/mistral-nemo | mistral-nemo |
google/gemma-3-4b-it | gemma-3-4b |
deepseek/deepseek-chat | deepseek-v3.2 |
openai/gpt-oss-20b | gpt-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:
providerpreferences,route: "fallback"andmodels: [...]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 a400naming 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
- Every model id you call exists here.
GET /v1/modelsis public, so this is one script. - 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
400before any paid work. See Tool calling and Structured output. - Your error handling reads
error.type. Our envelope is OpenAI’s, and an empty balance is a402withinsufficient_quotarather than a429.
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.