fetch handler in JavaScript, executed close to the consumer. The gateway authenticates and meters consumers, then calls your Worker; your code does whatever the API does — forward to your servers, compute an answer, read a database, call several upstreams. There is no SDK to import; the same code runs unchanged in the standard Workers tooling (wrangler dev).
The Worker tab is available for APIs served by the edge gateway. Each API is either in template mode, where the Worker is generated from the template, or in code mode, where you own the files. APIs moved from the classic gateway with endpoint-specific targets start in code mode with generated code that reproduces their classic behaviour.
Template or code
- Template (default): the Worker is generated from the template. Saving creates a preview on its own URL; Deploy to production in the bar on the Worker tab serves it to your consumers. View generated code shows exactly what runs.
- Code: Edit code stores the generated files as yours. From then on nothing is regenerated. Deploy preview puts your files on their own URL; Deploy to production (in the editor, the bar or Promote on the Deployments tab) serves them to your consumers. Back to template deletes them and generates the Worker again, as a preview too.
- From the CLI or GitHub: deploying files with
jojapi deploy(or from GitHub) to an API in template mode switches it to code mode. The files become a preview deployment; production keeps serving the template’s deployment until you promote them (jojapi deploy --proddoes both at once). Back to template restores the template.
Files
node:* and cloudflare:* modules are available (nodejs_compat). Outgoing traffic is HTTP and HTTPS through fetch(); raw TCP sockets (connect() from cloudflare:sockets, and database drivers built on them) are not available, so reach a database through an HTTP API.
500 Server Error and are recorded under Runtime errors.
The contract with the gateway
The gateway speaks to your Worker with HTTP headers only. Anything a consumer sends underx-jojapi-* is dropped before your Worker sees the request, so these values are trustworthy.
Request headers your Worker receives
The consumer’s own headers (minus hop-by-hop ones) arrive as they were sent; the API key never does. The body streams through unread.
Response headers your Worker may set
Every
x-jojapi-* header is stripped before the consumer sees the response; the gateway adds the usage and quota headers itself.
Billing
Metered billable objects read their units from the usage sources configured in Billable objects (a response header, a header key or a JSON field, each with a default). Where no source is configured, thex-jojapi-usage header applies. Platform rules stay the same: a 5xx bills nothing, a fixed amount is a ceiling, values are non-negative integers, and streaming responses (text/event-stream) report usage in-band as described in Adjusting usage.
Variables
Named values bound to the Worker asenv.NAME: upstream API keys, tokens, a proxy URL. Secret variables are encrypted at rest and never shown again; plain ones are visible. Names use capital letters, digits and underscores.
Storage and queues
Managed storage and queues created for your API alone and bound to its Worker:
They are added and removed on the Worker tab under Bindings, or from the CLI with
jojapi resources add <kind> <BINDING> (kv, d1, r2, queue, or do --class Counter) and jojapi resources remove <BINDING>, as previews like every other save. A removed key-value store, database or queue is deleted with its data once no active deployment uses it any more; object storage must be empty by then (see Removing storage). A binding name is unique across variables and storage.
A resource can also be used by other APIs — your own, or another account’s after it accepts — without copying it: see Shared resources.
Console logging and limits
- Console logging (Worker tab → Errors & logs): captures what your Worker writes with
console.*and the stack of uncaught exceptions. It is separate from the request log, which records every request anyway. It stays on until you switch it off, and each request then costs a little more to process: the cost appears under Compute usage as Console logging (per invocation) and Console log lines (per stored line). The last 500 lines are kept. - Limits: a request may use up to 30 seconds of CPU time and 50 outgoing requests, with an upstream call of at most 90 seconds. The platform raises these per API when an API needs more; write to support.
Compute usage
The Worker tab (Usage) shows what the Worker and its storage used at the edge over the last days — requests, CPU time, key-value, SQL, object storage, stateful object and queue operations, storage, console logging — next to the included allowance and a list-price estimate. It is informational: nothing is charged for compute today, and any future charge would be announced in advance.Runtime errors
Whatever goes wrong inside or behind your Worker forms one stream per API, grouped by kind and message: unreachable origins and timeouts (the generated code reports them), exceptions and bad outcomes (CPU or memory limits), 5xx answers, and everything your code reports throughx-jojapi-error. Each issue shows how often it happened, when it was first and last seen, and its recent occurrences with request ids that link back to the request log. Platform-side refusals (invalid key, quota, rate limit) are not runtime errors; they stay in the request log.
Proxies and the egress relay
IP-address hosts and non-standard ports are reached automatically through the platform’s egress relay. To send a request through your own HTTP, HTTPS or SOCKS5 proxy, set the headerx-jojapi-relay-proxy: <proxy URL> on your fetch(); keep the URL in a secret variable. The template’s Proxy field does exactly this.
The relay forwards your method, headers and body as they are: it sets Host, drops hop-by-hop headers and the platform’s own, and adds nothing. Put proxy credentials in the URL (http://user:pass@host:port), not in a header. Redirects are not followed (a 3xx comes back to your Worker), and a request gives up after 90 seconds.
Browser impersonation
Some sites answer differently depending on what the client’s TLS handshake and HTTP/2 connection look like. Setx-jojapi-relay-impersonate: <profile> on a fetch() and the relay makes that request with a real browser’s identity: its TLS handshake, HTTP/2 settings and default headers. It works with or without a proxy.
- Profiles:
chrome,chrome_android,edge,firefox,safariandsafari_iosfollow the newest version the platform has, or pin one such aschrome146,firefox144orsafari184. An unknown profile answers400. - Headers: yours replace the profile’s same-named ones in place, so set your own
user-agent, cookies oraccept-languagewhen you need them. The profile keepsaccept-encoding, and the body reaches your Worker already decoded. - The response carries
x-jojapi-relay-impersonatewith the exact profile used. - Without the header, requests keep the default client (HTTP/1.1).
- Failures of your own proxy are named in the relay’s
502answer, for exampleProxy Tunnel Refused (407).
Local development
Your Worker runs underwrangler dev (the standard Workers development server) like any other. Send the contract headers yourself while testing:
.dev.vars file, resources their local wrangler equivalents. The jojapi CLI (jojapi pull, jojapi deploy, jojapi dev) does this for you and bundles npm dependencies before upload. jojapi deploy creates a preview deployment and prints its URL; jojapi deploy --prod (or jojapi promote <id>) serves it in production.