Call an authenticated API from a sandbox
Last updated Oct 22, 2026 Let code in a sandbox call an API that needs a token, while the token stays in your Worker. Requests from the sandbox reach an entrypoint in your Worker, which allows only the requests you choose and adds the token. In this example, the code reads your profile from the GitHub API.
Prerequisites
- A Worker with a Durable Object that starts a container with the Durable Object scheduling policy, or a Worker that loads Dynamic Workers with a Worker Loader binding named
LOADER. To create one, refer to Run a Linux command or Run JavaScript. - A GitHub access token that can read your profile.
Store the token
- In
wrangler.jsonc, declare the token as a secret, then generate types:
jsonc
{
"secrets": {
"required": ["GITHUB_TOKEN"],
},
}
toml
[secrets]
required = [ "GITHUB_TOKEN" ]
npmyarnpnpm
npx wrangler types
yarn wrangler types
pnpm wrangler types
- Store the token when Wrangler prompts for it:npmyarnpnpm
npx wrangler secret put GITHUB_TOKEN
yarn wrangler secret put GITHUB_TOKEN
pnpm wrangler secret put GITHUB_TOKEN
Call the API from a Container
The container sends its request to api.github.com as usual. The Durable Object intercepts HTTPS requests to that hostname and delivers them to an entrypoint in your Worker, which adds the token.
- Add an entrypoint to your Worker that accepts one GitHub API request and adds the token:
src/index.jsjs
```js import { WorkerEntrypoint } from "cloudflare:workers";
export class GitHubGateway extends WorkerEntrypoint { async fetch(request) { const url = new URL(request.url);
if (
request.method !== "GET" ||
url.hostname !== "api.github.com" ||
url.pathname !== "/user" ||
url.search !== ""
) {
return new Response("Forbidden", { status: 403 });
}
return fetch("https://api.github.com/user", {
headers: {
Accept: "application/vnd.github+json",
Authorization: `Bearer ${this.env.GITHUB_TOKEN}`,
"User-Agent": "cloudflare-sandbox",
"X-GitHub-Api-Version": "2022-11-28",
},
});
}
} ```
src/index.tsts
```ts import { WorkerEntrypoint } from "cloudflare:workers";
export class GitHubGateway extends WorkerEntrypoint
if (
request.method !== "GET" ||
url.hostname !== "api.github.com" ||
url.pathname !== "/user" ||
url.search !== ""
) {
return new Response("Forbidden", { status: 403 });
}
return fetch("https://api.github.com/user", {
headers: {
Accept: "application/vnd.github+json",
Authorization: `Bearer ${this.env.GITHUB_TOKEN}`,
"User-Agent": "cloudflare-sandbox",
"X-GitHub-Api-Version": "2022-11-28",
},
});
}
} ```
The entrypoint allows only GET /user, so code in the sandbox cannot use the token for anything else. Allow each request your code needs, and nothing more.
2. Add a method to your Durable Object that intercepts api.github.com and runs code that calls it:
src/index.tsts
```ts
export class MyContainer extends DurableObject
async getUsername(): Promise<string> {
const container = this.ctx.container;
if (!container) {
throw new Error("No container is configured");
}
await container.interceptOutboundHttps(
"api.github.com",
this.ctx.exports.GitHubGateway,
);
if (!container.running) {
container.start({
image: "cloudflare/debian-trixie",
entrypoint: ["sleep", "infinity"],
// The container can reach only the hostnames you intercept
enableInternet: false,
});
}
const process = await container.exec(
[
"node",
"--input-type=module",
"--eval",
`const response = await fetch("https://api.github.com/user");
if (!response.ok) throw new Error("GitHub request failed"); console.log((await response.json()).login);`, ], { env: { // The intercept terminates TLS with a certificate that the container // CA certificate signs. Node.js reads the CA certificate from this path NODE_EXTRA_CA_CERTS: "/etc/cloudflare/certs/cloudflare-containers-ca.crt", }, }, ); const output = await process.output();
if (output.exitCode !== 0) {
throw new Error(new TextDecoder().decode(output.stderr));
}
return new TextDecoder().decode(output.stdout).trim();
}
} ```
- Add a route to your Worker that returns the username:
src/index.tsts
ts
if (url.pathname === "/username") {
const sandbox = env.MY_CONTAINER.getByName("github");
return Response.json({ username: await sandbox.getUsername() });
}
The route uses a sandbox name of its own. An instance that is already running keeps the Internet access it started with, so enableInternet: false applies only to an instance that this method starts.
4. Deploy your Worker, then send a request to /username on the workers.dev URL that Wrangler prints:npmyarnpnpm
npx wrangler deploy
yarn wrangler deploy
pnpm wrangler deploy
sh
curl https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev/username
json
{ "username": "octocat" }
Call the API from a Dynamic Worker
A Dynamic Worker gets a method that calls one fixed GitHub endpoint, instead of the token.
- Add an entrypoint to your Worker with a method that calls GitHub with the token:
src/index.jsjs
```js import { WorkerEntrypoint } from "cloudflare:workers";
export class GitHub extends WorkerEntrypoint {
async getUsername() {
const response = await fetch("https://api.github.com/user", {
headers: {
Accept: "application/vnd.github+json",
Authorization: Bearer ${this.env.GITHUB_TOKEN},
"User-Agent": "cloudflare-sandbox",
"X-GitHub-Api-Version": "2022-11-28",
},
});
if (!response.ok) {
throw new Error(`GitHub returned ${response.status}`);
}
const user = await response.json();
return user.login;
}
} ```
src/index.tsts
```ts import { WorkerEntrypoint } from "cloudflare:workers";
export class GitHub extends WorkerEntrypointBearer ${this.env.GITHUB_TOKEN},
"User-Agent": "cloudflare-sandbox",
"X-GitHub-Api-Version": "2022-11-28",
},
});
if (!response.ok) {
throw new Error(`GitHub returned ${response.status}`);
}
const user = await response.json<{ login: string }>();
return user.login;
}
} ```
- In the
fetch()handler of your Worker, pass the entrypoint to the Dynamic Worker as a binding. The handler needs itsctxargument forctx.exports:
src/index.tsts
``ts
const sandbox = env.LOADER.load({
compatibilityDate: "2026-10-01",
mainModule: "code.js",
modules: {
"code.js":
import { WorkerEntrypoint } from "cloudflare:workers";
export class Code extends WorkerEntrypoint {
async run() {
return this.env.GITHUB.getUsername();
}
}
`,
},
env: {
// Create a stub that the Dynamic Worker can receive
GITHUB: ctx.exports.GitHub({ props: {} }),
},
// Block every other `fetch()` and `connect()` call from the Dynamic Worker
globalOutbound: null,
});
const username = await sandbox
.getEntrypoint<WorkerEntrypoint & { run(): Promise
The code calls this.env.GITHUB.getUsername(), and the method reads the token in your Worker.
3. Deploy your Worker, then send a request to the workers.dev URL that Wrangler prints:npmyarnpnpm
npx wrangler deploy
yarn wrangler deploy
pnpm wrangler deploy
sh
curl https://<YOUR_WORKER>.<YOUR_SUBDOMAIN>.workers.dev
json
{ "username": "octocat" }
Authenticate callers to your Worker first, so other people cannot use the quota of your token. For more information, refer to Sandbox security.
Related resources
- Clone a private repository: the same pattern for Git.
interceptOutboundHttps: intercept other Container destinations.- Bindings: pass other methods to a Dynamic Worker.
- Egress control: restrict or audit requests from a Dynamic Worker.
- Secrets: rotate or manage the token.
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/sandbox/network/call-an-authenticated-api/#page","headline":"Call an authenticated API from a sandbox","description":"Let code in a Container or Dynamic Worker call an authenticated API without giving it the access token.","url":"https://developers.cloudflare.com/sandbox/network/call-an-authenticated-api/","inLanguage":"en","image":"https://developers.cloudflare.com/sandbox/network/call-an-authenticated-api/og.png?v=2fc5c18d991062dc","dateModified":"2026-10-22","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Clone a private GitHub repository into a Linux sandbox without giving the sandbox the access token. title: Clone a private repository image: https://developers.cloudflare.com/sandbox/network/clone-a-private-repository/og.png?v=7057e6542fb559ab
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/sandbox/llms.txt
Use this file to discover all available pages before exploring further.
Docs