Making Your First Request
Explore the fundamental process of making your first API request with OpenRouter. Learn to obtain an API key, construct requests compatible with multiple providers, switch models by changing a simple parameter, and understand the response structure for effective integration and cost management.
In the last lesson, we established why OpenRouter is a strategic choice that solves ecosystem fragmentation by acting as a unified routing layer. Now, we move from theory to practice.
This lesson walks through the fundamental mechanics of making your first API call. We will get an API key, construct a request, send it to two different providers with a single line of code change, and learn how to interpret the response.
Getting your API key
Before you can make any requests, you need to authenticate your application. This is done using an API key. You can create a new key from your OpenRouter settings page. Once you create a key, its full value is only shown once. Copy it immediately and store it securely, as you would a password.
For security and operational hygiene, follow these best practices when managing your keys:
Assign descriptive names: Name your keys clearly to track their usage (e.g.,
prod-text-generation-v1-2024-q3).Set credit limits: Apply a specific spend limit to each key to cap financial exposure and prevent runaway costs.
Keep keys out of code: Never hardcode your API key in your source code. Use an environment variable (
OPENROUTER_API_KEY) or a secrets management system.Use guardrails: For fine-grained control, you can apply “Guardrails” to a key. This allows you to enforce budgets, restrict usage to an approved list of models, or enforce a zero-data-retention policy.
The anatomy of an API request
All standard chat completion requests are sent to a single endpoint:
https://openrouter.ai/api/v1/chat/completions
A request consists of two main parts: the headers and the body.
Headers: Authentication and attribution
The request must include a few key headers to be processed correctly.
Authorization: Bearer YOUR_API_KEY: This is required for authentication. ReplaceYOUR_API_KEYwith the key you just generated.Content-Type: application/json: This tells the server that you are sending data in JSON format.
While not required, OpenRouter highly recommends including two additional headers for app attribution:
HTTP-Referer: https://your-app.com: Your application’s primary URL.X-Title: Your App Name: Your application’s display name.
Including these headers allows your app to appear on OpenRouter’s public leaderboards and gives you access to more detailed analytics about your application’s model usage.
Body: The power of compatibility
OpenRouter’s request body is fully compatible with the OpenAI API. Any library, framework, or tool built to work with OpenAI will work with OpenRouter with minimal changes.
The body is a JSON object with two primary fields:
model: A string identifying the model you want to use (e.g.,openai/gpt-5.2).messages: An array of message objects that form the conversation history.
Let’s put this all together and make a call.
The code below accesses paid models. By default, a free API key will result in 403 errors.
Making the call and switching models
Here is a simple Python script using the requests library to call OpenRouter. Notice how the model name is stored in a variable.
This script demonstrates the core value proposition of OpenRouter. We called two different models from two different providers by changing a single string: the same endpoint, the same headers, the same code.
Deconstructing the response
When you run the script, you will get back a JSON response that also follows the OpenAI structure.
{"id": "gen-1772775724-guljboPk6lJyej3tglzy","object": "chat.completion","created": 1772775724,"model": "openai/gpt-5.2-20251211","provider": "OpenAI","choices": [{"index": 0,"logprobs": null,"finish_reason": "stop","native_finish_reason": "completed","message": {"role": "assistant","content": "Paris.","refusal": null,"reasoning": null}}],"usage": {"prompt_tokens": 13,"completion_tokens": 6,"total_tokens": 19,"cost": 0,"is_byok": true,"prompt_tokens_details": {"cached_tokens": 0,"cache_write_tokens": 0,"audio_tokens": 0,"video_tokens": 0},"cost_details": {"upstream_inference_cost": 0.00010675,"upstream_inference_prompt_cost": 2.275e-05,"upstream_inference_completions_cost": 8.4e-05},"completion_tokens_details": {"reasoning_tokens": 0,"image_tokens": 0,"audio_tokens": 0}}}
Let’s look at the key parts:
model: This field confirms which model ultimately served the request. Notice how the provider (anthropic) is part of the model’s unique name.choices: This is an array containing the model’s response(s). The main content is insidechoices[0].message.content.usage: This object is vital for cost management. It tells you exactly how many tokens were consumed for the prompt and the completion. Because pricing is per-token, this is the ground truth for how much a request costs.
Finding detailed routing information
The model field tells you which model and provider succeeded. To find out which providers were attempted during a fallback, query the /api/v1/generation endpoint using the id from the response. It returns raw metadata, including an array of every provider that was attempted and the HTTP status code each returned. This is useful for debugging complex production routing and fallback strategies.
Copy the generation ID from the output of the previous widget and set it as the generation_id variable in the code widget below:
Conclusion
One endpoint, one authorization header, one model parameter. That consistency is what makes it practical to swap providers without touching the rest of your code. In the next lesson, we will explore the full model marketplace: how models are named, what metadata they expose, and how to select the right one for a given task.