Share a local API
Share a local API with curl, Postman and browser fetch calls from another origin, using a Horizon tunnel and the x-hrzn-skip-warning header.
Give a teammate, a client or a frontend on another origin a public HTTPS URL for the API on your laptop.
Before you begin
- Node.js 18 or later
- A Horizon account and the CLI (see Getting started)
- A reserved subdomain for
-s. Reserve one on the Subdomains page. - An API running on your machine, for example on
http://localhost:3000
Start your API
This example is a plain Node.js server. It sends its own CORS headers, because Horizon doesn't add them.
import { createServer } from "node:http";
const corsHeaders = {
"Access-Control-Allow-Origin": "https://my-frontend.example",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, x-hrzn-skip-warning",
};
createServer((request, response) => {
if (request.method === "OPTIONS") {
response.writeHead(204, corsHeaders);
response.end();
return;
}
response.writeHead(200, { ...corsHeaders, "Content-Type": "application/json" });
response.end(JSON.stringify({ message: "hello from my laptop" }));
}).listen(3000);Replace https://my-frontend.example with the origin of the page that calls your API. Run it.
node server.mjsStart a tunnel
Use -s with a subdomain you reserved. Without it the subdomain is random and changes every run. Reserved subdomains are a paid feature, see Pricing.
hrzn tunnel http://localhost:3000 -s my-appHORIZON: Tunnel connected
URL https://my-app.hrzn.run (reserved)
Forwarding http://localhost:3000
Request log https://hrzn.run/dashboard/tunnels/my-appCall it from curl or Postman
curl sends a non-browser user agent, so it skips the Before you continue page.
curl https://my-app.hrzn.run/{"message":"hello from my laptop"}Postman also sends a non-browser user agent. Create a request, set the URL to https://my-app.hrzn.run/, and select Send. If you get an HTML page, add a header named x-hrzn-skip-warning with any value.
Call it from a browser on another origin
A browser sends a browser user agent, so add the x-hrzn-skip-warning header to every fetch. Any value works.
const response = await fetch("https://my-app.hrzn.run/", {
headers: { "x-hrzn-skip-warning": "1" },
});
console.log(await response.json());A custom header makes the browser send a CORS preflight first. That is an OPTIONS request. Horizon doesn't show the warning page for OPTIONS, so the preflight reaches your API.
Your API still answers the preflight. It must send its own CORS headers, including x-hrzn-skip-warning in Access-Control-Allow-Headers. The server in step 1 does this.
Check it works
Run the curl command from step 3. Your Horizon terminal prints:
GET 200 /From a browser on another origin, the terminal prints the preflight first, then the call:
OPTIONS 204 /
GET 200 /Troubleshooting
My app gets an HTML page instead of JSON
The request got the Before you continue page. Add the x-hrzn-skip-warning header, with any value, to the request.
The browser blocks the request with a CORS error
Horizon doesn't add CORS headers. Your API must send Access-Control-Allow-Origin and answer OPTIONS. If you send x-hrzn-skip-warning from a browser, list it in Access-Control-Allow-Headers.
The preflight returns an error
Your API must answer OPTIONS with a success status, such as 204, and the three Access-Control-Allow-* headers. Check that no route or middleware rejects OPTIONS.
The URL changed after a restart
You started the tunnel without -s. Restart with -s my-app. -s needs a subdomain you reserved, see Pricing.
Nothing reaches your API
- Check that the Horizon terminal is still running. If its last line is
Connection lost. Reconnecting…, wait forReconnected. - Check that your API runs on the port in the
hrzn tunnelcommand.
Next steps
- Test a mobile app against your local API
- Read MDN's guide to CORS.
Share your local dev site
Share your local dev site with a teammate or client. Send them a public HTTPS link to localhost with a Horizon tunnel.
Test a mobile app against your local API
Point an iOS, Android, React Native or Expo app at your local API through a Horizon HTTPS tunnel, without device network setup.