In this article
The process holding the API key needs its own authorization and spending boundary, even when the interface is written in JavaScript.
A first OpenRouter call from Node.js can use server-side fetch. The important boundary is between the user’s browser and the process authorized to spend API funds. Keep the OpenRouter credential in the latter, even if both the frontend and backend are written in JavaScript.
Put the call on the server
This illustrative server example requires a Node.js runtime supporting the shown APIs and authorized environment settings. It contains no working credential or fixed model selection.
const key = process.env.OPENROUTER_API_KEY;
const model = process.env.OPENROUTER_MODEL;
if (!key || !model) throw new Error('Missing server configuration');
const response = await fetch(
'https://openrouter.ai/api/v1/chat/completions',
{
method: 'POST',
headers: {
Authorization: `Bearer ${key}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model,
messages: [{ role: 'user', content: 'Reply: ready' }],
max_tokens: 32,
}),
signal: AbortSignal.timeout(60000),
},
);
const data = await response.json();
if (!response.ok || data.error) {
throw new Error(`API request failed: ${response.status}`);
}
console.log(data.id, data.model);
Do not place this Authorization header in code delivered to the browser. A server environment variable also needs careful logging and deployment handling; its location alone does not guarantee secrecy.
Bound incoming application requests
Your backend needs its own user authorization, input limits and permitted model selection. Otherwise a browser request can turn a small demo into an unrestricted spending interface. Choose a short output limit for the first test and increase it only after understanding usage.
For example, let a test user submit one small text task rather than arbitrary external URLs, tool definitions and model overrides. These restrictions belong to the application boundary and remain relevant even when the external key itself has a limit.
Distinguish failure states
Separate missing local configuration, HTTP rejection, network timeout and malformed response. Save safe status fields and identifiers without echoing the credential or private input. A JSON parsing exception may hide a different upstream response, so preserve enough context to identify the failing layer.
An HTTP success still needs application-level checks. Inspect content, finish state and usage instead of assuming the presence of a response ID proves the task succeeded. Keep the baseline request while introducing streaming, tools or a gateway.
Test cancellation and handover
Cancel a controlled request and observe how the frontend, backend and external connection finish. A closed browser view does not automatically prove all downstream work stopped. Define a total task deadline and a bounded retry policy, keeping attempts distinguishable in your records.
Document who owns the credential and how the service receives a replacement. For a hardware pilot, keep this integration work separate from GPU acceptance.
Official references: openrouter.ai — quickstart, openrouter.ai — authentication.
Related guides: Setting up an OpenRouter API key for an application, Reading an OpenRouter streaming response incrementally.