Expose an endpoint
Connect an existing frontend to a work through a small JSON API.
Every endpoint is protected before your code runs, so a work only handles requests that already passed these checks:
- Input validation against the work’s
inputSchema. Invalid requests are rejected and don’t use a run. - Rate limiting and burst protection per endpoint and per visitor, at Cloudflare’s edge and again at admission.
- Allowed origins for browser calls (CORS).
- Identity, when the work asks for it: a signed-in Shopify customer verified through the app proxy or a headless storefront’s signed assertion, a merchant session, or a single-use invitation token.
- Edge caching, when a public GET work opts in. Responses served from the cache don’t run your work. See Cache a public read.
Your work still enforces business rules, such as whether this customer owns this record. For a public form, add what the feature needs on top: a uniqueness rule against duplicate signups, or a confirmation email before acting on an address.
Choose the entry point
| Trigger | Who can invoke it |
|---|---|
callable | An authenticated merchant or store-scoped agent through Littleworks. No public HTTP function route. |
HTTP, auth: "public" | Anyone who can reach the endpoint, subject to input validation, origin policy, and traffic limits. |
HTTP, auth: "customer" | A signed-in storefront customer: verified through Shopify’s shared app proxy on a theme, or asserted by a headless storefront’s server (Hydrogen) with a signed token. See customer access. |
HTTP, auth: "token" | A caller with a valid invitation token for this work and version. |
HTTP, auth: "merchant" | An authenticated merchant or store-scoped agent. This is not customer-account authentication. |
Declare an HTTP trigger
{
"trigger": {
"type": "http",
"method": "POST",
"auth": "public",
"origins": [
"https://your-store.example"
]
}
}Replace the example origin with the exact origin of the frontend, including its scheme. Declare GET or POST. POST accepts a JSON object; GET supplies query parameters as strings. Set an appropriate inputSchema for those values.
deploy_function, get_function and list_functions return the URLs of an HTTP work: previewUrl always, and url once the work is published (a paused work keeps it). Customer works add headlessPreviewUrl and headlessUrl for headless storefronts. Use them instead of constructing a path from a shop domain.
Real and preview URLs
| URL | Runs |
|---|---|
Real: https://littleworks.app/f/{store}/{name} | The current version of a published work, as a real run. Draft works return FUNCTION_NOT_PUBLISHED; paused works return FUNCTION_PAUSED. |
Preview: https://littleworks.app/f/{store}/preview/{token}/{name} | The newest version, as a test run, whatever the work’s status. |
Customer works: /apps/littleworks/{name} and /apps/littleworks/preview/{token}/{name} | The same pair through the store’s app proxy, for themes. A headless storefront’s server calls the direct /f/ pair instead, returned as headlessUrl and headlessPreviewUrl, with a signed customer assertion. |
The preview token is unguessable and belongs to one work. Use the preview URL to try a draft, or a pending version of a published work, from an unpublished theme or another frontend before publishing. Point published frontends only at the real URL: a preview URL runs whatever is deployed next, and documents it creates are test records. A merchant can rotate a work’s preview token in Littleworks; the old preview URL then returns 404.
const response = await fetch(workUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ message: "Hello" }),
});
if (!response.ok) throw new Error("The request could not be completed");
const result = await response.json();Here, workUrl is the URL returned by deployment. The input must match your work’s schema. Littleworks hosts the endpoint; your agent still needs to build and place the frontend using the appropriate theme or storefront tools.
Decide what a public request may do
For example, accepting a verified-purchase review requires server-side validation of a purchase-linked invitation. A public endpoint and a customer-supplied order ID alone do not establish eligibility. Never put an agent token in storefront code.
Return JSON
Return a JSON-compatible value or Response.json(...) with a status. Littleworks forwards the body and status, not arbitrary headers, cookies, streaming output, or HTML. This is an API endpoint, not a frontend hosting service.
Cache a public read
A published public GET work can let Cloudflare’s edge answer repeat requests. Reviews, badges, counters and other display data change far less often than shoppers read them. A cached response is fast and doesn’t use a run; the request that fills or refreshes the cache is an ordinary run.
{
"trigger": {
"type": "http",
"method": "GET",
"auth": "public",
"origins": [
"https://your-store.example"
],
"cache": {
"seconds": 3600,
"staleSeconds": 600
}
},
"inputSchema": {
"type": "object",
"properties": {
"product": {
"type": "string"
},
"country": {
"type": "string"
}
},
"additionalProperties": false
}
}| Entry point | Cached |
|---|---|
HTTP GET, auth: "public", real URL | Yes, when the trigger declares cache. |
| Preview URL | Never. It runs the newest version as a test run and returns X-Littleworks-Cache: BYPASS. |
auth: "customer", "token" or "merchant" | Never. Those responses belong to one caller. |
| POST, callable, page and automation works | Never. |
Requests with Authorization or Idempotency-Key | Never; they run the work. |
- Freshness.
seconds(10–86,400) is how long a response is served as fresh.staleSeconds(0–86,400, default 0) lets the edge keep serving it for that much longer while one run refreshes it in the background, which improves latency, not run count. If a refresh fails, the last response can be served a little longer. - What is stored. Only status 200 responses. Errors, 4xx and 5xx results, and Littleworks errors such as
FUNCTION_PAUSEDor a full run allowance always reach the caller. - The query is the key. The whole query string, sorted, keys the cache, so the input schema needs explicit
propertiesandadditionalProperties: false: an unknown parameter is rejected instead of creating a new entry. A repeated parameter or a query longer than 1,024 bytes runs the work without the cache. - Shared responses. Cached responses are shared by everyone who sends the same query; never return personal data from a cached work. Cached works receive no buyer IP, so pass market and language as explicit parameters (for example
countryandlanguagefor Storefront@inContext). - Read-only. Every run of a cached work, test runs included, can read documents, secrets, Shopify (queries only) and external services with GET or HEAD. Writes,
auth.issueTokenandcache.purgefail withCACHED_WORK_READ_ONLY. Deploying reports a warning when the source appears to write. - Changes take effect within about 15 seconds. Publishing, rolling back, pausing, resuming, deleting and purging reach every edge location within about 15 seconds; until then the previous responses may still be served. Browsers never cache responses (
Cache-Control: no-store). - Purge after a write. Another work in the same feature calls
await cache.purge("reviews.list")after it changes what the cached work returns, for example when a review is approved (at most 5 purges per run). Agents usepurge_cache; the CLI useslittleworks cache purge <name>. A purge clears every query of the work. - Check it. After publishing, request the real URL twice and read the
X-Littleworks-Cacheheader:MISS(a run, withX-Run-Id), thenHIT(withAge).STALEmeans a refresh is under way;BYPASSmeans the request ran without the cache. Local development has no edge cache.
Savings depend on traffic: a key saves runs only when it is requested again within seconds. Fewer, broader keys save more. One badge map for the whole store beats one request per product. The Usage page shows responses served from cache separately from runs.
Handle traffic protection
Littleworks automatically applies burst, concurrency, per-work and store execution limits, plus storage and external-request allowances. Littleworks time-based limits return HTTP 429 with Retry-After and error.retryAt. Cloudflare may block a flood before it reaches Littleworks; handle HTTP 429 even when the response is not JSON. Back off; do not automatically replay a mutation. The Shopify admin shows affected works and retry guidance. These limits are platform policy, not merchant settings.