Durable State API
The Durable State API is an internal JSON endpoint used by the dapp to manage nonces, enforce rate limits, and track quotas. It is backed by a single Cloudflare Durable Object that acts as a small, strongly-consistent state engine running alongside the worker.
This endpoint is not intended to be exposed directly to untrusted public clients. It is designed to be called from trusted services (such as the Next.js app) using a shared bearer token.
Endpoint summary
Within the worker, all requests whose path starts with /state are routed to this handler.
| Property | Value |
|---|---|
| Method | POST only |
| Path | Any path beginning with /state |
| Content type | application/json |
| Authentication | Authorization: Bearer STATE_SERVICE_AUTH_TOKEN |
If a request does not use POST, the handler returns a 405 Method Not Allowed error. All valid requests must include a JSON body describing which state action to perform.
Authentication
Access to the Durable State API is guarded by a shared bearer token.
- The worker expects a header in every request:
Authorization: Bearer STATE_SERVICE_AUTH_TOKEN
- The value of
STATE_SERVICE_AUTH_TOKENis configured as a Cloudflare secret and must match the corresponding environment variable in the Next.js app.
If authentication or configuration is missing:
- If the Durable Object binding (
STATE_STORE) is not configured, the worker responds with a500error and a message indicating that state storage is not configured. - If
STATE_SERVICE_AUTH_TOKENis not set in the worker environment, the worker responds with a500error indicating the auth token is missing. - If the
Authorizationheader is absent or does not match the expected bearer value, the worker responds with401 Unauthorized.
Request body
The request body is a JSON object that specifies an action and the parameters required for that action. The worker validates the payload and forwards it to the Durable Object, which executes the requested operation.
At a high level, the supported actions fall into three groups:
- Nonce actions – simple ephemeral tokens.
- Rate limit checks – sliding-window rate limiting.
- Quota management – usage windows for arbitrary keys.
If the body is empty or cannot be parsed as JSON, the worker returns a 400 error.
Nonce actions
Nonces are short-lived values used for authentication flows (for example, SIWE or other sign-in flows). They are keyed by an application-defined identifier.
Supported nonce actions:
-
nonce:set- Fields:
identifier– string key for the nonce (for example, a wallet address or session identifier).value– string value to store.ttlSeconds– number of seconds before the nonce expires.
- Behavior:
- Stores the value under the given identifier with a time-to-live.
- Fields:
-
nonce:get- Fields:
identifier– key to look up.
- Behavior:
- Returns the current value for the identifier or
nullif none is present.
- Returns the current value for the identifier or
- Fields:
-
nonce:consume- Fields:
identifier– key to look up.
- Behavior:
- Returns the value associated with the identifier and deletes it in a single step. If no value is present, it returns
null.
- Returns the value associated with the identifier and deletes it in a single step. If no value is present, it returns
- Fields:
Example payloads:
{
"action": "nonce:set",
"identifier": "wallet:0x1234…",
"value": "random-nonce-value",
"ttlSeconds": 300
}{
"action": "nonce:get",
"identifier": "wallet:0x1234…"
}{
"action": "nonce:consume",
"identifier": "wallet:0x1234…"
}Rate limit action
Rate limits allow the dapp to screen operations (for example, login attempts or API calls) before performing them.
ratelimit:check- Fields:
limiter– string name for the limiter (for example,"login"or"siwe").identifier– key to rate limit (for example, IP address or wallet address).limit– maximum number of allowed operations within the window.windowSeconds– length of the sliding window in seconds.
- Behavior:
- Tracks a list of timestamps for the given limiter and identifier.
- Removes entries that fall outside the current window.
- Marks the call as allowed if the number of timestamps is still below
limit, otherwise as blocked.
- Fields:
Example:
{
"action": "ratelimit:check",
"limiter": "siwe",
"identifier": "ip:203.0.113.5",
"limit": 5,
"windowSeconds": 60
}Quota actions
Quotas represent a reusable window of allowed usage for arbitrary keys. They track how much of a limit has been consumed and when the window resets.
Supported quota actions:
-
quota:ensure- Fields:
key– string key identifying the quota bucket.limit– maximum allowed usage within the window.durationSec– window length in seconds.
- Behavior:
- Ensures that a quota window exists for the key. If none exists or it has expired, a new window is created.
- Fields:
-
quota:increment- Fields:
key– quota key.amount– amount by which to increment usage.
- Behavior:
- Increments
usedwithin the window and returns the new usage and remaining capacity. - If the window has not been initialized (for example, no prior
quota:ensurecall), the operation fails with a404-style error.
- Increments
- Fields:
-
quota:incrementBatch- Fields:
entries– array of{ key, amount }objects.
- Behavior:
- Applies increments across multiple quota keys in a single call.
- If any referenced key does not have an initialized window, the operation fails and identifies which window was missing.
- Fields:
-
quota:resetKeys- Fields:
keys– array of quota keys to clear.
- Behavior:
- Deletes quota entries for each key, returning how many were removed.
- Fields:
-
quota:resetPrefix- Fields:
prefix– key prefix.
- Behavior:
- Deletes all quota entries whose keys start with the specified prefix.
- Fields:
Example payloads:
{
"action": "quota:ensure",
"key": "nft-mint:collection-1",
"limit": 100,
"durationSec": 3600
}{
"action": "quota:increment",
"key": "nft-mint:collection-1",
"amount": 1
}{
"action": "quota:resetPrefix",
"prefix": "nft-mint:"
}Response structure
All responses returned by the Durable Object share a common envelope. The worker simply forwards these responses back to the caller.
The envelope has two possible shapes:
- Success:
ok: trueresult– action-specific payload.
- Error:
ok: falseerror– human-readable message.
The HTTP status code reflects the outcome:
- Successful operations generally return
200. - Invalid requests return
400. - Missing quota windows return
404. - Misconfigurations (such as missing bindings or secrets) return
500.
Action-specific result shapes
The result field varies by action type:
-
Nonce actions
nonce:set–resultistruewhen the value is stored successfully.nonce:get–resultis the stored string value ornull.nonce:consume–resultis the stored string value (if present) ornullonce consumed.
-
Rate limit
ratelimit:check–resultis an object with:success–trueif the call falls within the limit; otherwisefalse.limit– configured limit for the window.remaining– remaining allowed calls within the current window (zero when blocked).reset– timestamp (in milliseconds) when the window will fully reset.
-
Quotas
quota:ensure–resultis a quota window object:limit– maximum allowed usage within the window.used– current usage.duration– total length of the window in seconds.resetAt– epoch timestamp (in seconds) indicating when the window resets.
quota:increment–resultis an object containing:used– updated usage.remaining– how much capacity is left before the quota is exhausted.
quota:incrementBatch–resultistruewhen all increments succeed.quota:resetKeys/quota:resetPrefix–resultis an object that reports how many entries were deleted and which keys were affected.
Error handling
In addition to the envelope mentioned above, the worker uses standard HTTP status codes for common error paths:
405 Method Not Allowed– The request used a method other thanPOST.400 Bad Request– The body was missing, could not be parsed as JSON, or theactionfield was unsupported.401 Unauthorized– TheAuthorizationheader was missing or did not match the configured bearer token.404 Not Found– A quota operation referenced a quota window that has not been initialized.500 Internal Server Error– The worker was misconfigured (for example, missingSTATE_STOREbinding orSTATE_SERVICE_AUTH_TOKEN).
These errors are intended to be consumed by internal services rather than end users. The messages are primarily targeted at developers operating the dapp.
Internal behavior (overview)
The Durable Object behind the /state endpoint is responsible for executing all state operations. While most callers only need the HTTP contract, it can be useful to understand how the implementation behaves:
- The worker resolves a single Durable Object instance from the
STATE_STOREbinding and uses it as a global state engine. - Nonces are stored with an explicit expiration based on
ttlSeconds, so they automatically disappear after they are no longer valid. - Rate limits are modeled as lists of timestamps. On each check, timestamps outside the configured window are discarded before evaluating whether another call is allowed.
- Quotas store a window with a fixed
limit,usedcount,duration, andresetAttime. OnceresetAtis reached, a new window is established on the nextquota:ensurecall. - Keys are organized in a namespaced fashion (for example, using prefixes) so that prefix-based resets can efficiently clear related entries.
The actual TypeScript types and storage keys are implementation details; the contract exposed by the Durable State API is the JSON interface documented above.
Related topics
- Overview: See the main Cloudflare Worker overview page for information on Wrangler configuration, environment variables, and testing.
- Email Relay API: See the email relay documentation for details on the companion endpoint that sends gated messages via Brevo.