Opens in a new tab
Guide

What is OpenRouter? A practical guide to choosing and using AI models

In brief

Understand the model marketplace and gateway, compare chat and API workflows, and choose a useful route through its models, providers, costs and privacy controls.

15 min read

Is this the right starting point?
Before you read
Basic model-chat experience and, for the integration exercise, Python and API-key handling.
What you will take away
Compare models through OpenRouter, constrain provider routes and cost, and review a small API-generated handoff.
Sources
Overhead view of an orange locomotive at a three-way railway junction, a metaphor for model routing.
On this page12 sections

You want to try another model on the difficult sentence. First, however, come another account, key and API format. OpenRouter tries to make the model comparison easier by putting a common connection in front of those services.

OpenRouter is a hosted model gateway and marketplace. One account and API give access to models from multiple developers and providers, with web chat for trials. OpenRouter forwards requests to an eligible provider that runs the model. It is not software you install on a GPU to download and run those models locally.

This can help you explore, connect a model-configurable application or build multi-model software. It does not make models equally capable or providers identical. Its practical value is changing a choice without rebuilding every connection around it.

Start with chat if you want to compare answers, or one selected model and approved providers for an application. Check quality, data handling and cost before adding backups. A large catalog offers choices, not an obligation to use them all.

How a model marketplace became a gateway

OpenRouter began in early 2023, co-founded by Alex Atallah and Louis Vichy. Atallah, its cofounder and CEO, previously co-founded OpenSea, according to his biography. The premise was that software developers would want changing model choices rather than one permanent supplier.

At first you might use a catalog to find a model and copy a prompt into its chat. A gateway carries the next step: your application sends that prompt through one API address even when you choose another model. OpenRouter combines the place to find candidates with the connection used to call them.

A June 30, 2025 Orrick announcement describes $40 million in seed and Series A funding. Stripe announced an acquisition agreement on August 19, 2026; OpenRouter said its name, product and roadmap would remain. That agreement alone is not evidence here of a completed acquisition.

Its founding account emphasizes a multi-model future. Whether your application needs that flexibility today is the separate practical decision.

The model, its provider, and the router are different things

Take Meta's Llama as an example. Meta develops the model, another company may host it, and OpenRouter can route your request to that host. The roles differ:

  • The model generates the answer. Its capabilities determine whether it can write, reason, interpret an image, or use the features your task needs.
  • The provider runs a model endpoint, the service address that accepts requests. It contributes serving capacity, latency, pricing and data-handling terms.
  • OpenRouter presents the common API and selects an eligible route under the applicable settings.

The model identifies the learned system; the provider identifies who runs it for this request. Several endpoints can serve the same model with different capacity, price, latency or terms. The routing documentation describes the available choices.

Provider fallback tries another eligible host for the same model. Model fallback tries a different model after an error. That second change can alter behavior as well as availability, so test the backup model independently.

Model A routes among its eligible providers; a configured model fallback moves to Model B and its own eligible providers.
Provider fallback changes the serving endpoint; model fallback changes the model. This conceptual illustration does not show an observed run.

Auto Router delegates model choice itself. It is a different experiment from selecting one model or a backup list. Begin with a setup you understand before automating selection; otherwise a changed answer may be difficult to explain.

When OpenRouter saves work, and when it adds it

Suppose you need a clearer maintenance notice. OpenRouter’s chat lets you send the same text to several models without opening a new account for each. If one gives a useful result, the API can make that choice available to an application. That repeated comparison is where the extra connection may save work.

The test is small enough to explain: did the rewrite keep the maintenance time, maximum interruption and exception for old reports? Another agreeable answer is not necessarily another usable notice.

If the model you already use handles that notice well, a direct provider API may be enough. If you only want to write it once, a finished assistant application may be easier than managing model access. The three choices below change how much setup the notice needs.

Your main needA reasonable starting pointWhat you take on
Try several hosted models through one accountOpenRouter chat or APIModel and provider choices, usage charges, and another service handling requests
Use one provider's capabilities directlyThat provider's APIA separate integration and account for each additional provider
Get a ready-made personal assistantAn assistant applicationThe choices and workflow that application exposes

Two other routes become relevant when you want to operate the infrastructure. LiteLLM offers a common-interface SDK and a proxy you host yourself, leaving you responsible for its deployment, updates and availability. A local model runtime also puts inference on hardware you control, with its own memory and serving work. A self-hosted gateway that forwards remotely does not make that inference local. The inference guide separates those choices.

A gateway is also a dependency. Provider failover may handle one endpoint failure without removing OpenRouter's own availability from the path. Weigh that alongside the integration it saves.

Choose around the job, not the leaderboard

For the notice, begin with its exception: old exported reports remain available even while some new reports may not load. The model must preserve that distinction in a short rewrite. Use the catalog to find a text model and its exact ID, then check that the prompt fits its context limit and the current price fits your trial.

This first job needs text input and output. A later screenshot would require image support; an application asking for a fixed response shape or tool calls would need those features too. Check both the model and serving endpoint when that need arises. A long context allowance supplies room, not proof that the model will attend to every important sentence.

Give two candidates the identical notice and instructions. If one turns “some customers” into everyone, reject that rewrite before comparing cost. Among versions that keep the conditions, compare how much correction remains and how long each took.

Keep the prompt and returned model and provider beside the chosen version. If an answer changes later, those details let you see whether the request, model or serving route changed. Replacing all three together may improve the setup, but will not show which replacement helped. The models API can collect current model information when the experiment grows.

Try a real task in chat before building a client

For a first trial, open web chat, sign in and review privacy and credit settings. Select a model and use non-sensitive input. Check its current price; a chat interface does not make inference free.

Try this constructed customer-notice rewrite, whose important conditions are easy to inspect:

Rewrite this notice in plain English, under 70 words.
Keep the time zone, the maximum interruption, and the exception.
Do not imply that all customers will lose access.

On Tuesday at 14:00 UTC, reporting will undergo maintenance.
Some customers may be unable to load new reports for up to 10 minutes.
Previously exported reports remain available.

Check for 14:00 UTC, no more than ten minutes affecting some customers, and continued availability of previous exports. “Reporting will be unavailable” drops those distinctions. A cheerful rewrite can be easier to read and less accurate.

Send the identical request to another model and compare the version you would use after review, including corrections. This says something about this task, not programming, every language or general model rank.

If that is enough, stay in chat. Use the API when the operation belongs in a script or application. Confirm explicit OpenRouter or compatible API support and routing options; a model dropdown alone does not establish compatibility.

What you pay for when the model answers

Each notice attempt adds input and output usage to the bill. The words you send and the answer can have different rates; continuing the chat can also resend earlier material. The FAQ covers credit purchases, separate fees and model features such as reasoning, images, caching and tools that may add other charge categories.

At hypothetical rates of $1 per million input and $2 per million output tokens, sending 2,000 input tokens and receiving 500 costs $0.003. Ten thousand identical calls cost $30 before other charges. These are teaching rates, not a quote for a named model.

Now suppose the first rewrite drops the exception and a second call repairs it with the same usage. The usable notice cost $0.006 across those two calls. Looking only at the final answer would hide half the charge. Longer history or a different-length reply changes the calculation, so keep the actual usage from both attempts.

A lower rate can still cost more if that model needs more attempts. Bringing your own provider key changes who bills the underlying inference; it does not make the calls free. Compare all attempts needed for the notice, including the time you spend checking and correcting it.

Begin with a small trial budget and inspect Activity after the first call. For a team, a shared workspace pool answers how much everyone may spend together; member and key limits constrain a particular caller. Neither changes the token rate. The spend guide explains how to set those limits.

Which companies get the prompt?

Our synthetic notice can travel without exposing a customer record. A private incident log is different: OpenRouter receives the prompt and forwards it to an eligible provider. Removing a person’s name may leave tokens, account numbers or internal details. Before sending that log, identify both recipients and the permissions covering this use.

Imagine the provider promises not to train on the log but keeps a copy. That is different from retaining no prompt content. OpenRouter offers data_collection: "deny" and zdr: true; the latter requires endpoints it classifies as zero-retention. Read the provider policies and ZDR scope for the chosen endpoint, because the same company may operate endpoints under different terms.

The log can leave copies elsewhere too. OpenRouter’s own controls distinguish prompt/output logging, improvement opt-ins, retained metadata and anonymous categorization. Your own application might then log the response. Selecting ZDR for one inference route does not delete a copy that another part of the workflow made. The same documentation flags an October 27, 2026 planned update: OpenRouter will screen even ZDR inference traffic for child sexual abuse material. Flagged content may be kept for human review, reporting and legally required preservation. That stated exception matters when deciding whether this route may receive the log.

If a tool sends part of the log to search, that search service is another recipient. If processing must stay in a particular region, check the regional service and applicable terms; company headquarters does not locate the machine doing the work. Follow the actual log through each of these steps before treating the route as approved.

Connect one model to a small Python client

The next example puts those route choices into a visible request. It asks for a draft incident handoff from a small synthetic note. The model goes in one field; the permitted provider goes in another; price limits and ZDR requirements determine whether that provider is eligible to answer.

Use Python 3, an account, your own API key and approved credit. The client uses the standard library. It keeps the key on the machine running the script, outside source control and browser code. The placeholders below must become an actual model ID and approved provider from the current catalog before a charged call can run.

A provider can fail these restrictions even when it serves the model: this example permits at most $1 per million input and $2 per million output tokens and requires ZDR. If no endpoint meets all three choices, the correct result is no route. The quickstart explains the connection; the steps below the request help make its choices deliberately.

A handoff with recovery still unconfirmed

In the constructed incident, checkout releases at 14:02 UTC, timeouts rise at 14:06, pool waiting increases while database CPU stays 35%, and rollback begins at 14:11. With no later measurements, the assistant may organize a handoff but cannot declare recovery.

Constructed checkout timeline ends at rollback start, with no recovery measurement supplied.
The handoff should retain the gap after 14:11. Starting rollback does not supply evidence that checkout recovered.

Read the request as the route that note takes: model, allowed providers and output budget. The excerpt omits full messages; the expandable client is complete. Saved as handoff.py, it prints a dry run unless --send requests the key and makes the call.

{
  "model": "YOUR_MODEL_ID",
  "messages": [{"role": "user", "content": "..."}],
  "max_completion_tokens": 500,
  "provider": {
    "only": ["YOUR_PROVIDER_SLUG"],
    "allow_fallbacks": true,
    "require_parameters": true,
    "data_collection": "deny",
    "zdr": true,
    "sort": "price",
    "max_price": {"prompt": 1, "completion": 2}
  }
}
  1. Choose a text-generation model from the catalog and copy its exact ID. For this short exercise, a non-reasoning model keeps the completion allowance easier to interpret.
  2. Inspect the available providers and select the ones approved for your input. A base provider slug can match multiple endpoint variants; use the full endpoint slug when approval is narrower.
  3. Check current prices and policy compatibility. This example requires ZDR and permits at most $1 per million input tokens and $2 per million output tokens. Those deliberately limited settings may leave no eligible route. Choose a suitable combination or revise the limits intentionally before sending.
  4. Set the experiment's account or key budget, then inspect the request in dry-run mode.

Inspect the request locally first:

python3 handoff.py --model YOUR_MODEL_ID --provider YOUR_PROVIDER_SLUG

Check the model, restrictions and observations in the JSON. Add --send from an interactive terminal when ready for a charged call. It stops if hidden key input is unavailable. Extra --provider arguments should name only approved providers.

The non-streaming call uses /api/v1/chat/completions and prints available metadata before accepting text. Its 500-token max_completion_tokens allowance may include reasoning and visible output on most providers. Check the selected model before enlarging it. The request reference and reasoning guide describe the fields.

The client rejects capped, empty or malformed answers and performs no automatic retries. Its 45-second socket timeout limits a blocked operation, not total production lifetime. Local tests cover those failures without model calls.

Full Python client: copy into handoff.py
"""Synthetic teaching example. Dry-run unless --send is specified."""
import argparse
import getpass
import json
import socket
import time
import urllib.error
import urllib.request
import warnings

INCIDENT = """Synthetic incident, all times UTC:
14:02 checkout release r42 finished.
14:06 checkout timeout rate rose from 0.2% to 7%.
14:08 database CPU remained at 35%; connection-pool wait rose.
14:11 responder began rollback. No post-rollback metrics supplied.
No customer count, traces, or root-cause analysis supplied.
"""

def make_request(model, providers):
    return {
        "model": model,
        "stream": False,
        "max_completion_tokens": 500,
        "messages": [
            {"role": "system", "content": (
                "Write an incident handoff in three sections: Observed facts, "
                "Uncertainty, Next checks. Use only the supplied evidence. "
                "Keep timestamps. Do not claim a root cause or recovery "
                "without evidence. Do not execute or suggest automatic changes."
            )},
            {"role": "user", "content": INCIDENT},
        ],
        "provider": {
            "only": providers,
            "allow_fallbacks": True,
            "require_parameters": True,
            "data_collection": "deny",
            "zdr": True,
            "sort": "price",
            "max_price": {"prompt": 1, "completion": 2},
        },
    }

def extract_answer(data):
    if not isinstance(data, dict):
        raise ValueError("Expected a response object")
    if data.get("error"):
        raise ValueError("API returned an error object")
    choices = data.get("choices")
    if not isinstance(choices, list) or not choices:
        raise ValueError("Missing choices")
    choice = choices[0]
    if not isinstance(choice, dict) or not isinstance(choice.get("message"), dict):
        raise ValueError("Malformed choice")
    if choice.get("finish_reason") != "stop":
        raise ValueError("Incomplete or unexpected finish; review before use")
    content = choice["message"].get("content")
    if not isinstance(content, str) or not content.strip():
        raise ValueError("No usable text returned")
    return content

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--model", required=True)
    parser.add_argument("--provider", action="append", required=True)
    parser.add_argument("--send", action="store_true")
    args = parser.parse_args()
    body = make_request(args.model, args.provider)
    if not args.send:
        print(json.dumps(body, indent=2))
        return
    try:
        with warnings.catch_warnings():
            warnings.simplefilter("error", getpass.GetPassWarning)
            key = getpass.getpass("OpenRouter API key (hidden): ")
    except getpass.GetPassWarning:
        raise SystemExit("Hidden input unavailable; use an interactive terminal")
    request = urllib.request.Request(
        "https://openrouter.ai/api/v1/chat/completions",
        data=json.dumps(body).encode(),
        headers={"Authorization": "Bearer " + key,
                 "Content-Type": "application/json"},
        method="POST",
    )
    started = time.monotonic()
    try:
        with urllib.request.urlopen(request, timeout=45) as response:
            data = json.load(response)
    except urllib.error.HTTPError as error:
        raise SystemExit(f"HTTP {error.code}; inspect Activity before retrying")
    except (urllib.error.URLError, socket.timeout, TimeoutError):
        raise SystemExit("Transport failure; remote completion is unknown")
    except (ValueError, KeyError, IndexError, TypeError):
        raise SystemExit("Unusable response; inspect Activity before retrying")
    if not isinstance(data, dict):
        raise SystemExit("Malformed response; inspect Activity before retrying")
    print(json.dumps({"id": data.get("id"), "model": data.get("model"),
                      "usage": data.get("usage"),
                      "elapsed_seconds": round(time.monotonic()-started, 2)},
                     indent=2))
    try:
        answer = extract_answer(data)
    except ValueError as error:
        raise SystemExit(str(error) + "; metadata retained above")
    print("\nDRAFT FOR HUMAN REVIEW\n" + answer)

if __name__ == "__main__":
    main()

Read the settings as a policy for this request

Suppose the named provider is unavailable. With only, another provider outside that list stays ineligible even when fallbacks are enabled. With order instead, the name expresses a preference; permitted fallback may reach another provider. Here price sorting ranks only the eligible endpoints, and require_parameters excludes those that cannot support the request’s parameters.

An endpoint charging more than the example’s token-rate limits is also ineligible. Those max_price limits do not cap the number of calls or total task spending. If the intersection is empty, inspect which condition excluded the route before changing it. The routing reference defines how the restrictions combine.

If no permitted route works, display the original incident observations. The responder loses draft assistance but keeps the facts, without silently sending them to an unapproved destination merely to fill an output box.

Check the handoff and find out who produced it

A useful handoff retains timestamps, separates observations from possible causes and asks for post-rollback measurements, pool waits or relevant traces. It leaves recovery unknown. These are criteria for the synthetic input, not a captured model result.

Reject invented customer counts, database-health conclusions from steady CPU, or recovery inferred from rollback starting. Keep the observations beside the fluent draft to catch those leaps.

The generation ID links local output to accounting. Use Activity or the metadata endpoint for actual provider, model, usage and cost. Requested and served routes are separate facts.

Timeout can leave remote completion unknown; inspect Activity before repeating. For truncation, read finish state and usage rather than blindly resending. The error guide describes failures; your application owns retry decisions.

When a second model becomes useful

After several representative tests, a second acceptable model may become a useful backup. Replace the single model field with a priority-ordered models array:

"models": ["YOUR_PRIMARY_MODEL_ID", "YOUR_APPROVED_FALLBACK_MODEL_ID"]

Fallback reacts to errors such as rate limits, availability and context length, not to a completed handoff inventing recovery. The response identifies the model used and its pricing applies. Restrictions must permit its provider. See fallback behavior.

Test each model first. A backup that fills in missing recovery evidence can improve API success while worsening the application. The single-model baseline makes that visible.

Return to the customer notice: did another model improve the explanation while preserving time, duration and the exception? That is a reason to compare further. OpenRouter earns its extra layer when the freedom of choice saves more work than routing adds. Begin with one understood route and let actual answers guide the next choice.

Sources & context

Sources linked in this article. Read alongside the author’s analysis; a citation does not independently verify a publisher’s claims.

Report an error or outdated detail

Keep reading

Where to go next