go2llm API documentation

403 unsupported_country_region_territory and "region not supported"

A 403 means the server understood the request and recognised the credential, and is refusing anyway. Two very different things hide behind that: the vendor refusing your location, and a gateway refusing your key's permissions. The fixes have nothing in common, so read error.code before doing anything.

Written 2026-09-12.

The vendor version: region refusal

OpenAI returns this when the request originates from a country it does not serve:

{
  "error": {
    "code": "unsupported_country_region_territory",
    "message": "Country, region, or territory not supported",
    "type": "request_forbidden",
    "param": null
  }
}

Anthropic's equivalent arrives as a 403 during signup or on the API, and Claude Code surfaces it as a generic failure to reach the service.

Two properties of this refusal catch people out.

It is checked at more than one layer. Location is inferred from the request IP, but also from the billing card's country, the phone number used at signup, and the account's history. A VPN changes exactly one of those four. That is why "I turned on a VPN and it still fails" is the normal outcome rather than the surprising one — and why an account that once worked can start failing later, when a check runs against a signal the VPN never touched.

It is a terms-of-service position, not a bug. There is no header to set and no parameter to pass. Retrying is pointless, and routing around it with borrowed identity documents or someone else's card is how accounts get terminated with the balance still on them.

The practical options are: use a provider that serves your region, or use a protocol-compatible gateway that does. go2llm is the second kind — an Anthropic- and OpenAI-compatible API with its own catalog and its own pricing, which is why the vendors' region checks are not part of the path. It carries no vendor affiliation and no vendor authorization; it matches the wire protocol, nothing more.

The gateway version: four different 403s

go2llm returns 403 for four distinct reasons, and the code field tells them apart:

error.codeMeaningFix
inactiveThe key was revokedIssue a new key in the dashboard
expiredThe key is past its expiry dateIssue a new key or extend the current one
modelThe model is not on this key's allow-listAdd the model, or use a key that permits it
ipYour current IP is not in the key's allow-listAdd the IP, or clear the allow-list for that key
{
  "error": {
    "type": "model",
    "code": "model",
    "message": "model not allowed for this key",
    "request_id": "gw_1a2b3c4d5e6f708192a3b4c5d6e7f809"
  }
}

The ip case is the one that bites in production. A key restricted to an office IP works on a laptop in the office and fails from CI, from a cloud function, or from home — same key, same code, different egress address. Cloud runners in particular rotate egress IPs, so an allow-list that worked last month can fail this month without anyone changing anything.

The model case is usually a typo in the model ID rather than a real permission problem. Check the exact public ID first:

curl -s https://go2llm.tech/v1/models | python3 -m json.tool | head -30

That endpoint needs no key. If the ID you are sending is not in that list, you have a 404 model_not_found, not a permissions problem.

Telling the two apart in one command

Run the request with no SDK and no environment variables and look at what comes back:

curl -i https://go2llm.tech/v1/messages \
  -H "x-api-key: $GO2LLM_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model":"claude-sonnet-5","max_tokens":16,"messages":[{"role":"user","content":"ping"}]}'

A 200 means the key, the model and your IP are all fine, and whatever failed is in your client's configuration. A 403 with code in inactive | expired | model | ip points straight at which of the four to fix. A 401 is a different page entirely — see 401 authentication_error.

Handling 403 in code

Like 401 and 402, a 403 is fatal. Do not retry it; report which of the four causes fired so whoever reads the log knows what to change:

import httpx

FIXES = {
    "inactive": "key revoked — issue a new one",
    "expired":  "key expired — issue a new one",
    "model":    "model not allowed for this key — check GET /v1/models and the key's allow-list",
    "ip":       "current IP not in the key's allow-list — check the egress address of this host",
}

r = httpx.post(url, headers=headers, json=payload, timeout=120.0)
if r.status_code == 403:
    err = r.json().get("error", {})
    code = err.get("code", "unknown")
    raise SystemExit(f"403 {code}: {FIXES.get(code, err.get('message'))} "
                     f"request_id={err.get('request_id')}")

Related errors

FAQ

Will a VPN fix unsupported_country_region_territory? Sometimes, temporarily, and not reliably. Location is inferred from several signals — request IP, billing card country, signup phone number, account history — and a VPN changes only the first.

What is go2llm's relationship to Anthropic and OpenAI? None — it is a protocol-compatible gateway with its own published catalog and its own prices, carrying no vendor affiliation or authorization. Requests go to go2llm.tech, not to the vendors' endpoints, which is why the vendors' regional checks are not part of the path.

Why does the same key work locally and fail in CI? Almost always the ip code: the key carries an IP allow-list and your CI runner's egress address is not on it. Cloud runners rotate addresses, so allow-listing a single IP is fragile there.

Does a 403 charge my balance? No. The check happens before any provider call and before cost is settled.


Get a key and make your first request in 3 minutes → go2llm.tech. Registration credits $1 to your balance; no foreign card and no VPN required. Per-model prices are on the pricing page.