Your request fails with 403, a 402 shows up in the logs right after, and you're ready to swap the key and spin up a VPN. On September 22, 2026 I ran the same checks by hand from a server in Nuremberg, Germany, in a netcup data center: OpenRouter answered with JSON, not a block page.
If your balance is empty and you have no way to top it up, the working route is to move your requests to altrouter. Two fields change in your code: base_url and api_key; sign up.
Still, look at the response body first. The same 403 can be a protective HTML page sitting in front of the API, or a JSON refusal from the application itself. These are different problems, and a new key won't help when the request never reached the API at all.
Checking OpenRouter without a key
I started with an unauthenticated request. The model doesn't matter here: the point was to see who actually answers the connection.
curl -s https://openrouter.ai/api/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{"model":"openai/gpt-5.6","messages":[{"role":"user","content":"hi"}]}'
Response:
HTTP 401, application/json: {"error":{"message":"No cookie auth credentials found","code":401}}
401 is an authorization refusal, and the JSON shows the request reached the API. If a protective layer had cut the connection, you'd get HTML instead of a structured error.
Next I passed a made-up key:
curl -s https://openrouter.ai/api/v1/chat/completions \ -H "Authorization: Bearer sk-or-v1-0000" \ -H "Content-Type: application/json" \ -d '{"model":"openai/gpt-5.6","messages":[{"role":"user","content":"hi"}]}'
Response:
HTTP 401, application/json: {"error":{"message":"User not found.","code":401}}
JSON again. So on September 22, 2026 the Nuremberg server was not turned away by OpenRouter's protective layer. An extra check of the model list returned HTTP 200, application/json in 0.12 s.
curl https://openrouter.ai/api/v1/modelsThis doesn't prove that every request from every address will always get through. What matters for diagnosis is something else: at a specific location on a specific date the service was answering, and the reason for the refusal was in the response body.
Why one 403 isn't like another
403 Forbidden is just a status code. It doesn't tell you whether the request reached the application.
If the body is JSON in the API's error format, check the key, model access and balance. If the body is HTML with Sorry, you have been blocked or access denied by security policy, a protective layer stopped the request before the key was even looked at.
I didn't reproduce the HTML block in this test: from the Nuremberg server OpenRouter returned JSON. But the shape of that response is well known, and the typical causes are a VPN, a data-center IP address and too many requests from a single IP.
Here a VPN can be the cause of the error, not the cure. Exit addresses of mass-market VPNs pick up a bad reputation from scrapers and bots. A data-center address can also be filtered more strictly than a home connection.
Don't confuse this with 429 Too Many Requests. A 429 is a JSON response from the API itself when you go over your account's limit, while an HTML page means the request never reached the application.
402 insufficient credits: the most common "block"
A separate scenario looks even more convincing: the API recognizes the key but returns 402 insufficient credits. That means the service works, but there's no money left to pay for tokens.
In summer 2026, users whose accounts had a Russian geography received emails saying their payment methods would no longer be supported. They were offered to spend the remaining credits or request a refund. Payments were switched off, not access, so an integration could keep running until the last of the balance was spent.
That's how a delayed failure happens. In the morning requests go through, by evening the balance hits zero, and the app is no longer looking at a temporary network glitch but at a 402.
Work out your remaining balance and average daily spend. Balance divided by spend gives you a rough runway in days - your technical deadline before the next failure.
The main takeaway is simple: OpenRouter is not blocked in Russia. Payment acceptance is switched off for accounts with a Russian geography, so the problem often starts not with the network route or the key, but with being unable to top up the balance.
The outage may be on the vendor's side
OpenRouter is a gateway to models from many vendors. If one of them is failing, the error can look like a problem with OpenRouter itself.
Read the response text and try a model from another vendor. If a request to Gemini goes through and one to Claude doesn't, you don't necessarily need to replace the whole gateway: the cause may be on Anthropic's side.
This is where the one-size-fits-all recipe breaks down. You can't reliably tell where a failure comes from by the status alone, so check the response body and compare models.
Checklist before you switch integrations
- Find the response body, not just the status line.
402orinsufficient credits- check your balance and spend.- JSON with
401- check the key and the account. - HTML with
403- check the VPN, the server address and request rate. - Compare requests from two places: your local machine and a server.
- If you can't top up, prepare a second route through an OpenAI-compatible API.
A new key won't fix an HTML block. Changing DNS won't add credits. In debugging these are two separate branches, and mixing them up costs you time.
A final run through altrouter
I sent the same short question through altrouter. For this request I used the gemini-3.1-flash-lite model.
curl -s https://api.altrouter.ai/v1/chat/completions \ -H "Authorization: Bearer $ALTROUTER_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"gemini-3.1-flash-lite","max_tokens":60,"messages":[{"role":"user","content":"Answer in one word: capital of France?"}]}'
Response:
HTTP 200 in 6.8 s: {"object":"chat.completion","model":"gemini-3.1-flash-lite","choices":[{"index":0,"message":{"role":"assistant","content":"Paris"},"finish_reason":"stop"}],"usage":{"prompt_tokens":96,"completion_tokens":3,"total_tokens":99}}
usage is the block where the API reports token consumption for the request. Here it's just 99 tokens: 96 input and 3 output.
For a Python client the migration looks like this:
from openai import OpenAI client = OpenAI( base_url="https://api.altrouter.ai/v1", # was: https://openrouter.ai/api/v1 api_key="ar-...", # was: sk-or-v1-... ) r = client.chat.completions.create( model="gemini-3.1-flash-lite", messages=[{"role": "user", "content": "Answer in one word: capital of France?"}], ) print(r.choices[0].message.content)
base_url is the address the SDK sends requests to. api_key is the authorization key passed to that address.
As of September 22, 2026, altrouter had 79 public models. gemini-3.1-flash-lite cost $0.225 per 1M input tokens and $1.35 per 1M output tokens, versus $0.25 and $1.50 from Google - 10% below the vendor's official price.
You can pay with a Russian bank card, via SBP (Russia's Faster Payments System) or in crypto. Confirming your Telegram adds $1 to your balance to try things out, and on most models the price is 10–15% below the vendor's official one. The route has a downside you should factor in too: a specific model or API may be missing from the catalog.
What to check before you move
Until October 27, 2026, POST https://api.altrouter.ai/v1/messages returned 404. Now there's an Anthropic-compatible endpoint, and tools built on the Anthropic format, including Claude Code, connect via ANTHROPIC_BASE_URL=https://api.altrouter.ai.
The catalog is narrower than OpenRouter's: 122 models as of October 27, 2026, with almost no open-weight models. So if your code is tied to a specific model name, check it against the catalog first.
This route doesn't replace a compatibility check. If your library can change base_url, uses the OpenAI format and the model you need is in the catalog, swapping two fields is enough.
If you're staying: how to top up
For OpenRouter, what's left is a foreign card, a payment intermediary or crypto. Fees and restrictions depend on the method; the top-up options, with a calculator, are collected on how to top up OpenRouter from Russia.
FAQ
What does "Sorry, you have been blocked" on openrouter.ai mean?
It's an HTML page from a protective layer, not a JSON response from the API. The cause may be a VPN, a data-center address or a high request rate from a single IP. If you're hitting limits on the API itself, that's a different error - see how to handle 429 rate limits.
How do I fix "forbidden: access denied by security policy" on OpenRouter?
That response comes from a filter in front of the service. Look at your exit point and network address instead of issuing a new key blindly. If you need a second route anyway, an OpenRouter alternative on the OpenAI format only takes two changed fields.
Is "access denied by security policy" an OpenRouter API error?
Not when it arrives as HTML: that means the request was stopped before it reached the application. JSON means the request reached the API, and the cause is in the key, the model or the balance - here is what JSON API errors look like.
Is OpenRouter blocked in Russia?
The service itself isn't blocked; payments are disabled for accounts with a Russian geography. An integration keeps working while there are credits left on the balance. Ways to top up anyway are collected on how to top up OpenRouter from Russia.
What does a 403 error from OpenRouter mean?
A 403 can come from a protective layer or from the application itself. The response body tells them apart: HTML points to a network filter, JSON to an API error. Status codes and what they mean are listed in the API errors reference.
Will a VPN help if OpenRouter isn't working?
A VPN is often what causes the HTML 403 in the first place. Start by sending the request with the VPN off. If the real problem is an empty balance, no network change will help - see the OpenRouter alternative.