THE REGIONAL API RELAY
Your request.
From somewhere else.
Put RegionHop in front of an API URL to send the same request from another location. Keep the response intact, or capture a report with location and timing details.
Diagnostics in headers.Probe ↗JSON arguments in.
A detailed JSON report out.
One key to get started
Sign in with your invited email address and create a personal API key. Set it in your terminal before running these examples. The documentation is public; API requests require authentication.
export REGIONHOP_KEY='YOUR_REGIONHOP_KEY'Use X-RegionHop-Key for the proxy and Authorization: Bearer for probe and catalog endpoints. Keep your target API token separate. Personal keys allow proxy, probe, and catalog reads. Catalog changes require the administrator key.
Each account allows 120 authenticated API requests per minute and 10,000 per UTC day across all its keys. A limit returns HTTP 429 with Retry-After. Manage expiry and revoke keys from your account page. You can have up to 10 active keys.
01 / PROXY
Add a prefix. Keep your request.
Prepend https://regionhop.com/{destination}/ to the complete target URL. Send its usual method, headers, and body. Target query parameters stay in the target URL; they are not RegionHop options.
/{destination}/https://api.example.com/path?query=valuecurl --include 'https://regionhop.com/SJC/https://httpbingo.org/anything' \
-H "X-RegionHop-Key: $REGIONHOP_KEY" \
-H 'Content-Type: application/json' \
--data-binary '{"hello":"world"}'This example really sends a POST to an echo API. Substitute your own target URL and body. For a file, use --data-binary @payload.json. Add -H 'Authorization: Bearer YOUR_TARGET_TOKEN' when your target requires it.
| Destination | Value in the URL | Behavior |
|---|---|---|
| Observed colo | SJC, CDG, or another cataloged uppercase code | Select a catalog node and verify its observed colo before sending. |
| Region hint | wnam, weur, or another supported hint | Use a reusable instance with best-effort regional placement. |
| Specific node | node-{id} | Use an active 64-character lowercase hexadecimal ID from GET /nodes. Verify its last known colo before sending. |
Methods: GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS. For GET, omit the data argument. For HEAD, use curl --head.
The response stays the target response
The target HTTP status, application headers, and body pass through without a JSON wrapper. Binary responses and streams are supported. RegionHop adds metadata headers:
HTTP/1.1 200 OK
Content-Type: application/json
X-RegionHop-Colo: SJC
X-RegionHop-Requested-Colo: SJC
X-RegionHop-Headers-Ms: 42
X-RegionHop-Dispatch-Ms: 190
X-RegionHop-Upstream-Status: 200
{"hello":"world"}Authentication in the URL
Clients that cannot add headers can use /{destination}/{key}/https://target.example/path. URL-encode the key as one path segment. The URL key takes precedence over X-RegionHop-Key. A URL containing a key is a credential and can appear in logs and browser history. Prefer the header form when possible.
Transparent at the API level: connection-specific headers, forwarding headers, and Referer are removed; Host and Content-Length are runtime-managed. The X-RegionHop-* namespace is reserved. HTTP framing and compression can be managed by the runtime.
02 / PROBE
Get a report around the response.
/probeSend JSON with Content-Type: application/json. Use exactly one destination selector: region, colo, or node.
curl 'https://regionhop.com/probe' \
-H "Authorization: Bearer $REGIONHOP_KEY" \
-H 'Content-Type: application/json' \
--data '{
"url": "https://httpbingo.org/anything",
"colo": "SJC",
"method": "POST",
"headers": {"User-Agent": "RegionHop-Docs/1.0"},
"payload": {"hello":"world"}
}'| Parameter | Values and default |
|---|---|
url required | Absolute HTTP(S) URL with a public DNS hostname. No URL credentials, local hostnames, or IP literals. |
region | One of the 11 lowercase region hints. Exactly one selector required. |
colo | Three uppercase letters from the catalog, for example SJC. Exactly one selector required. |
node | Active 64-character lowercase hexadecimal ID from GET /nodes. Exactly one selector required. |
method | GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS. Default: POST. |
headers | Object whose values are strings. Only these target headers are forwarded. Default: none. Include the target Authorization here if needed. |
payload | Optional JSON value. Strings are sent verbatim; other values are JSON-encoded. Omit for GET and HEAD. Content-Type defaults to text/plain for strings, application/json otherwise, unless supplied in headers. |
timeoutMs | Integer 100 to 30000. Default: 15000. Covers target fetch and reading the response, excluding the preceding location check. |
maxResponseBytes | Integer 1 to 5242880. Default: 1048576 (1 MiB). Larger responses are captured up to this limit. |
The example supplies a User-Agent for the echo service. Probe mode does not copy the caller's headers to the target.
Managed headers are rejected in probe arguments: Host, Content-Length, connection and proxy headers, Forwarded, Via, X-Real-IP, True-Client-IP, CF-*, X-Forwarded-*, X-RegionHop-*, and the internal hop marker.
{
"objectId": "<catalog node ID>",
"startedAt": "2026-09-18T10:00:00.000Z",
"requestedColo": "SJC",
"location": {
"colo": "SJC",
"source": "cloudflare-trace",
"observedAt": "2026-09-18T09:59:59.990Z",
"error": null
},
"timings": {
"headersMs": 42,
"durationMs": 48,
"dispatchMs": 190
},
"response": {
"status": 200,
"statusText": "OK",
"ok": true,
"headers": {
"content-type": "application/json"
},
"body": {
"hello": "world"
},
"bodyEncoding": "json",
"bytes": 17,
"truncated": false
},
"error": null,
"ingressColo": "FRA"
}Reading a report
location.colois a fresh outbound trace observation.ingressColois where your client entered Cloudflare.objectIdidentifies the instance.requestedColoappears for strict colo/node selections.regionappears when the instance has a regional hint, which may differ from its observed location.headersMsmeasures the target fetch until headers arrive.durationMsincludes reading the captured body.dispatchMsincludes node selection, startup, the location check, target fetch, encoding, and dispatch overhead. None includes the client's trip to RegionHop.response.bodyEncodingisjson, UTF-8text, orbase64for binary/unknown content types. Truncated JSON is returned as text.bytesis the captured byte count;truncatedsays whether capture hit the size limit.- Captured headers are diagnostic metadata. The runtime can decompress the body, so do not use those headers to decode the report again.
A completed probe returns HTTP 200 even when the target returns 4xx/5xx or the report contains a target timeout. Check both error and response.status. Authentication, validation, missing destinations, and dispatch failures use HTTP error statuses instead.
03 / LOCATIONS
Choose where the request starts.
Use an uppercase colo for a verified catalog selection. RegionHop checks the node before sending and does not silently fall back to another colo or retry the target request.
Observed colos
Loading the latest catalog health. You can also view the status page.
Location chips copy a proxy prefix. Checks run hourly and become stale after 90 minutes. Health describes our nodes reaching the trace endpoint, not the availability of your API.
Region hints
These are placement preferences, not exact city selectors. Hints can resolve to the same colo or a nearby supported region. For example, use /weur/https://your-api.example/path or "region": "weur".
| Hint | Requested region |
|---|---|
wnam | Western North America |
enam | Eastern North America |
weur | Western Europe |
eeur | Eastern Europe |
apac | Asia-Pacific |
apac-ne | Northeast Asia-Pacific |
apac-se | Southeast Asia-Pacific |
oc | Oceania |
sam | South America (may use a nearby supported region) |
afr | Africa (may use a nearby supported region) |
me | Middle East (may use a nearby supported region) |
The reported colo comes from a separate request to Cloudflare's trace endpoint. It is not an authoritative physical-location reading or proof of the target's exact network egress. To verify what your own API sees, return its observed source IP or request metadata.
04 / METADATA
Inspect the route and timing.
Proxy metadata is added to response headers when available. Target 4xx/5xx responses remain target responses and do not gain a RegionHop error code.
| Response header | Meaning |
|---|---|
X-RegionHop-Colo | Fresh trace-observed colo. Omitted if unavailable on a best-effort hint route. |
X-RegionHop-Requested-Colo | Expected colo for a strict colo or node selection. |
X-RegionHop-Node | Selected Durable Object instance ID. |
X-RegionHop-Region | The instance’s original regional hint. Omitted for unhinted instances. |
X-RegionHop-Observed-At | ISO timestamp of the successful location observation. |
X-RegionHop-Ingress-Colo | Where the client entered Cloudflare. |
X-RegionHop-Headers-Ms | Target fetch to response headers in milliseconds, excluding the preceding trace check. |
X-RegionHop-Dispatch-Ms | Node selection and Worker-to-node dispatch through response headers, including startup and location verification. Excludes client network time and body download. |
X-RegionHop-Upstream-Status | Target HTTP status, also used as the actual proxy response status. |
X-RegionHop-Error | RegionHop failure code. Absent for ordinary target responses. |
What the target receives
Proxy mode adds X-RegionHop-Node, X-RegionHop-Colo when observed, and X-RegionHop-Region when the instance has a hint. Both modes add the internal X-Regional-Probe-Hop: 1 marker to prevent recursive relaying. RegionHop strips its proxy credentials; target Authorization is forwarded. Probe mode forwards only the target headers supplied in the JSON envelope, plus the hop marker.
Use probe mode for captured-body duration. A streaming proxy cannot report total download time in headers that have already been sent.
05 / BEHAVIOR
Know what happened.
Proxy failures return plain text with X-RegionHop-Error. Probe failures before dispatch return JSON shaped as {"error":"message"}. Once a probe report is available, failures use error: {code, message} with response: null.
| Condition / code | Proxy HTTP status | Probe behavior |
|---|---|---|
UNAUTHORIZED: missing or incorrect key | 401 | 401, JSON error string. |
INVALID_REQUEST: malformed arguments or managed headers | 400; 413 for oversized input; 403 for a configured hostname restriction | Same statuses, JSON error string. Non-JSON /probe input returns 415. |
DESTINATION_UNAVAILABLE: no active matching node | 503 | 503, JSON error string. Target not sent. |
LOCATION_UNAVAILABLE: strict location cannot be checked | 503 | Report error when pre-send trace fails; HTTP 503 if the selected node has no known colo. Target not sent. |
COLO_MISMATCH: strict trace differs from expected colo | 409 | HTTP 200 report with this error. Target not sent. |
UPSTREAM_TIMEOUT: target exceeds deadline | 504 | HTTP 200 report with this error. The target may have processed the request. |
UPSTREAM_FETCH_FAILED: target connection fails | 502 | HTTP 200 report with this error; also covers response-body failures. |
DISPATCH_FAILED: regional service dispatch fails | 502 | 502, JSON error string. The target may already have received the request. |
RECURSIVE_REQUEST: internal hop marker received | 400 | 400, JSON error string. |
NOT_CONFIGURED: service key missing | 503 | 503, JSON error string. |
For a region hint, an unavailable trace does not block the target request. Inspect the missing colo or location.error. For strict colo/node selections, failed verification blocks the target request.
| Limit | Proxy | Probe |
|---|---|---|
| Request size | 1 MiB body | 1 MiB JSON envelope |
| Target timeout | 15 seconds until response headers | 100 to 30,000 ms including captured body; default 15,000 ms |
| Response size | Streamed without a RegionHop capture cap | 1 MiB captured by default; configurable up to 5 MiB |
| Redirects / retries | Neither followed nor retried by RegionHop | Neither followed nor retried by RegionHop |
After proxy headers are sent, a body failure terminates the stream; the status cannot be changed. Runtime/platform limits still apply. Redirect Location is passed through unchanged. If your client follows an absolute redirect, it may leave RegionHop.
Cookies, URL origins, and CORS headers are not rewritten. Browser CORS and CSP rules still apply. This is an HTTP API relay; CONNECT, WebSocket, and whole-browser tunneling are not supported. Target payloads and responses are not persisted by RegionHop.
Track your allowance.
Open the playground to enter a target URL, method, headers and body, choose a location, and inspect a captured response. It uses your signed-in session and the same account allowance as your keys. Redirects are not followed. Responses are displayed as text, with a 1 MiB capture limit and a 15 second timeout.
Your account shows current minute and UTC day usage. GET /usage with Authorization: Bearer YOUR_KEY returns asOf and minute/day objects with used, remaining, limit, and ISO resetAt. Reading usage does not consume quota.
| Response header | Meaning |
|---|---|
X-RegionHop-Limit-Minute / X-RegionHop-Limit-Day | Account limits: 120 per minute and 10,000 per UTC day. |
X-RegionHop-Remaining-Minute / X-RegionHop-Remaining-Day | Requests remaining after this request. Shared across keys and the playground. |
X-RegionHop-Reset-Minute / X-RegionHop-Reset-Day | Next fixed-window boundary as Unix seconds. |
These headers accompany admitted personal-key API requests, including errors, and quota rejections. RegionHop quota rejections return 429 with Retry-After in seconds and consume neither allowance. Authenticated catalog reads, validation failures, and target failures count. Account management and usage reads are free. Administrator credentials are unmetered and do not receive account quota headers. Target rate-limit headers remain separate.
See your request stats.
Your account page shows request volume, target response classes, RegionHop failures, request modes, and average target-header latency by observed colo. Choose 24 hours or 7 days. New history starts when tracking is enabled; earlier requests cannot be reconstructed.
GET /stats?hours=24 or GET /stats?hours=168 uses your personal bearer key and does not consume quota. The default is 24. It returns asOf, from, trackingSince, a summary, hourly timeline, colos, and modes. Totals contain requests, target2xx, target3xx, target4xx, target5xx, regionhopErrors, timedRequests, and averageHeadersMs. Missing timings are null, not zero.
Stats count admitted proxy requests and POST probe/playground attempts, including validation failures. Target 4xx/5xx responses are separate from RegionHop failures. Quota rejections, catalog reads, account management, unauthenticated requests and administrator credentials are excluded, so this total can differ from quota usage. Proxy stats describe the response through receipt of target headers; later streaming interruptions are not recorded.
Latency is measured from the regional node to target response headers. It excludes authentication, location verification, and body download. Averages mix the targets you called and are not a network speed ranking. The colo is a trace observation, not a guarantee of target-specific egress. Counts use 24 or 168 UTC hour buckets, including the current partial hour. Writes are asynchronous and best effort; refresh to see recent requests.
RegionHop retains seven days of hourly aggregates in the existing database, with hourly cleanup and deletion when the account is deleted. No target URLs, headers, bodies or individual request history are stored. There is no separate analytics service.
06 / DISCOVERY
Read available locations.
| Endpoint | Authentication | Response |
|---|---|---|
GET /regions | Bearer key | {regions, placement}: all supported hint names and a placement note. |
GET /nodes | Bearer key | {nodes, colos, placement}: active node records and selectable observed colo codes. Reads the catalog without running new checks. |
GET /status.json | Public | Aggregate health, timestamps, node counts, per-colo states, and up to 24 hours of sampled history. No node IDs or credentials. |
GET /status | Public | Human-readable network status. |
GET /health | Public | {"ok":true}. Worker liveness only, not catalog health. |
curl 'https://regionhop.com/nodes' \
-H "Authorization: Bearer $REGIONHOP_KEY"Node records include id, hint, registeredAt, checkedAt, location, lastKnownColo, and status; discovery-created nodes also record creationIngressColo. Inventory states are unchecked, observed, stale, or unavailable.
Public health states are healthy (all assigned nodes pass), degraded (some pass), unavailable (none pass), unknown (no completed observation), and stale (last sweep over 90 minutes old). Read checkedAt alongside health; generatedAt is the time the status response was generated. History records checks, not continuous uptime.
07 / OPERATIONS
Maintain the catalog.
Normal API use needs none of these calls. These endpoints share the Bearer key and can perform checks or allocate nodes.
Refresh, discovery, compaction, and tickets
| Endpoint | Arguments and behavior |
|---|---|
POST /nodes/refresh | No body required. Inspect existing nodes without allocating. Returns the catalog plus checked. Does not record a scheduled-health history entry. |
POST /nodes/check-health | No body required. Run and record a health sweep. Returns {checked, nodes, checkedAt} or {checked:false, reason} if a check is already running. |
POST /nodes/discover | Optional JSON object. strategy: hinted (default) or local. Hinted: regions array of 1 to 11 supported hints, default all; countPerRegion integer 1 to 3, default 1. Local: count integer 1 to 3, default 1; omit regions/countPerRegion. Returns catalog plus checked count. Request max 16 KiB. Active inventory max 128; capacity returns HTTP 409 with a JSON error string. |
POST /nodes/compact | No body required. Retire surplus nodes, keeping two per known colo and preserving original hint routes. Returns {retired, nodes, colos, missingRedundancy}. Retired IDs are no longer selectable. |
POST /nodes/tickets | Optional JSON: uses integer 1 to 40 (default 1); ttlSeconds integer 30 to 900 (default 300). Returns HTTP 201 with {token, url, uses, expiresAt}. Max 32 outstanding tickets; request max 4 KiB. |
GET /nodes/seed/{token} | Ticket authentication only. Consumes one use to create and inspect one unhinted node. Returns HTTP 201 with {node}. Invalid/expired/exhausted/revoked tickets return 410; capacity returns 409. |
DELETE /nodes/tickets/{token} | Bearer key. Revoke a ticket, returning HTTP 204. |
Discovery creates instances, not guaranteed new locations. Local discovery uses the receiving Worker for creation; placement still varies. Tickets grant only bounded creation and trace inspection, not arbitrary proxy access. Compact after discovery if you want to restore two active nodes per known colo.