Diagnostic LLM APIs
The same key works on your laptop and 403s in production
The feature works. It works on two laptops, it works in CI, it worked in the preview deployment, and it has never once failed in review. It is promoted to production on a Thursday and every request returns 403. Not a rate limit, not an expired key, not a model id: a flat refusal with a message about countries. Nobody changed the code. Nobody rotated the key. What changed is that the edge platform picked a point of presence closer to the user, and the request now leaves the internet somewhere the provider does not serve.
Run the probe where the problem is. GET /v1/models with the same read-only key your application uses, from inside the production egress path — the same VPC, the same region, the same edge runtime. A 200 proves that geography is allowed from that host. A 403 with code: "unsupported_country_region_territory" proves it is not.
One run is not enough, because a 403 on its own has more than one cause. So run the identical probe from a host you already trust, and compare. The script prints a one-line JSON observation which you carry to the other host as LLM_EGRESS_BASELINE; it holds a provider, a status code and an error code, and nothing else. 403 here and 200 there is geography. 403 on both is the account rather than the location. 401 on both is the credential, which is a different note.
Both providers are probed if both keys are set, because both geo-block and the answer can differ between them. OpenAI documents the refusal as a 403 whose code is unsupported_country_region_territory. Anthropic publishes a list of the countries, regions and territories it can serve, and does not document a distinct error code, so the script records whatever error.type comes back verbatim rather than asserting one it cannot cite.
Two things the script deliberately does not do. It does not call any third-party service to discover its own public IP or country: that is an external dependency added to a diagnostic, and the provider's own answer is the only one that matters anyway. And it does not print a proxy as a repair. The fix is to pin execution to a supported region — a Vercel regions config, a Cloud Run or Lambda redeploy in a supported region, a VPN turned off — and routing around a geographic restriction is not a repair this section will suggest.
The problem in plain words
The block is on the request's egress IP, not on your account's country. That single sentence explains every strange thing about this failure. Your organization is in a supported country, your billing address is fine, your key is fine, and the request is refused because of where the packet came from — which is a property of the machine, not of you.
Modern deployment targets move that machine without telling you. Edge platforms run your function at whichever point of presence is nearest the user, so the same code executes from a different country per request. A cloud region chosen for latency to a customer base can sit outside the supported list. A corporate VPN relocates a developer's egress. A newly added region in a multi-region deployment inherits the code and not the assumption.
So the failure has the worst possible test profile: total from the affected host and absent from every host where anybody looks. CI passes, staging passes, the laptop passes, and the only environment that fails is the one you cannot easily attach a debugger to. And because it is a 403 rather than a 429 or a 500, no amount of retrying helps; a retry loop simply produces the same refusal faster.
The second-order problem is diagnosis by guesswork. A 403 gets read as a permissions problem, so somebody rotates the key, which changes nothing, and then somebody widens the key's scopes, which changes nothing and leaves you with a more powerful credential than you started with. The refusal is not about the credential at all, and the only cheap way to prove that is to send the identical request with the identical key from somewhere else.
Why it happens
One observation cannot be a finding here, because the variable is the machine. Every other note in this section reads state that is the same wherever you read it from. This one reads state that only exists relative to a location, so the unit of evidence is a pair: the same call, the same key, two hosts. That is also why the script's output is designed to be carried — a small JSON blob you paste into the environment of the second run — rather than assumed.
The pair is what separates geography from credentials, and that separation is the whole value. A 403 read alone sends people to the key page. A 403 here beside a 200 there, on the same key, makes the credential impossible as an explanation. The script states each of those outcomes as its own verdict and, in the credentials case, says plainly that this is not the note and points elsewhere.
Both providers get probed, and they do not answer the same way. OpenAI documents a 403 with unsupported_country_region_territory, which is a code the script can match on and treat as proof. Anthropic publishes a supported-regions list rather than a documented error code for this case, so the script records the returned error.type verbatim and grades it as an unexplained 403 rather than pattern-matching a string it has no source for. Inventing that code would be worse than not having it.
No third-party geolocation call, on purpose. It would be easy to fetch a public IP and look up its country, and it would add a network dependency, a privacy question and a second thing that can be wrong to a script whose entire job is to reduce ambiguity. The provider's 403 already is the answer to "is this location allowed", and it is the only authority that counts.
The repair is a region pin, never a proxy. The documented fix is to pin execution: an explicit region on an edge function, a redeploy of a container or function into a supported region, a VPN disabled. Routing the provider's host through an egress somewhere else is a way to defeat a restriction rather than to resolve one, and this section prints repairs you should actually run.
Corroborating this from the usage report would be somebody else's reading. A deployed project with no requests in it does look like a hard block, and it also looks like a dead credential, a paused deploy or a feature nobody used — which is exactly the ambiguity the zero-usage note exists to grade. This script stays with the probe pair, where the evidence is unambiguous.
The fix, as a flow
This is the one fault in the section where the interesting variable is the machine. The call is trivial and the key is the same one that works on your laptop, so a single run proves nothing at all: it has to be issued from the production egress path and compared against the identical run from a host you already trust. Two observations, one difference, and the difference is a place.
How to fix it
Run it first from a host you trust, and keep the blob
A laptop, a CI runner, a bastion in a region you know works. The script prints one line of JSON: provider, status, error code. It contains no key and no hostname. That line is the baseline.
Run it again from the production egress path
The same VPC, the same region, the same edge runtime, with LLM_EGRESS_BASELINE set to the line from step one. Same code, same key, different machine, which is the only variable that matters.
Read the pair rather than the status
403 here and 200 there is geography. 403 on both is the account or an organization-level restriction. 401 on both is the credential and belongs elsewhere. 200 here is the end of it, whatever anybody suspected.
Record the error code verbatim, and only claim what it says
unsupported_country_region_territory on OpenAI is documented and is treated as proof. Anything else, including any Anthropic 403, is printed exactly as returned and graded as an unexplained refusal rather than assigned a cause the script cannot cite.
Print the region pin
For an edge function, the platform's region configuration. For Cloud Run, Lambda or a container, a redeploy into a supported region. For a VPN, disable it. Never a proxy, and the script does not offer one.
How to check it worked
Re-run from the pinned deployment with the same baseline. The production observation should move to 200 and the verdict to clear. The check that matters more is the negative one: if you fixed it by routing the provider's host through somewhere else, this script will now say clear and it will be telling you about the proxy rather than about your deployment, which is precisely why the repair it prints is a region pin.
# on a host you trust
python3 llm_egress_region_probe.py
# openai 200 reachable this egress path is allowed for this key
# anthropic 200 reachable this egress path is allowed for this key
# baseline: {"anthropic":{"code":"","status":200},"openai":{"code":"","status":200}}
# from the production egress path
LLM_EGRESS_BASELINE='{"anthropic":{"code":"","status":200},"openai":{"code":"","status":200}}' \
python3 llm_egress_region_probe.py
# openai 403 region-blocked unsupported_country_region_territory
# geography-isolated openai: 403 here and 200 from the baseline host on the same
# key, so the difference is the egress path and not the
# credential
# repair: pin execution to a supported region. On Vercel, export const config
# = { regions: ['iad1'] }. On Cloud Run, Lambda or a container,
# redeploy in a supported region. On a VPN, turn it off.
# repair: do not route the provider host through another egress to get around
# this. Move the workload, not the packets.
# anthropic 200 reachable this egress path is allowed for this key
# clear anthropic: 200 from this host, so the egress path is fine
# 1 finding(s)
The full code
Two GETs at most and six pure functions. error_code, which reads error.code and falls back to error.type so one function covers both providers' error envelopes; observation, which reduces a response to a provider, a status and a code and is the only thing that ever leaves the process; classify, which grades one observation and treats only the documented code as proof of a region block; compare, which is the note, and is the only function that sees two hosts; blob and load_baseline, which round-trip the observation as one sorted line of JSON with no credential in it; and repair_lines, which prints region pins and refuses to print a proxy.
"""Prove whether a 403 is about where the request left from, or about the key.
Read only. One GET of /v1/models per provider whose key is present, and
nothing else. No request body is constructed, nothing is generated, nothing is
billed, and no third-party service is contacted -- in particular the script
never looks up its own public IP, because the provider's own answer to "is this
location allowed" is the only authority that matters.
The variable here is the machine. Every other reading in this section is the
same wherever it is taken from; this one only exists relative to a location, so
the unit of evidence is a pair. Run it once from a host you trust, carry the
one-line observation it prints, and run it again from the production egress
path with that line in LLM_EGRESS_BASELINE.
The blob carries a provider, a status and an error code. It never contains a
key, a hostname or anything else, and there is a test that says so.
"""
import argparse
import json
import logging
import os
import sys
import requests
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("llm_egress_region_probe")
# Both are model listings: free, read-only, and refused by a geographic block
# exactly as any other call from the same host would be.
PROVIDERS = {
"openai": {"url": "https://api.openai.com/v1/models",
"env": "OPENAI_API_KEY"},
"anthropic": {"url": "https://api.anthropic.com/v1/models",
"env": "ANTHROPIC_API_KEY"},
}
# The one code this script treats as proof of a geographic block, because it is
# the one that is documented. Anthropic publishes a supported-regions list and
# no distinct code for this case, so an Anthropic 403 is recorded verbatim and
# graded as unexplained rather than assigned a cause with no source.
BLOCK_CODE = "unsupported_country_region_territory"
FINDINGS = ("geography-isolated", "region-blocked-unconfirmed",
"region-blocked-everywhere", "forbidden-unexplained")
def error_code(body):
"""The provider's error code from a JSON body. Pure. Empty when absent.
One function for both envelopes: OpenAI puts a machine-readable string in
error.code and Anthropic puts one in error.type, and falling back covers
both without branching on the provider.
"""
error = (body or {}).get("error") if isinstance(body, dict) else None
if not isinstance(error, dict):
return ""
for field in ("code", "type"):
value = error.get(field)
if value:
return str(value).strip()
return ""
def observation(provider, status, body):
"""One probe result, reduced. Pure. The only thing that leaves the process.
Three fields on purpose. A response body can contain an organization name,
a request id or a message quoting the request, and none of that needs to be
carried between two machines to answer a question about a status code.
"""
return {"provider": str(provider),
"status": None if status is None else int(status),
"code": str(error_code(body) or "")}
def classify(obs):
"""Grade one observation. Pure. Returns (state, detail)."""
obs = obs or {}
status = obs.get("status")
code = str(obs.get("code") or "")
if status is None:
return ("unreachable",
"no response at all, which is a network answer rather than a "
"policy one")
status = int(status)
if status == 200:
return ("reachable", "this egress path is allowed for this key")
if status == 403 and code == BLOCK_CODE:
return ("region-blocked", BLOCK_CODE)
if status == 403:
return ("forbidden-other",
"403 with code %r, which is not the documented geographic block"
% (code or "(none returned)"))
if status == 401:
return ("credentials", "401, which is the key and not the location")
if status == 429:
return ("rate-limited",
"429, so this host reaches the provider fine and the question "
"is capacity rather than geography")
return ("unexpected", "%d with code %r" % (status, code or "(none)"))
def compare(local, baseline):
"""The note. Pure. Returns (state, detail). The only two-host function.
A 403 read alone sends people to the key page. A 403 here beside a 200
there, on the same key, makes the credential impossible as an explanation,
and that is the entire reason this script asks to be run twice.
"""
local_state, local_detail = classify(local)
provider = (local or {}).get("provider") or "(unknown)"
if not baseline:
if local_state == "region-blocked":
return ("region-blocked-unconfirmed",
"%s: 403 %s from this host. The code is documented, but "
"with no baseline this has not been separated from an "
"account-level restriction" % (provider, BLOCK_CODE))
if local_state in ("forbidden-other", "credentials"):
return ("no-baseline",
"%s: %s, and no baseline to compare it against. Run this "
"from a host you trust first" % (provider, local_detail))
return (("clear" if local_state == "reachable" else local_state),
"%s: %s" % (provider, local_detail))
base_state, _ = classify(baseline)
if local_state == "reachable":
return ("clear",
"%s: 200 from this host, so the egress path is fine"
% provider)
if local_state == "region-blocked" and base_state == "reachable":
return ("geography-isolated",
"%s: 403 here and 200 from the baseline host on the same key, "
"so the difference is the egress path and not the credential"
% provider)
if local_state == "region-blocked" and base_state == "region-blocked":
return ("region-blocked-everywhere",
"%s: 403 %s from both hosts, so this is the account or an "
"organization-level restriction rather than this deployment's "
"location" % (provider, BLOCK_CODE))
if local_state == "credentials" and base_state == "credentials":
return ("credentials-not-geography",
"%s: 401 from both hosts on the same key, which is the "
"credential and not the location" % provider)
if local_state == "credentials":
return ("credentials-here-only",
"%s: 401 here and %s from the baseline host. A key that "
"authenticates elsewhere and not here is usually a different "
"key in the environment, not a geographic block"
% (provider, base_state))
if local_state == "forbidden-other" and base_state == "reachable":
return ("forbidden-unexplained",
"%s: %s here and 200 from the baseline host. The host is the "
"difference; the code is not one this script can attribute"
% (provider, local_detail))
return ("inconclusive",
"%s: %s here, %s from the baseline host"
% (provider, local_state, base_state))
def blob(observations):
"""The one line to carry to the other host. Pure. Sorted, and no secrets.
Keys are sorted so two runs of the same script produce a byte-identical
string, which makes it obvious when the thing being pasted around has
changed.
"""
payload = {}
for obs in observations or []:
row = obs or {}
payload[str(row.get("provider"))] = {
"status": row.get("status"),
"code": str(row.get("code") or ""),
}
return json.dumps(payload, sort_keys=True, separators=(",", ":"))
def load_baseline(raw):
"""{provider: observation} from the blob. Pure. Empty dict on anything odd.
Deliberately forgiving. The blob is pasted between machines by a human
under time pressure, and a mangled paste should produce "no baseline" and a
clear instruction rather than a stack trace on the host that is on fire.
"""
try:
parsed = json.loads(str(raw or "").strip() or "{}")
except ValueError:
return {}
if not isinstance(parsed, dict):
return {}
out = {}
for provider, row in parsed.items():
if not isinstance(row, dict):
continue
status = row.get("status")
try:
status = None if status is None else int(status)
except (TypeError, ValueError):
status = None
out[str(provider)] = {"provider": str(provider), "status": status,
"code": str(row.get("code") or "")}
return out
def repair_lines(state):
"""The repair for one verdict. Pure. Printed, never performed.
A region pin, never a proxy. Routing the provider's host through an egress
somewhere else defeats a restriction rather than resolving one, and this
section prints repairs that are meant to be run.
"""
pin = ("pin execution to a supported region. On Vercel, export const "
"config = { regions: ['iad1'] }. On Cloud Run, Lambda or a "
"container, redeploy in a supported region. On a VPN, turn it off.")
no_proxy = ("do not route the provider host through another egress to get "
"around this. Move the workload, not the packets.")
if state == "geography-isolated":
return [pin, no_proxy]
if state == "region-blocked-unconfirmed":
return ["run this same script from a host you already trust and paste "
"its blob into LLM_EGRESS_BASELINE here. One 403 does not "
"separate the location from the account.", pin]
if state == "region-blocked-everywhere":
return ["both hosts are refused, so moving this deployment will not "
"help. Check the organization's country and any access "
"restriction on the account before touching infrastructure."]
if state == "credentials-not-geography":
return ["not this note. The same key is refused from both hosts, which "
"is a credential question: check that the key exists, is "
"enabled, and belongs to the project you think it does."]
if state == "credentials-here-only":
return ["compare the environment on the two hosts. A key that works "
"from one machine and 401s from another is almost always a "
"different value in the environment rather than a location."]
if state == "forbidden-unexplained":
return ["record the error code exactly as printed and check the "
"provider's supported regions list for the country this host "
"egresses from.", pin]
if state == "no-baseline":
return ["run this from a host you trust and set LLM_EGRESS_BASELINE to "
"the blob it prints. Without the pair there is one status code "
"and no conclusion."]
return []
def probe(provider, key, timeout=30):
"""One GET. Returns (status, body). Never raises on a 4xx: it is the answer."""
spec = PROVIDERS[provider]
headers = {}
if provider == "openai":
headers["Authorization"] = "Bearer " + key
else:
headers["x-api-key"] = key
headers["anthropic-version"] = "2023-06-01"
try:
r = requests.get(spec["url"], headers=headers, params={"limit": 1},
timeout=timeout)
except requests.RequestException as exc:
log.debug("probe of %s failed: %s", provider, exc)
return (None, None)
try:
return (r.status_code, r.json())
except ValueError:
return (r.status_code, None)
def main():
ap = argparse.ArgumentParser(description=__doc__)
ap.add_argument("--baseline", default=os.environ.get("LLM_EGRESS_BASELINE"),
help="the blob printed by a run on a host you trust")
args = ap.parse_args()
present = [p for p in sorted(PROVIDERS)
if os.environ.get(PROVIDERS[p]["env"])]
if not present:
log.error("set OPENAI_API_KEY or ANTHROPIC_API_KEY. Both are used for "
"one read-only GET of /v1/models and nothing else")
return 2
baseline = load_baseline(args.baseline)
observations = []
findings = 0
for provider in present:
status, body = probe(provider, os.environ[PROVIDERS[provider]["env"]])
obs = observation(provider, status, body)
observations.append(obs)
state, detail = classify(obs)
emit = log.warning if state != "reachable" else log.info
emit("%-11s %s %-14s %s", provider,
"---" if obs["status"] is None else obs["status"], state, detail)
verdict, why = compare(obs, baseline.get(provider))
emit = log.warning if verdict in FINDINGS else log.info
emit("%-20s %s", verdict, why)
for line in repair_lines(verdict):
emit(" repair: %s", line)
if verdict in FINDINGS:
findings += 1
log.info("baseline: %s", blob(observations))
if not baseline:
log.info("no baseline was supplied. Carry that line to the other host "
"and run this again there")
log.info("%d finding(s)", findings)
return 1 if findings else 0
if __name__ == "__main__":
sys.exit(main())
/**
* Prove whether a 403 is about where the request left from, or about the key.
*
* Read only. One GET of /v1/models per provider whose key is present, and
* nothing else. No request body, nothing generated, nothing billed, and no
* third-party service contacted: the script never looks up its own public IP,
* because the provider's own answer is the only authority that counts.
*
* The variable is the machine, so the unit of evidence is a pair. Run it from
* a host you trust, carry the one-line blob, run it again from production with
* that line in LLM_EGRESS_BASELINE.
*/
const PROVIDERS = {
openai: { url: 'https://api.openai.com/v1/models', env: 'OPENAI_API_KEY' },
anthropic: { url: 'https://api.anthropic.com/v1/models', env: 'ANTHROPIC_API_KEY' },
};
// The one code treated as proof, because it is the one that is documented.
export const BLOCK_CODE = 'unsupported_country_region_territory';
const FINDINGS = new Set(['geography-isolated', 'region-blocked-unconfirmed',
'region-blocked-everywhere', 'forbidden-unexplained']);
/** The provider's error code from a JSON body. Pure. Empty when absent. */
export function errorCode(body) {
const error = (body && typeof body === 'object') ? body.error : null;
if (!error || typeof error !== 'object') return '';
for (const field of ['code', 'type']) {
if (error[field]) return String(error[field]).trim();
}
return '';
}
/** One probe result, reduced. Pure. The only thing that leaves the process. */
export function observation(provider, status, body) {
return {
provider: String(provider),
status: (status === null || status === undefined) ? null : Math.trunc(Number(status)),
code: String(errorCode(body) || ''),
};
}
/** Grade one observation. Pure. Returns [state, detail]. */
export function classify(obs) {
const row = obs ?? {};
const code = String(row.code ?? '');
if (row.status === null || row.status === undefined) {
return ['unreachable',
'no response at all, which is a network answer rather than a policy one'];
}
const status = Math.trunc(Number(row.status));
if (status === 200) return ['reachable', 'this egress path is allowed for this key'];
if (status === 403 && code === BLOCK_CODE) return ['region-blocked', BLOCK_CODE];
if (status === 403) {
return ['forbidden-other',
`403 with code '${code || '(none returned)'}', which is not the documented `
+ 'geographic block'];
}
if (status === 401) return ['credentials', '401, which is the key and not the location'];
if (status === 429) {
return ['rate-limited',
'429, so this host reaches the provider fine and the question is capacity '
+ 'rather than geography'];
}
return ['unexpected', `${status} with code '${code || '(none)'}'`];
}
/** The note. Pure. Returns [state, detail]. The only two-host function. */
export function compare(local, baseline) {
const [localState, localDetail] = classify(local);
const provider = (local ?? {}).provider ?? '(unknown)';
if (!baseline) {
if (localState === 'region-blocked') {
return ['region-blocked-unconfirmed',
`${provider}: 403 ${BLOCK_CODE} from this host. The code is documented, `
+ 'but with no baseline this has not been separated from an '
+ 'account-level restriction'];
}
if (localState === 'forbidden-other' || localState === 'credentials') {
return ['no-baseline',
`${provider}: ${localDetail}, and no baseline to compare it against. Run `
+ 'this from a host you trust first'];
}
return [localState === 'reachable' ? 'clear' : localState,
`${provider}: ${localDetail}`];
}
const [baseState] = classify(baseline);
if (localState === 'reachable') {
return ['clear', `${provider}: 200 from this host, so the egress path is fine`];
}
if (localState === 'region-blocked' && baseState === 'reachable') {
return ['geography-isolated',
`${provider}: 403 here and 200 from the baseline host on the same key, so `
+ 'the difference is the egress path and not the credential'];
}
if (localState === 'region-blocked' && baseState === 'region-blocked') {
return ['region-blocked-everywhere',
`${provider}: 403 ${BLOCK_CODE} from both hosts, so this is the account or `
+ "an organization-level restriction rather than this deployment's location"];
}
if (localState === 'credentials' && baseState === 'credentials') {
return ['credentials-not-geography',
`${provider}: 401 from both hosts on the same key, which is the credential `
+ 'and not the location'];
}
if (localState === 'credentials') {
return ['credentials-here-only',
`${provider}: 401 here and ${baseState} from the baseline host. A key that `
+ 'authenticates elsewhere and not here is usually a different key in the '
+ 'environment, not a geographic block'];
}
if (localState === 'forbidden-other' && baseState === 'reachable') {
return ['forbidden-unexplained',
`${provider}: ${localDetail} here and 200 from the baseline host. The host `
+ 'is the difference; the code is not one this script can attribute'];
}
return ['inconclusive',
`${provider}: ${localState} here, ${baseState} from the baseline host`];
}
/** The one line to carry to the other host. Pure. Sorted, and no secrets. */
export function blob(observations) {
const payload = {};
for (const obs of observations ?? []) {
const row = obs ?? {};
payload[String(row.provider)] = {
code: String(row.code ?? ''),
status: row.status ?? null,
};
}
const sorted = {};
for (const key of Object.keys(payload).sort()) {
const inner = payload[key];
sorted[key] = { code: inner.code, status: inner.status };
}
return JSON.stringify(sorted);
}
/** {provider: observation} from the blob. Pure. Empty object on anything odd. */
export function loadBaseline(raw) {
let parsed;
try {
parsed = JSON.parse(String(raw ?? '').trim() || '{}');
} catch {
return {};
}
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return {};
const out = {};
for (const [provider, row] of Object.entries(parsed)) {
if (!row || typeof row !== 'object') continue;
const raw_status = row.status;
const status = (raw_status === null || raw_status === undefined
|| !Number.isFinite(Number(raw_status)))
? null : Math.trunc(Number(raw_status));
out[String(provider)] = { provider: String(provider), status,
code: String(row.code ?? '') };
}
return out;
}
/** The repair for one verdict. Pure. A region pin, never a proxy. */
export function repairLines(state) {
const pin = "pin execution to a supported region. On Vercel, export const "
+ "config = { regions: ['iad1'] }. On Cloud Run, Lambda or a container, "
+ "redeploy in a supported region. On a VPN, turn it off.";
const noProxy = 'do not route the provider host through another egress to get '
+ 'around this. Move the workload, not the packets.';
if (state === 'geography-isolated') return [pin, noProxy];
if (state === 'region-blocked-unconfirmed') {
return ['run this same script from a host you already trust and paste its '
+ 'blob into LLM_EGRESS_BASELINE here. One 403 does not separate the '
+ 'location from the account.', pin];
}
if (state === 'region-blocked-everywhere') {
return ['both hosts are refused, so moving this deployment will not help. '
+ "Check the organization's country and any access restriction on the "
+ 'account before touching infrastructure.'];
}
if (state === 'credentials-not-geography') {
return ['not this note. The same key is refused from both hosts, which is a '
+ 'credential question: check that the key exists, is enabled, and belongs '
+ 'to the project you think it does.'];
}
if (state === 'credentials-here-only') {
return ['compare the environment on the two hosts. A key that works from one '
+ 'machine and 401s from another is almost always a different value in the '
+ 'environment rather than a location.'];
}
if (state === 'forbidden-unexplained') {
return ['record the error code exactly as printed and check the provider '
+ 'supported regions list for the country this host egresses from.', pin];
}
if (state === 'no-baseline') {
return ['run this from a host you trust and set LLM_EGRESS_BASELINE to the '
+ 'blob it prints. Without the pair there is one status code and no '
+ 'conclusion.'];
}
return [];
}
async function probe(provider, key) {
const spec = PROVIDERS[provider];
const url = new URL(spec.url);
url.searchParams.set('limit', '1');
const headers = provider === 'openai'
? { Authorization: `Bearer ${key}` }
: { 'x-api-key': key, 'anthropic-version': '2023-06-01' };
try {
const r = await fetch(url, { headers });
let body = null;
try { body = await r.json(); } catch { body = null; }
return [r.status, body];
} catch {
return [null, null];
}
}
async function main() {
const present = Object.keys(PROVIDERS).sort()
.filter((p) => process.env[PROVIDERS[p].env]);
if (!present.length) {
console.error('set OPENAI_API_KEY or ANTHROPIC_API_KEY. Both are used for '
+ 'one read-only GET of /v1/models and nothing else');
process.exitCode = 2;
return;
}
const baseline = loadBaseline(process.env.LLM_EGRESS_BASELINE);
const observations = [];
let findings = 0;
for (const provider of present) {
const [status, body] = await probe(provider, process.env[PROVIDERS[provider].env]);
const obs = observation(provider, status, body);
observations.push(obs);
const [state, detail] = classify(obs);
console.log(`${provider.padEnd(11)} ${obs.status ?? '---'} ${state.padEnd(14)} ${detail}`);
const [verdict, why] = compare(obs, baseline[provider]);
console.log(`${verdict.padEnd(20)} ${why}`);
for (const line of repairLines(verdict)) console.log(` repair: ${line}`);
if (FINDINGS.has(verdict)) findings += 1;
}
console.log(`baseline: ${blob(observations)}`);
if (!Object.keys(baseline).length) {
console.log('no baseline was supplied. Carry that line to the other host and '
+ 'run this again there');
}
console.log(`${findings} finding(s)`);
process.exitCode = findings ? 1 : 0;
}
if (import.meta.url === `file://${process.argv[1]}`) await main();
Add a test
The first test is the pair the note is built on: the documented 403 here beside a 200 from the baseline host has to produce geography-isolated, and the detail has to say why the credential is ruled out. The second and third are the outcomes that hand the reading away — 403 from both hosts is the account, 401 from both is the key — because a diagnostic that can only reach its own conclusion is not a diagnostic. Then the single-observation case, which must refuse to conclude even when the code is the documented one. Then the blob, round-tripped and asserted to contain no credential, which is the one property that makes it safe to paste between machines. And last, an undocumented 403 graded as unexplained rather than pattern-matched into a cause the script cannot cite.
from llm_egress_region_probe import (BLOCK_CODE, blob, classify, compare,
error_code, load_baseline, observation,
repair_lines)
def blocked(provider="openai"):
return observation(provider, 403,
{"error": {"message": "Country, region, or territory "
"not supported.",
"type": "invalid_request_error",
"code": BLOCK_CODE}})
def ok(provider="openai"):
return observation(provider, 200, {"data": [], "object": "list"})
def test_the_pair_is_what_turns_a_403_into_a_statement_about_geography():
state, detail = compare(blocked(), ok())
assert state == "geography-isolated"
assert "not the credential" in detail
lines = repair_lines(state)
assert any("regions: ['iad1']" in line for line in lines)
assert any("Move the workload, not the packets" in line for line in lines)
# A repair that routes around the block is never printed.
assert not any("proxy the" in line.lower() for line in lines)
def test_blocked_from_both_hosts_is_the_account_and_not_this_deployment():
state, detail = compare(blocked(), blocked())
assert state == "region-blocked-everywhere"
assert "organization-level restriction" in detail
assert any("moving this deployment will not help" in line
for line in repair_lines(state))
def test_a_401_from_both_hosts_is_handed_to_the_credential_question():
unauth = observation("openai", 401, {"error": {"code": "invalid_api_key"}})
state, detail = compare(unauth, unauth)
assert state == "credentials-not-geography"
assert "not the location" in detail
assert any("not this note" in line for line in repair_lines(state))
state, _ = compare(unauth, ok())
assert state == "credentials-here-only"
assert any("different value in the environment" in line
for line in repair_lines(state))
def test_one_observation_refuses_to_conclude_even_with_the_documented_code():
state, detail = compare(blocked(), None)
assert state == "region-blocked-unconfirmed"
assert "has not been separated from an account-level restriction" in detail
assert any("host you already trust" in line for line in repair_lines(state))
assert compare(ok(), None)[0] == "clear"
def test_the_blob_round_trips_and_carries_no_credential():
line = blob([blocked(), ok("anthropic")])
assert "sk-" not in line and "api" not in line.lower().replace("api.", "")
assert line == ('{"anthropic":{"code":"","status":200},'
'"openai":{"code":"unsupported_country_region_territory",'
'"status":403}}')
back = load_baseline(line)
assert classify(back["openai"])[0] == "region-blocked"
assert classify(back["anthropic"])[0] == "reachable"
# A mangled paste produces no baseline and an instruction, not a traceback.
assert load_baseline("{not json") == {}
assert load_baseline(None) == {}
def test_an_undocumented_403_is_recorded_rather_than_attributed():
other = observation("anthropic", 403,
{"error": {"type": "permission_error",
"message": "..."}})
state, detail = classify(other)
assert state == "forbidden-other"
assert "permission_error" in detail
verdict, why = compare(other, ok("anthropic"))
assert verdict == "forbidden-unexplained"
assert "not one this script can attribute" in why
assert any("supported regions list" in line for line in repair_lines(verdict))
def test_bodies_are_read_in_either_envelope_and_odd_ones_do_not_raise():
assert error_code({"error": {"code": "a", "type": "b"}}) == "a"
assert error_code({"error": {"type": "b"}}) == "b"
assert error_code({"error": "a string"}) == ""
assert error_code(None) == ""
assert observation("openai", None, None)["status"] is None
assert classify(observation("openai", None, None))[0] == "unreachable"
assert classify(observation("openai", 429, None))[0] == "rate-limited"
assert repair_lines("clear") == []
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { BLOCK_CODE, blob, classify, compare, errorCode, loadBaseline,
observation, repairLines } from './llm-egress-region-probe.mjs';
const blocked = (provider = 'openai') => observation(provider, 403, {
error: { message: 'Country, region, or territory not supported.',
type: 'invalid_request_error', code: BLOCK_CODE },
});
const ok = (provider = 'openai') =>
observation(provider, 200, { data: [], object: 'list' });
test('the pair is what turns a 403 into a statement about geography', () => {
const [state, detail] = compare(blocked(), ok());
assert.equal(state, 'geography-isolated');
assert.ok(detail.includes('not the credential'));
const lines = repairLines(state);
assert.ok(lines.some((l) => l.includes("regions: ['iad1']")));
assert.ok(lines.some((l) => l.includes('Move the workload, not the packets')));
assert.ok(!lines.some((l) => l.toLowerCase().includes('proxy the')));
});
test('blocked from both hosts is the account and not this deployment', () => {
const [state, detail] = compare(blocked(), blocked());
assert.equal(state, 'region-blocked-everywhere');
assert.ok(detail.includes('organization-level restriction'));
assert.ok(repairLines(state).some((l) => l.includes('moving this deployment will not help')));
});
test('a 401 from both hosts is handed to the credential question', () => {
const unauth = observation('openai', 401, { error: { code: 'invalid_api_key' } });
const [state, detail] = compare(unauth, unauth);
assert.equal(state, 'credentials-not-geography');
assert.ok(detail.includes('not the location'));
assert.ok(repairLines(state).some((l) => l.includes('not this note')));
assert.equal(compare(unauth, ok())[0], 'credentials-here-only');
assert.ok(repairLines('credentials-here-only')
.some((l) => l.includes('different value in the environment')));
});
test('one observation refuses to conclude even with the documented code', () => {
const [state, detail] = compare(blocked(), null);
assert.equal(state, 'region-blocked-unconfirmed');
assert.ok(detail.includes('has not been separated from an account-level restriction'));
assert.ok(repairLines(state).some((l) => l.includes('host you already trust')));
assert.equal(compare(ok(), null)[0], 'clear');
});
test('the blob round trips and carries no credential', () => {
const line = blob([blocked(), ok('anthropic')]);
assert.ok(!line.includes('sk-'));
assert.equal(line, '{"anthropic":{"code":"","status":200},'
+ '"openai":{"code":"unsupported_country_region_territory","status":403}}');
const back = loadBaseline(line);
assert.equal(classify(back.openai)[0], 'region-blocked');
assert.equal(classify(back.anthropic)[0], 'reachable');
assert.deepEqual(loadBaseline('{not json'), {});
assert.deepEqual(loadBaseline(null), {});
});
test('an undocumented 403 is recorded rather than attributed', () => {
const other = observation('anthropic', 403,
{ error: { type: 'permission_error', message: '...' } });
const [state, detail] = classify(other);
assert.equal(state, 'forbidden-other');
assert.ok(detail.includes('permission_error'));
const [verdict, why] = compare(other, ok('anthropic'));
assert.equal(verdict, 'forbidden-unexplained');
assert.ok(why.includes('not one this script can attribute'));
assert.ok(repairLines(verdict).some((l) => l.includes('supported regions list')));
});
test('bodies are read in either envelope and odd ones do not raise', () => {
assert.equal(errorCode({ error: { code: 'a', type: 'b' } }), 'a');
assert.equal(errorCode({ error: { type: 'b' } }), 'b');
assert.equal(errorCode({ error: 'a string' }), '');
assert.equal(errorCode(null), '');
assert.equal(observation('openai', null, null).status, null);
assert.equal(classify(observation('openai', null, null))[0], 'unreachable');
assert.equal(classify(observation('openai', 429, null))[0], 'rate-limited');
assert.deepEqual(repairLines('clear'), []);
});
FAQ
Why can't the script just check what country the server is in?
It could, by calling a third-party geolocation service, and it deliberately does not. That would add a network dependency, a privacy question and a second thing that can be wrong to a script whose entire purpose is to remove ambiguity. It would also answer the wrong question: what matters is not which country an IP database thinks the host is in, it is whether the provider will serve a request from it. The provider's own 403 is that answer, and it is authoritative in a way no lookup can be.
Is a 403 not just a permissions problem with the key?
That is exactly the wrong turn this note exists to prevent, and it is why the script insists on two runs. A 403 read alone sends people to rotate the key, which changes nothing, and then to widen its scopes, which changes nothing and leaves a more powerful credential in production. The same key returning 200 from another host makes the credential impossible as an explanation. When the pair says the opposite, the script says so too: 401 from both hosts is named credentials-not-geography and pointed elsewhere.
Which regions are actually supported?
It differs by provider and it changes, so the script does not carry a list. OpenAI documents the refusal as a 403 whose code is unsupported_country_region_territory and publishes its supported countries separately. Anthropic publishes a list of the countries, regions and territories it can support access from, with some entries carved out at sub-national level. Both are pages to check rather than constants to hardcode, and a script that shipped a copy of either would be wrong within a release or two.
The block only happens sometimes. Is that possible?
Yes, and it is the most confusing version. Edge platforms run your function at whichever point of presence is nearest the user, so a single deployment can egress from several countries and the failure will correlate with your users' locations rather than with anything in your code. Run the probe from inside the same runtime rather than from a build step or a shell on the same account, and run it more than once. Intermittent is still a region pin; it just means more than one region is in play.
Why not confirm it from the usage report as well?
Because a project with no requests in it is a genuinely ambiguous signal and there is already a note that grades it properly. Zero usage looks like a hard block, and it also looks like a dead credential, a deploy that never rolled out, or a feature nobody used. Folding that into this script would import an ambiguity it exists to remove, so it stays with the probe pair, where a 403 here and a 200 there means exactly one thing.
Related field notes
- When the model resolves and one route still cannot use it
- The other header on the same request, and the matrix that proves it
- Geography as a line on the bill rather than a wall
Sources
Every figure in this note is traced to one of these. Prices are list rates and change — check them for your own region before acting.
- Error codes — OpenAI platform docs
- Supported regions — Claude API reference
- Models — OpenAI API reference
- List Models — Claude API reference
If your setup is misbehaving in a way this note does not cover, message me on LinkedIn with what you are seeing.