StatikAPI Documentation

Build APIs from your data — without backend complexity.

StatikAPI helps you combine external APIs with your own content, shape outputs, and publish reliable structured endpoints. These docs cover the open source foundation, with guidance for local CLI workflows, Cloudflare deployment, and hosted platform usage.

StatikAPI App

Use the hosted visual workflow when you want managed publishing, private APIs, and automation.

Open StatikAPI App

The open source CLI stays free and self-hostable. StatikAPI App adds hosted workflows, visual editing, automations, analytics, and private API access.

Last updated: May 2026

Cloudflare Worker + Static Assets Adapter

The Cloudflare adapter turns a StatikAPI project into a Cloudflare deployment with two clearly separated surfaces:

  • Public routes are emitted as Cloudflare Static Assets under /public/...
  • Private routes are stored in a private R2 bucket and served through the Worker

It also provides:

  • a public manifest served as a static asset
  • a private manifest served by the Worker
  • authenticated route-path POST webhooks for private rebuilds
  • a local preview UI and Cloudflare-specific dev flow

This page documents the current shipped behavior in this repo.


1) What the adapter supports today

Public routes

  • Public is the default.
  • A route without config.cloudflare.public = false is treated as public.
  • Public routes are emitted under /public/....
  • Public routes are generated during statikapi-cf build into your configured Static Assets directory.
  • Cloudflare should serve those assets directly, without forcing the Worker to run first.

Private routes

  • Set config.cloudflare.public = false to make a route private.
  • Private routes keep their original route paths.
  • Private output is stored in a private R2 bucket.
  • Private reads require the configured auth header.

Manifests

  • Public manifest:
    • /public/_manifest when STATIK_USE_INDEX_JSON = "false"
    • /public/_manifest/index.json when STATIK_USE_INDEX_JSON = "true"
  • Private manifest:
    • GET /_manifest
    • requires the private auth header

Manifest entries include route metadata from the source route:

  • srcRoute records the original route pattern
  • webhookAvailable is true when that route can be refreshed by webhook POSTs
  • webhookRoute repeats the source route that webhook POSTs target

Webhook rebuilds

  • POST /
    • rebuild all webhook-enabled private outputs
  • POST /users/1
    • rebuild the matching private route
  • POST to a public route path
    • rejected on purpose

Important limitation in this version

Public routes are not webhook-refreshable in this version.

That means:

  • POST / and route-path POST requests refresh private Worker-managed outputs only
  • public Static Assets change only after a new build + deploy

If a route must be updated immediately by webhook in production, it should stay private instead of public.


2) Scaffold a project

Use the dedicated template:

bash
pnpm dlx create-statikapi my-worker --template cloudflare-adapter
# or
npx create-statikapi my-worker --template cloudflare-adapter
# or
yarn create statikapi my-worker --template cloudflare-adapter

The template creates:

  • wrangler.toml
  • statikapi.config.js
  • .dev.vars
  • src-api/
  • package-manager-specific dev, build, and deploy scripts

The scaffold asks for:

  • project name
  • package manager
  • Static Assets directory for public output
  • private R2 bucket binding and bucket name
  • KV binding and namespace id
  • STATIK_BUILD_TOKEN
  • STATIK_SRC
  • STATIK_USE_INDEX_JSON
  • private auth header name and value
  • Cloudflare account id
  • Cloudflare API token placeholder
  • optional runtime limit values

After scaffolding:

bash
cd my-worker
pnpm install
pnpm dev

The default dev flow:

  • builds the Worker bundle
  • builds public Static Assets
  • starts local wrangler dev
  • starts the preview UI
  • tries to open /_ui/ automatically

Local vs deployed variables

The scaffold now writes a real .dev.vars file directly.

Use .dev.vars for:

  • local preview/private-route auth during pnpm dev
  • local wrangler commands that need CLOUDFLARE_ACCOUNT_ID or CLOUDFLARE_API_TOKEN

In the current scaffold contract:

  • .dev.vars stays local to your machine
  • wrangler.toml still carries non-secret runtime config such as route-shape settings, bindings, and usage limits
  • the auth/build values below are no longer scaffolded in wrangler.toml:
    • STATIK_BUILD_TOKEN
    • STATIK_PRIVATE_AUTH_HEADER_NAME
    • STATIK_PRIVATE_AUTH_HEADER_VALUE

So if a value must affect the deployed Worker runtime, make sure it is set in the deployed Worker configuration, not just in local .dev.vars.


3) Route behavior and route config

Route modules can export route-level Cloudflare config:

js
export const config = {
  cloudflare: {
    public: false,
    webhook: true,
  },
};

config.cloudflare.public

  • omitted or true
    • route is public
    • output is emitted under /public/...
  • false
    • route is private
    • output stays on the original route path
    • route is served by the Worker

config.cloudflare.webhook

  • omitted
    • follows the project default
  • true
    • allow webhook rebuilds for that route
  • false
    • reject targeted webhook rebuilds for that route and skip it during full rebuilds

config.listIndex

The Cloudflare adapter also supports config.listIndex for dynamic and catch-all routes, just like the regular CLI path.

Example:

js
export const config = {
  listIndex: {
    enabled: true,
    pick: ['id', 'title'],
  },
};

That will generate a parent collection route in addition to the per-item outputs:

  • item routes:
    • /public/posts/1
    • /public/posts/2
  • collection route:
    • /public/posts

The exact file shape depends on STATIK_USE_INDEX_JSON.


4) File and URL shape

The adapter supports two public-output shapes via STATIK_USE_INDEX_JSON.

STATIK_USE_INDEX_JSON = "false"

Public assets look like:

  • route surface:
    • /public
    • /public/posts/1
    • /public/docs/guide
    • /public/_manifest
  • backing asset files may use hidden extensionless index files so parent and child routes can coexist:
    • public/index
    • public/posts/1/index
    • public/docs/guide/index
    • public/_manifest/index

STATIK_USE_INDEX_JSON = "true"

Public assets look like:

  • /public/index.json
  • /public/posts/1/index.json
  • /public/docs/guide/index.json
  • /public/_manifest/index.json

Private routes always keep the route path itself:

  • /account
  • /posts/1
  • /_manifest

For false mode, private stored keys are extensionless too, for example:

  • account
  • posts/1

The preview UI and snippet generator should follow the actual project shape.


5) wrangler.toml shape

The scaffolded wrangler.toml is built around one Worker + one Static Assets directory + one private R2 bucket + one KV namespace.

Typical structure:

toml
[assets]
directory = "./public"
binding = "ASSETS"

[[r2_buckets]]
binding = "STATIK_PRIVATE_BUCKET"
bucket_name = "my-statikapi-private"

[[kv_namespaces]]
binding = "STATIK_MANIFEST"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

[vars]
STATIK_SRC = "src-api"
STATIK_USE_INDEX_JSON = "false"
STATIK_PRIVATE_BUCKET_BINDING = "STATIK_PRIVATE_BUCKET"
STATIK_WORKER_REQUEST_LIMIT = "0"
STATIK_R2_CLASS_A_LIMIT = "0"
STATIK_R2_CLASS_B_LIMIT = "0"

Important notes:

  • [assets].directory is the public output target
  • do not configure run_worker_first = true for this adapter contract
  • public asset requests should be served by Static Assets directly
  • the Worker still handles private routes, manifests, and rebuild webhooks

Cloudflare references:


6) Local development

Recommended command

bash
pnpm dev

This runs statikapi-cf dev.

The local flow is:

  1. Build dist/worker.mjs
  2. Generate public assets into the configured assets directory
  3. Start local wrangler dev
  4. Start the preview UI, usually on http://127.0.0.1:8788/_ui/
  5. Watch src-api/ and statikapi.config.js
  6. Rebuild on change
  7. Refresh private outputs locally after rebuild so private route content also changes during dev

Preview UI behavior

The preview UI for Cloudflare projects:

  • shows both public and private routes
  • groups them separately
  • injects the private auth header from .dev.vars when previewing private routes
  • uses the Worker origin in the route details snippets

.dev.vars

Local preview of private routes depends on:

env
STATIK_PRIVATE_AUTH_HEADER_NAME=x-statik-private-auth
STATIK_PRIVATE_AUTH_HEADER_VALUE=dev-private-token
STATIK_BUILD_TOKEN=dev-token-123

If those values are missing, private preview and local private rebuilds will fail.


7) Build, preview, and deploy commands

Build

bash
pnpm build

Build should produce both:

  • Worker bundle:
    • dist/worker.mjs
  • public assets:
    • inside your configured Static Assets directory

Preview only

If you are already running the Worker separately:

bash
pnpm exec statikapi-cf preview --worker http://127.0.0.1:8787 --port 8788

Deploy

bash
pnpm deploy

or:

bash
wrangler deploy

In the scaffolded project, pnpm deploy is the safer default because it runs the StatikAPI build first.

Deploying publishes:

  • Worker code
  • Worker bindings/config
  • public Static Assets

It does not magically refresh public assets later from runtime webhooks.


8) Pricing, free limits, and what starts charging

This adapter uses four Cloudflare product surfaces:

  • Workers
    • for private route reads, POST rebuilds, and the private manifest
  • Static Assets
    • for public routes under /public/...
  • Workers KV
    • for manifest/control-plane metadata
  • R2
    • for private route payloads

What is free on Cloudflare's free tiers

As of May 2026, the most relevant limits for this adapter are:

  • Workers Free
    • 100,000 requests per day
    • 10 ms CPU time per invocation
    • 50 external subrequests per invocation
    • 20,000 Static Asset files per Worker version
  • Static Assets
    • static-asset requests are free and unlimited
    • no additional storage charge for Static Assets
  • Workers KV Free
    • 100,000 key reads per day
    • 1,000 writes per day
    • 1,000 deletes per day
    • 1,000 list requests per day
    • 1 GB stored data
  • R2 Standard Free
    • 10 GB-month storage per month
    • 1 million Class A operations per month
    • 10 million Class B operations per month
    • egress is free

Cloudflare references:

What that means for this adapter in practice

If your project is mostly public:

  • public asset traffic is the cheapest path
  • static-asset requests do not consume Worker request quota when they are served directly by Static Assets

If your project is private-heavy:

  • every private read goes through the Worker
  • those requests count against Workers usage
  • each private read also causes R2 read-class work

If you rebuild private routes often:

  • the Worker rebuild endpoint uses Worker requests
  • KV updates count against KV usage
  • R2 writes/lists count against R2 Class A operations

When charges start

Workers

  • on the free plan, once you exceed the free daily limits, requests stop being free and you need the paid Workers plan
  • on the paid Workers Standard plan, pricing currently includes:
    • 10 million requests per month
    • 30 million CPU milliseconds per month
    • then overages are billed per additional million
  • Cloudflare currently documents the Workers Paid plan as having a $5/month minimum

Static Assets

  • static-asset requests are free and unlimited
  • Cloudflare currently documents no extra storage charge for Static Assets themselves

Workers KV

  • on the free plan, exceeding a daily KV limit causes further operations of that type to fail until reset
  • on the paid plan, overages are billed by operation/storage type

R2

  • beyond the monthly free tier, R2 bills separately for:
    • GB-month stored
    • Class A operations
    • Class B operations
  • Cloudflare rounds usage up to the next billable unit

Rough free-tier guidance

These are rules of thumb, not guarantees:

  • if your API is mostly public and traffic is low to moderate, the free plan can stretch surprisingly far because Static Asset requests are free
  • if you have many authenticated/private reads, the Worker free request limit becomes the first likely bottleneck
  • if you rebuild private data frequently or store lots of private payloads, R2 and KV become the next costs to watch

You should expect to move to the paid Workers plan sooner if:

  • private endpoints are used regularly in production
  • private rebuild webhooks run frequently
  • your Worker does noticeable compute per request

Concrete examples

Example A: Mostly public API

Imagine:

  • almost all traffic hits /public/...
  • private routes are rarely used
  • rebuilds happen a few times per day

This is the most free-tier-friendly shape for this adapter because:

  • public Static Asset requests are free and unlimited
  • Worker usage stays relatively low
  • R2/KV activity stays low

In this shape, your first paid pressure point is often deployment/build frequency or private-admin usage, not public traffic itself.

Example B: Private dashboard/API usage

Imagine:

  • authenticated clients frequently read private routes
  • each private request goes through the Worker
  • each private read also hits private storage

In this shape, your first pressure point is usually:

  • Workers request usage

and then:

  • R2 read operations

This is the shape where you should expect to move to a paid Workers plan sooner.

Example C: Rebuild-heavy private content

Imagine:

  • a CMS or backend triggers POST / or POST /route often
  • each rebuild updates multiple private outputs

In this shape, the cost pressure is usually:

  • Worker rebuild requests
  • KV manifest/control-plane writes
  • R2 Class A operations

That means frequent rebuild automation can become the main source of metered usage even before read traffic gets large.

What happens when you exceed limits

  • Workers Free
    • requests can fail once the account exceeds the free request quota
  • KV Free
    • further operations of the exceeded type fail until reset
  • R2
    • overage is billed rather than simply blocked, according to the current pricing model

Check the latest Cloudflare docs before launch, because these limits and prices can change.


9) Webhook rebuild contract

Full rebuild

bash
curl -X POST "https://api.example.com/" \
  -H "Authorization: Bearer YOUR_STATIK_BUILD_TOKEN"

Effect:

  • rebuild webhook-enabled private outputs
  • update the private manifest
  • leave already-deployed public Static Assets unchanged

Targeted private rebuild

bash
curl -X POST "https://api.example.com/account" \
  -H "Authorization: Bearer YOUR_STATIK_BUILD_TOKEN"

Effect:

  • rebuild only that private route, if webhook is enabled for it

Public-route POSTs are rejected

Example:

bash
curl -X POST "https://api.example.com/public/posts/1" \
  -H "Authorization: Bearer YOUR_STATIK_BUILD_TOKEN"

That should be rejected because public routes are Static Assets in this model.

STATIK_BUILD_TOKEN is not a Cloudflare API token

STATIK_BUILD_TOKEN is your app’s own shared secret for authorizing rebuild webhooks.

It protects:

  • POST /
  • POST <private-route-path>

It should be a long random secret and should be treated like any other deploy-time secret.


10) Private auth header contract

Private reads require the configured auth header.

Example:

bash
curl "https://api.example.com/account" \
  -H "x-statik-private-auth: YOUR_PRIVATE_VALUE"

The same applies to:

  • GET /_manifest
  • all private route reads

Public routes do not need that header.


11) Cloudflare setup checklist

A) Account ID

You need your Cloudflare Account ID for Worker deploy wiring and token scoping.

Official doc:

Practical dashboard path:

  1. Open Workers & Pages
  2. Look for Account details
  3. Copy the Account ID

B) Private R2 bucket

You need the bucket name, not a random label.

Official docs:

You can create it in the dashboard or with Wrangler:

bash
wrangler r2 bucket create my-statikapi-private

C) KV namespace

You need the namespace id for wrangler.toml.

Official docs:

In Cloudflare:

  1. Go to Workers & Pages
  2. Open KV
  3. Create or select a namespace
  4. Copy the namespace id

D) Static Assets directory

This is the folder Wrangler uploads as public Static Assets.

Official docs:

Common choices:

  • public
  • dist
  • another generated folder

For this adapter, the scaffold defaults to public.


12) Create the Cloudflare API token correctly

Use a custom API token for deployment. Do not use the global API key.

Official docs:

Recommended minimum permissions

For this adapter, the practical account-scoped token is:

  • Workers Scripts Edit
  • Workers KV Storage Edit
  • Workers R2 Storage Edit

Optional:

  • Workers Tail Read
    • only if you want wrangler tail

Cloudflare’s docs may show the same capability as Edit in some places and Write in others. For deployment purposes, use the write-capable version of those permissions.

Recommended token creation flow

  1. Open Cloudflare Dashboard
  2. Go to My Profile -> API Tokens
    • or Manage Account -> API Tokens if you use account-owned tokens
  3. Select Create Token
  4. Choose Create Custom Token
  5. Give it a name like:
    • statikapi-cloudflare-deploy
  6. Add these Account permissions:
    • Workers Scripts: Edit
    • Workers KV Storage: Edit
    • Workers R2 Storage: Edit
  7. Scope the token to the specific Cloudflare account you will deploy into
  8. Create the token and copy it immediately

Where this token goes

Keep the Cloudflare deploy token in your local environment or local .dev.vars for deploy commands, and use CI/project secrets in automated deploy flows.

Keep it separate from:

  • STATIK_BUILD_TOKEN
  • STATIK_PRIVATE_AUTH_HEADER_VALUE

Those are application/runtime secrets, not Cloudflare deploy credentials.

Git-connected deployments

Cloudflare can also deploy a Worker from a connected Git repository through its build system. That path is useful if you want deployments to run on pushes instead of from your terminal or self-managed CI.

For this repo, keep the same separation of responsibilities:

  • source code and wrangler.toml live in git
  • deploy credentials live in Cloudflare build/project secrets or deployment settings
  • runtime secrets live in the deployed Worker configuration
  • STATIK_DEPLOY_ORIGIN points at the final deployed Worker or custom domain if you want to make manual post-deploy private-output seeding easier

Recommended Git-connected setup:

  1. Connect the repository in the Cloudflare dashboard.
  2. Choose the branch you want to deploy.
  3. Set the deployment command to pnpm deploy if you want this wrapper's build-first behavior.
  4. Make sure the deployment environment has the Cloudflare deploy credential and any build-time values the wrapper needs.
  5. Set the deployed Worker secrets separately for:
    • STATIK_BUILD_TOKEN
    • STATIK_PRIVATE_AUTH_HEADER_NAME
    • STATIK_PRIVATE_AUTH_HEADER_VALUE

If you prefer a simpler remote pipeline, you can use wrangler deploy as the deployment command and keep the StatikAPI build outside the Cloudflare build step. That works too, but then you must document the manual build and post-deploy seed steps yourself.


13) Deploy to Cloudflare and add a custom domain

Deploy the scaffolded project

After filling in wrangler.toml, preparing .dev.vars, and setting the deployed Worker auth/build vars, the normal deployment flow is:

bash
pnpm deploy

or, if you prefer Wrangler directly:

bash
pnpm build
wrangler deploy

What this deploy does:

  • uploads the Worker bundle
  • uploads the configured Static Assets directory
  • applies the Worker bindings and non-secret variables from wrangler.toml

What pnpm deploy adds on top of raw wrangler deploy:

  • runs the StatikAPI build first so changed public Static Assets are fresh at deploy time
  • loads local deploy credentials from .dev.vars for the wrapper command
  • if you need to seed private outputs after deploy, send a manual POST / to the deployed Worker
  • pnpm deploy still succeeds even when you skip that manual seed step

Before deploying, confirm:

  • the private R2 bucket already exists
  • the KV namespace already exists
  • the binding names in wrangler.toml match the names referenced by the scaffold
  • the deployed Worker has:
    • STATIK_BUILD_TOKEN
    • STATIK_PRIVATE_AUTH_HEADER_NAME
    • STATIK_PRIVATE_AUTH_HEADER_VALUE
  • your deploy token has the required permissions

You can set those deployed Worker values either:

  • in the Cloudflare dashboard under Worker settings/secrets
  • or with Wrangler:
bash
wrangler secret put STATIK_BUILD_TOKEN
wrangler secret put STATIK_PRIVATE_AUTH_HEADER_NAME
wrangler secret put STATIK_PRIVATE_AUTH_HEADER_VALUE

If you want the deploy wrapper to try seeding private outputs automatically after a deploy, also set:

  • STATIK_DEPLOY_ORIGIN

in .dev.vars to the deployed Worker or custom-domain origin.

Short production checklist

Before your first production deploy:

  1. Create the private R2 bucket.
  2. Create the KV namespace.
  3. Confirm your Cloudflare API token can:
    • deploy Workers
    • access KV
    • access R2
  4. Confirm wrangler.toml contains the real:
    • account id
    • bucket name
    • KV namespace id
    • assets directory
  5. Decide whether your production hostname will be:
    • workers.dev temporarily
    • or a Custom Domain such as api.example.com
  6. Confirm which routes must stay private because they need webhook-refreshable behavior.
  7. Optionally set STATIK_DEPLOY_ORIGIN=https://your-app.example.com in .dev.vars if you want a saved worker origin for manual seeding.
  8. Run:
bash
pnpm deploy

If you need to seed private outputs after deploy, use:

bash
curl -X POST "YOUR_WORKER_URL/" \
  -H "Authorization: Bearer YOUR_STATIK_BUILD_TOKEN"

Make sure the deployed Worker has the same secrets as your local .dev.vars by setting STATIK_BUILD_TOKEN, STATIK_PRIVATE_AUTH_HEADER_NAME, and STATIK_PRIVATE_AUTH_HEADER_VALUE in Wrangler or the Cloudflare dashboard.

Add a custom domain

For production, Cloudflare recommends using a Custom Domain or route instead of relying only on the workers.dev hostname.

Official docs:

Cloudflare's current Custom Domain flow:

  1. Make sure the domain is on an active Cloudflare zone you control.
  2. Deploy the Worker first.
  3. In Cloudflare Dashboard:
    • go to Workers & Pages
    • open your Worker
    • go to Settings -> Domains & Routes
    • choose Add -> Custom Domain
  4. Enter the hostname, for example:
    • api.example.com
  5. Confirm the setup.

Cloudflare will create the necessary DNS record and certificate handling for that hostname.

Wrangler-based custom domain example

Cloudflare also documents configuring custom domains in Wrangler:

toml
[[routes]]
pattern = "api.example.com"
custom_domain = true

Then deploy again:

bash
wrangler deploy

Important caveats

  • you cannot create a Custom Domain on a hostname with an existing conflicting CNAME
  • a Custom Domain points the whole hostname at the Worker
  • this adapter expects public assets and private Worker endpoints to live under the same deployed hostname

That means a hostname like api.example.com works well for this adapter:

  • public assets:
    • https://api.example.com/public/...
  • private routes:
    • https://api.example.com/account
  • manifests:
    • https://api.example.com/public/_manifest...
    • https://api.example.com/_manifest

14) Deployment model and tradeoffs

This Cloudflare integration is designed around a deliberate tradeoff:

  • public
    • cheaper to serve
    • cache-friendly
    • deployed as Static Assets
    • not runtime-refreshable by webhook in this version
  • private
    • Worker-managed
    • authenticated
    • runtime-refreshable by webhook

That means you should decide route visibility partly based on freshness needs:

  • if the route must update immediately from a webhook, keep it private
  • if it can wait for a build + deploy, keep it public

15) Troubleshooting

assets binding missing

Usually means your wrangler.toml is missing:

toml
[assets]
directory = "./public"
binding = "ASSETS"

GET /_manifest returns 403

Expected unless you send the private auth header.

Public route did not change after POST /

Expected in this version.

You must:

  1. run a new build
  2. deploy again

Private route did not change during local dev

Make sure:

  • .dev.vars exists
  • STATIK_BUILD_TOKEN is present
  • STATIK_PRIVATE_AUTH_HEADER_NAME and STATIK_PRIVATE_AUTH_HEADER_VALUE are present

Preview UI looks stale after UI-source changes

Run:

bash
pnpm ui:build

That now refreshes both:

  • packages/cli/ui
  • packages/adapter-cloudflare/ui

so the regular CLI preview and Cloudflare preview stay in sync.

Get started

Ready to publish your first API?

Start locally with the CLI or use StatikAPI Cloud when you want managed publishing and automation.

Get Started View examples