The project's platform operations, driven with the project's own token — what is neither generic document access (Admin), nor a storefront process (Process), nor the master-only control plane (System):
- Providers — switching who issues the invoices (
invoicing/provider) and who processes the paid orders (orders/processor): the one writers of those switches, which the generic CRUD refuses; the staff actions on one order (Retry now / Push now for a provider module). - Payments — the catalog the checkout offers (
payments/setup), Mark paid / Mark unpaid and the payment-alert Resolved for offline payments. - Shipping — the shipment actions on an order (
shipping/create/label/cancel/track/manifest), a carrier'sverify, and the transitionalshipping/setuproute whose replacement is the generic CRUD oversetup/shipping+setup/deliverywith the credentials through Securesecret. - Traffic —
analytics/report, the Google Analytics 4 proxy (curated named reports from the project's own property through a service account the platform holds — the credential never reaches a browser), with the onboarding probesanalytics/propertiesandanalytics/verify(a failure answers200 {ok: false, reason}) and the transitionalanalytics/setup; andstats/report, the platform's own server-side traffic record (nothing to configure, adblock-proof, batch-fresh). - Console — the console's pinned side menu and Overview widgets (
console/read/menu/overview): two documents of the console itself, shared by Demo and PROD, replaced whole (read, edit, write back) and validated against the console's own vocabulary — the page routes and widget kindsconsole/readanswers. Writes need theownerrole or the explicitproject.consolegrant (the SDK & MCP token carries it); each write shows in the project's Activity log. - Web — the web-address status with its custom-domain checks (
web/domain) and the hosting (hosting/status/provision/reroll/host): the owner-facing half of the tenant fleet's control plane, with no serving secret in any answer.
Every route rides the token's project grant — its own root key in the access tree (member tokens hold it for every member) — and then gates itself: the switches, the payment alert and the GA4 probes are owner / master only (403 role_forbidden), the staff actions take editor too, each checks the token's path scope on the document it touches (the setup doc, the order, setup/web for hosting), and a module install token is refused. Typed refusals come back top-level ({error: code, message, …} with the code's status).
Owner-facing web-address status for the admin card. Returns the store's canonical tenant URLs per env plus live reachability probes (HEAD from the functions side, 4s timeout) - https_ok means TLS + any HTTP answer - and the /healthz of the server that owns THIS store's custom domains (its custody pin, else the fleet default - see /system/tenant/host). Always includes the dns block of record values the admin renders as custom-domain setup instructions (A/AAAA targets - empty when the target advertises a CNAME only, CNAME target, host_source, the _drop TXT name + expected value - txt_value absent until the store is provisioned with a site key). With domains, each listed custom domain additionally gets live checks - _drop TXT vs the expected value, A/AAAA vs that target's addresses (configured, else resolved live from the target host; CNAMEs resolve through), and an HTTPS HEAD which doubles as the certificate check. Pure UX, not part of the serving path (the tenant server re-validates the TXT itself).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
op | string | optional | Operation - only "status" (default). |
domains | list | optional | Custom domains to check (max 10). Hosts under the tenant base domain are rejected as invalid. |
Response
object — Returns {sid, prod - {url, https_ok, status}, demo - {url, https_ok, status}, server - {url, https_ok, status}, dns - {a, aaaa, cname_target, host_source, txt_name, txt_value}, domains? - [{domain, valid, txt_ok, txt_found, dns_ok, https_ok, status}]}.
Sample request
{
"op": "status",
"domains": [
"shop.example.com"
]
}Switch who issues the store's invoices (.dev/plans/invoice-providers.md A2.3) - the ONE writer of setup/invoice.provider (the crud fence refuses the field). platform = the built-in mask numbering; module:{id} = an installed, enabled and verified module that provides an invoicer (refusals provider_tag_invalid 400, invoicer_unknown 400, invoicer_unsupported 501, provider_disabled 409, provider_unverified 409). Appends setup/invoice.provider_history {at, provider, last_platform_num, by}. Answers the count of orders still pending at the previous module invoicer (they keep going to it - S-D9) and, for a module target, the warning renewal_snapshot {orders} when legacy Stripe subscriptions would renew from their frozen first-period snapshot. With dry: true it validates and counts but writes nothing (changed: false, dry: true) - what a confirmation shows before the switch. Role owner or master (403 role_forbidden); scope write on store/{sid}/setup/invoice.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
provider | string | required | platform or module:{id}. |
dry | boolean | optional | true = validate and count only, write nothing. |
Response
object — {provider, previous, changed, pending, warnings: [{code, orders}]}
Sample request
{
"provider": "module:fakturoid"
}Staff Retry now for a module-issued invoice that is pending (.dev/plans/invoice-providers.md A3.3) - the same invoice.issue call with the same idempotency key, to the entry's OWN provider, made in the request (20 s) and applied like the call task. period defaults to the only pending entry (400 period_required when there are several). Refusals: 404 provider_entry_missing, 409 provider_call_running (a call younger than 120 s), 409 provider_gone (the entry's module is no longer selectable - reassign). reassign: true (owner or master) re-issues through the CURRENT setup/invoice.provider under the key {oid}:{period}:r{n} (n = the entry's attempts); when the current provider is platform a built-in number is reserved and the finalize pass writes the document. Scope write on store/{sid}/orders/{oid}; roles master, owner, editor.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
order | string | required | The order id. |
period | string | optional | The invoicing entry - once, start or the period end in seconds. |
reassign | boolean | optional | Re-issue through the current provider (owner or master). |
Response
object — {state: issued | calling | failed, number?, provider?, key?, attempts?, error?, retry?}
Sample request
{
"order": "o1",
"period": "once"
}Staff Issue proforma (.dev/plans/proforma-invoices.md 3.9) - B2B pay before we ship. Switches an unpaid confirmed or ready order to a pending bank transfer (payment service transfer, cash, on_delivery, personal or none) with an optional due date; the order trigger then issues the proforma through the store's invoicer (the platform series, or a module that provides proformas) and mails it. Errors - 409 proforma_not_allowed {reason: disabled | invoicer | no_bank_account | status | exists | paid | service | customer}, 400 due_invalid, 404 order_not_found. Scope write on store/{sid}/orders/{oid}; roles master, owner, editor (role_forbidden otherwise).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
order | string | required | The order id. |
due | string | optional | YYYY-MM-DD the transfer is due, today or later (default: today + setup/proforma.due_days, 14). |
Response
object — {order, due?}
Sample request
{
"order": "aBcDeF123",
"due": "2026-10-25T00:00:00.000Z"
}Resend an unpaid proforma to the buyer (.dev/plans/proforma-invoices.md 3.4) - only one the platform mails (delivery platform; a provider-mailed proforma is 409 proforma_delivery_provider). Uses the store's template record proforma_email, else the bundled proforma e-mail. Errors - 409 proforma_missing, 409 proforma_not_issued. Scope write on the order; roles master, owner, editor.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
order | string | required | The order id. |
Response
object — {sent, error?}
Sample request
{
"order": "aBcDeF123"
}Switch who processes the store's paid orders (.dev/plans/invoice-providers.md O2) - the ONE writer of setup/order.processor (the crud fence refuses the field). platform = the store itself; module:{id} = an installed, enabled and verified module that provides orders (refusals provider_tag_invalid 400, orders_unknown 400, orders_unsupported 501, provider_disabled 409, provider_unverified 409). With a module selected every order is pushed ONCE (order.push, key = the order id) on the pass that first sees it ready or active with its number. Appends setup/order.processor_history {at, processor, by} (the last 50 kept). Answers the count of orders still pending at the previous module (they keep going to it - S-D9). Role owner or master (403 role_forbidden); scope write on store/{sid}/setup/order.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
processor | string | required | platform or module:{id}. |
Response
object — {processor, previous, changed, processor_history: [{at, processor, by}], pending}
Sample request
{
"processor": "module:baselinker"
}Staff push of an order to its order system (.dev/plans/invoice-providers.md O3) - ONE order.push call made in the request (20 s) and applied like the call task; the answer is the order's processing afterwards. Retry now (a pending or failed push): to the entry's OWN provider with the same key (the order id) - 409 provider_gone when that module is no longer selectable. Push now (no processing yet): to the current setup/order.processor - 409 processor_platform when none is selected, 409 order_not_pushable unless the order is ready, active, shipping or delivery, 409 order_number_missing without a number. reassign: true (owner or master) redirects a failed push to the current processor (same key). Always refused: 409 order_pushed (processing.ref is set), 409 provider_call_running (a call younger than 120 s), 404 order_not_found. Scope write on store/{sid}/orders/{oid}; roles master, owner, editor (role_forbidden otherwise).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
order | string | required | The order id. |
reason | string | optional | retry (default with an entry) or manual (default without one) - recorded on the entry. |
reassign | boolean | optional | Push through the current processor instead of the entry's (owner or master). |
Response
object — {processing: {provider, ref?, number?, url?, status?, status_at?, state?, attempts?, error?, retry?, …} | null}
Sample request
{
"order": "o1"
}The store's payment catalog (.dev/plans/invoice-providers.md G6) - op: get (default) reads it, op: set validates and replaces setup/payments.methods (the ONE writer of the catalog). With a non-empty catalog a checkout must resolve to one of its enabled rows, and cash, on_delivery and every module:{id} service exist only through it; an empty list removes the catalog (Stripe services are then unchecked and offline services refused, as before). The token needs path scope on setup/payments (read / write); a module token is refused (403 module_token_forbidden). Errors - 400 payment_method_unknown (shape, an unknown service, a module that is not a selectable payment provider, a method or currency its entry does not declare), 400 payment_method_limit (bounds outside the module method's limits).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
op | string | optional | get (default) or set. |
methods | array | optional | op: set - the whole catalog: [{id, service, method?, name: {locale: label}, countries?, currencies?, min?, max?, enabled?}]. service is one of stripe_checkout, stripe_intent, cash, on_delivery or a module:{id} payment provider; min / max are order-total bounds in major units. |
Response
object — {setup, methods, modules: [{tag, module, enabled, verified_at, name, entry}], legacy: store.payments}
Sample request
{
"op": "set",
"methods": [
{
"id": "cod",
"service": "on_delivery",
"name": {
"en": "Cash on delivery"
},
"countries": [
"CZ"
]
}
]
}Mark an offline payment as received (.dev/plans/invoice-providers.md G5) - staff Mark paid for an order whose payment service is cash, on_delivery or transfer. Writes payment.status succeeded, payment.paid_on, one payments.manual-{hash} entry (amount in minor units) and a ledger row; works in any order status and is idempotent (changed: false when already paid). The token needs write scope on the order; a module token is refused. Errors - 404 order_not_found, 409 payment_not_offline, 400 paid_on_invalid.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
order | string | required | The order id. |
paid_on | string | optional | YYYY-MM-DD the money arrived (default: today, Europe/Prague). |
amount | number | optional | Major units (default: the order's invoice total). |
Response
object — {changed, entry}
Sample request
{
"order": "aBcDeF123"
}Reverse a manual Mark paid (.dev/plans/invoice-providers.md G5) - removes the payments.manual-{hash} entry this route's settle wrote and restores payment.status pending unless another settlement (a delivery, an invoicer's paid event) still stands. Same access as settle.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
order | string | required | The order id. |
Response
object — {changed, entry}
Sample request
{
"order": "aBcDeF123"
}Resolve an order's payment alert (.dev/plans/invoice-providers.md G6) - payment_alert marks money the platform could not act on by itself - a module gateway's payment that was paid after the order closed (paid_after_close), paid but never recorded (paid_unrecorded), refunded at the gateway (refunded) or reported with another amount (amount_mismatch). The merchant settles it in the gateway's portal, then clears the alert here; a ledger row records who did. Owner or master (403 role_forbidden), write scope on the order, a module token is refused. Errors - 404 order_not_found, 400 clear_required.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
order | string | required | The order id. |
clear | boolean | required | Must be true. |
Response
object — {cleared}
Sample request
{
"order": "aBcDeF123",
"clear": true
}Run one curated Google Analytics 4 report for the store (visitors, page views, traffic sources, ...). The store must be configured first via /admin/analytics/setup (service account + property id). Dimensions/metrics are fixed server-side per report name — this is a named-report proxy, not a raw GA4 passthrough. Responses are cached ~10 min per (store, report, range); the realtime report ~30 s. Typed failures carry a reason in the error body: not_configured | invalid_credentials | permission_denied | invalid_property | api_disabled | quota_exhausted | unavailable.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
report | string | required | overview (totals + deltas vs the previous period) | timeseries (daily activeUsers/sessions/screenPageViews) | pages (top 10 by views) | sources (sessions by default channel group) | devices (activeUsers by device category) | realtime (active users now, by country). |
range | string | optional | Preset date range - 7d | 28d | 90d. Ignored for realtime. Default: 28d |
from | string | optional | Explicit range start (YYYY-MM-DD, with to; overrides range, max 366 days). |
to | string | optional | Explicit range end (YYYY-MM-DD, with from). |
Response
object — Normalized report - {report, range?, labels?, series?, rows?, totals?, deltas?}. overview → totals + deltas (fractional change vs previous period, null when no previous data); timeseries → labels (ISO days) + series; pages/sources/devices → rows [{label, value, title?}]; realtime → totals.activeUsers + rows by country.
Sample request
{
"report": "overview",
"range": "28d"
}The transitional op route: status / set are the generic admin CRUD over setup/analytics (validated: property_id normalised, measurement_id G-..., sa_email / verified_at / version platform-written) with the service-account JSON through /secure/secret as ga4_service_account; properties / verify are project/analytics/properties and project/analytics/verify. Configure the store's Google Analytics 4 connection. op selects the operation - status (default): setup doc + has_credentials/configured flags (never the credential); set: merge-write property_id / enabled / measurement_id and/or store the service-account JSON (goes to the encrypted secret store under ga4_service_account; only the extracted sa_email lands in the setup doc; pass null to remove); properties: list the GA4 properties the stored service account can see (property picker); verify: run a minimal report against the property - success stamps verified_at, failure returns 200 with {ok: false, reason, message} (reason: invalid_credentials | permission_denied | invalid_property | api_disabled | ...).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
op | string | optional | Operation - status | set | properties | verify. Default: status |
property_id | string | optional | op=set - GA4 property id, 123456789 or properties/123456789 (normalized on write). Null/empty removes it. |
enabled | boolean | optional | op=set - master switch; reports are refused while false. |
measurement_id | string | optional | op=set - GA4 web stream measurement id (G-XXXXXXXXXX); reserved for future gtag injection, not used for reading. Null/empty removes it. |
service_account | string | optional | op=set - the raw service-account JSON (as a string). Stored AES-encrypted in the store's secret store; never returned. Null removes the stored credential. |
Response
object — status/set → {setup, has_credentials, configured}; properties → {properties: [{property, display_name, account?}]}; verify → {ok, verified_at?, reason?, message?}.
Sample request
{
"op": "set",
"property_id": "123456789",
"enabled": true
}Run one curated platform-statistics report for the store (page views, approximate visitors, bandwidth, cache/error rates, API read/write counts). Zero setup - the data is the platform's own server-side traffic record (tenant hosting + core API), so it works for every store and sees what adblockers hide; it is NOT Google Analytics (that stays /admin/analytics/*). Reports read the curated BigQuery daily aggregates - data is batch-fresh (hourly), the payload's fresh_to carries the honest freshness stamp. Views and visitors count pages a browser actually rendered (the render beacon every hosted page carries); every other page request is totals.bots, with bot_share its share of all page requests. Responses are cached ~10 min per (store, report, range). Typed failures carry a reason in the error body: not_provisioned (this environment's statistics pipeline is not set up - the UI shows a quiet empty state) | query_failed | quota_exhausted | unavailable.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
report | string | required | overview (totals + deltas vs the previous period, bot_share, fresh_to) | timeseries (daily views/visitors/bytes) | pages (top 10 paths - views, visitors, bytes, cache rate) | referrers (top referer hosts) | ops (API reads/writes by route group, hook deliveries ok/fail, bandwidth split html/assets/api) | summary (the compact widget payload - overview totals + a sparkline series). |
range | string | optional | Preset date range - 7d | 28d | 90d. Default: 28d |
from | string | optional | Explicit range start (YYYY-MM-DD, with to; overrides range, max 366 days). |
to | string | optional | Explicit range end (YYYY-MM-DD, with from). |
Response
object — Normalized report - {report, range, fresh_to?, bot_share?, totals?, deltas?, labels?, series?, rows?, pages?, ops?, spark?}. overview/summary → totals {views, visitors, bots, bytes_out, cache_rate, error_rate, api_calls} + deltas (fractional change vs previous period, null when no previous data); timeseries → labels (ISO days) + series (views/visitors/bytes); pages → pages [{path, views, visitors, bytes_out, cache_rate}]; referrers → rows [{label, value}]; ops → ops {groups, hooks, bytes}; summary additionally → spark {labels, views, visitors, bytes}.
Sample request
{
"report": "overview",
"range": "28d"
}The transitional op route - its replacement is the generic admin CRUD over setup/shipping + setup/delivery (validated) with the credentials through /secure/secret as carrier_{tag}. Carrier + delivery configuration (plans/carriers/README.md 3.2). op get (default) answers {setup - the setup/shipping doc (sender, label, cod, vat, auto_status, auto_create, networks, carriers - per carrier {enabled, sandbox, verified_at, account, defaults}), delivery - the public setup/delivery doc {methods}, carriers - store.carriers, has_credentials - per carrier, modules - the installed provider modules (module:{id} carriers) with their provides.carrier entry, mock - whether the test carrier mock exists here (Demo only)}; credentials are NEVER returned. op set merge-writes: setup (the shared blocks; null removes one), carrier + carrier_setup (one carrier block - account / defaults refuse credential-looking keys; null removes the block) + credentials (write-only into the encrypted secret store as carrier_{carrier}; null removes; a change clears verified_at), and/or methods (replaces setup/delivery.methods - validated: slug id, carrier tag, kind home | point | box, price >= 0 - and forces public: true). A provider module is configured on its module page, not here.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
op | string | optional | get | set. Default: get |
setup | object | optional | op=set - {sender?, label?, cod?, vat?, auto_status?, auto_create?, networks?}. |
carrier | string | optional | op=set - the built-in carrier tag the carrier_setup / credentials belong to (mock on Demo). |
carrier_setup | object | optional | op=set - {enabled, sandbox?, account?, defaults?, webhook?}. |
credentials | object | optional | op=set - the carrier's secret fields (write-only). |
methods | array | optional | op=set - the full delivery catalog, DeliveryMethod[] {id, carrier, service, kind, name, price, prices?, free_from?, countries?, cod?, point_types?, widget?, enabled?}. |
Response
object — {setup, delivery, carriers, has_credentials, modules, mock, supported}
Sample request
{
"op": "set",
"carrier": "mock",
"carrier_setup": {
"enabled": true
}
}One cheap authenticated call to the carrier (plans/carriers 3.4). Success stamps setup/shipping.carriers[carrier].verified_at; bad credentials answer 200 {ok - false, reason - invalid_credentials, message}. A module:{id} carrier runs the module's own verify point. Built-in carriers other than mock answer 501 carrier_unsupported until their phase ships. Back-office: a staff role or the project.shipping grant (403 role_forbidden), write scope on setup/shipping.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
carrier | string | required | The carrier tag (a built-in, module:{id}, or mock on Demo). |
Response
object — {ok, message?}
Errors
409 carrier_not_configured | carrier_disabled; 501 carrier_unsupported; 400 carrier_unknown; 403 role_forbidden | module_token_forbidden.
Sample request
{
"carrier": "mock"
}Create the order's shipment at its carrier (plans/carriers 3.3/3.4). The order must be ready / confirmed / active and have a complete delivery address; an order without a shipping choice takes method (a setup/delivery method id) or carrier, else the store's single enabled method. Idempotent: an order that already has service_data.id is 409 shipment_exists (a transaction re-checks it). Writes shipping.service_data {id, tracking, tracking_url, parcels, created, status - created, next_check} + a tracking ledger record; the status machine then moves ready → shipping. Never sets order.status directly. Back-office: a staff role or the project.shipping grant (403 role_forbidden), write scope on the order.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
order | string | required | The order id. |
method | string | optional | A setup/delivery method id for an order without a shipping choice. |
carrier | string | optional | A carrier tag for an order without a shipping choice. |
Response
object — {order, shipment: {id, tracking, tracking_url, status}}
Errors
409 shipment_exists | order_state | carrier_not_configured; 422 address_incomplete; 404 order_not_found; 502 carrier_error | module_failed; 403 role_forbidden | module_token_forbidden.
Sample request
{
"order": "Hk8yR64vJf2bz33yc44B"
}The shipment label (plans/carriers 3.3). Rendered by the carrier once, stored in the store's bucket under store/{sid}/shipping/{order}/{shipment}.{pdf|zpl} (never the files tree - it carries the recipient's PII) and re-read afterwards (reprint renders again). One order → {format, content_base64}; orders (≤ 50) → a ZIP of the labels {format - zip, orders, content_base64}. Stamps label.printed. Back-office: a staff role or the project.shipping grant (403 role_forbidden), write scope on every order.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
order | string | optional | One order id. |
orders | array | optional | Several order ids (bulk print). |
format | string | optional | pdf | zpl (default setup/shipping.label.format, else pdf). |
reprint | boolean | optional | Ask the carrier again instead of re-reading the stored label. |
Response
object — {order, format, content_base64} | {format: zip, orders, content_base64}
Sample request
{
"order": "Hk8yR64vJf2bz33yc44B"
}Cancel the shipment at the carrier and clear it from the order (plans/carriers 3.4); the ledger keeps the history and the status machine moves shipping back to ready. Refused (409 cancel_refused) once the carrier has the parcel. A carrier without a cancel API answers 501 cancel_unsupported unless force (then only our record is cleared - cancel it in the carrier portal). Back-office: a staff role or the project.shipping grant (403 role_forbidden), write scope on the order.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
order | string | required | The order id. |
force | boolean | optional | Clear our record even when the carrier cannot cancel. |
Response
object — {order, cancelled}
Sample request
{
"order": "Hk8yR64vJf2bz33yc44B"
}Pull the carrier's tracking events now (the order card's Refresh; plans/carriers 6) and apply them through the one tracking sink - new events append to the ledger, the newest becomes shipping.service_data.status, the status machine maps it (shipping → delivery → completed / returned), the poll watermark resets. Back-office: a staff role or the project.shipping grant (403 role_forbidden), write scope on the order.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
order | string | required | The order id. |
Response
object — {order, events, added, status, changed}
Sample request
{
"order": "Hk8yR64vJf2bz33yc44B"
}Close-of-day / handover manifest for the carriers that need one (plans/carriers 3.4) over the given orders' shipments of that carrier; stamps shipping.service_data.manifest. 501 manifest_unsupported for a carrier that needs none. Back-office: a staff role or the project.shipping grant (403 role_forbidden), write scope on every order.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
carrier | string | required | The carrier tag. |
orders | array | required | The order ids (≤ 50). |
Response
object — {id, shipments, content_base64}
Sample request
{
"carrier": "mock",
"orders": [
"Hk8yR64vJf2bz33yc44B"
]
}List the GA4 properties the stored service account (ga4_service_account in the secret store) can see - the property picker of the onboarding dialog. Without a stored credential the list is empty (not an error). Owner or master (403 role_forbidden).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
Response
object — {properties: [{property, display_name, account?}]}
Sample request
{
"store": "instore"
}Run a minimal report against the configured property to prove the credential + access - success stamps setup/analytics.verified_at, failure returns 200 with {ok: false, reason, message} (reason: not_configured | invalid_credentials | permission_denied | invalid_property | api_disabled | ...). Runs while the integration is still disabled (onboarding). Owner or master (403 role_forbidden).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
Response
object — {ok, verified_at?, reason?, message?}
Sample request
{
"store": "instore"
}The store's web hosting status - provisioned?, the site key (the owner's own _drop TXT value for custom domains), whether the site is published, the server that owns the store's custom domains and the canonical tenant URLs per env. Never the serving token or hook key (the fleet reads those through /system/tenant/resolve as master). A staff role or the project.hosting grant (403 role_forbidden) and read scope on setup/web.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
Response
object — Returns {sid, provisioned, site_key, published, custom_host, urls - {prod, demo}}.
Sample request
{
"store": "instore"
}Provision the store for the tenant web server (idempotent - new stores get it on creation; this is the manual / repair path): finds-or-mints the per-store serving token, the hook custody and the site key. Answers the hosting status - no secret. A staff role or the project.hosting grant (403 role_forbidden) and write scope on setup/web (the members' web: write capability).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
Response
object — Returns the hosting status {sid, provisioned, site_key, published, custom_host, urls}.
Sample request
{
"store": "instore"
}Rotate the store's hosting secrets - token (the serving token; the tenant server self-heals on its first 401), hook (the cache-push hook key; the managed setup/hooks entries follow) and / or site_key (breaks every custom-domain _drop TXT binding until DNS is updated - the console warns first). Only a new site_key is answered. A staff role or the project.hosting grant (403 role_forbidden) and write scope on setup/web.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
what | list | required | Any of token | hook | site_key. |
Response
object — Returns {sid, rerolled - the list, site_key? - the new value when rerolled}.
Sample request
{
"what": [
"site_key"
]
}Read or pin the server that serves the store's custom domains (the custody pin - the ONLY way a store's custom domains move off the platform fleet; stores migrate one at a time). Without host / clear: the current pin (source store | default) with the fleet default. With host (+ optional ip / ip6 the DNS instructions advertise; omit for a CNAME-only target): pin it - the managed setup/hooks entries are rewritten in the same call. clear: true returns the store to the fleet. A staff role or the project.hosting grant (403 role_forbidden) and write scope on setup/web.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
host | string | optional | The server's hostname to pin. |
ip | string | optional | A record advertised for that host. |
ip6 | string | optional | AAAA record advertised for that host. |
clear | boolean | optional | Remove the pin. |
Response
object — Returns {sid, host, ip, ip6, source - store | default, fleet - the platform default}.
Sample request
{
"host": "tenant-prod.inbs.cz"
}The store's reCAPTCHA setup for the per-form captcha gate - the page runtime switch, the public site key, whether the secret (recaptcha_secret in the secret store) is set, the hosts to register on the Google key (the custom domains in setup/web plus the platform hosts), the gate's last store-side problem (setup/captcha_health - no_secret | bad_secret | over_quota | unreachable | not_score_key | domain) and the ids of the protected forms (<= 50). Never the secret. A staff role or the project.captcha grant (403 role_forbidden) and read scope on setup/web.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
Response
object — {sid, enabled, provider, site_key, hide_badge, secret_set, hosts, health - {last_error?, last_error_at?, last_hostname?, last_ok_at?} | null, protected_forms}
Sample request
{
"store": "instore"
}Set the store's reCAPTCHA v3 (score) key - merges site_key, enabled (the page runtime) and hide_badge into setup/web.captcha; secret is stored write-through into the secret store as recaptcha_secret (its key effect stamps setup/web.captcha.secret_set), secret: null removes it. Every field is optional. Answers the status (never the secret). Owner or master (403 role_forbidden), write scope on setup/web; 400 invalid_site_key / invalid_secret.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
site_key | string | optional | The public site key of a Score-based (v3) reCAPTCHA key; null / empty clears it. |
secret | string | optional | The key's legacy secret (Cloud console - key - Integration - Use Legacy Key); null removes it. |
enabled | boolean | optional | Compose the page runtime (needs a site key). Forms are protected per form (FormEntity.captcha), never by this switch. |
hide_badge | boolean | optional | Hide Google's floating badge. Nothing is printed in its place - the site states its use of reCAPTCHA itself (privacy policy, a line in the form template). |
Response
object — The status (see /project/captcha/status).
Sample request
{
"store": "instore",
"site_key": "6Lxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"enabled": true
}Rebuild a form's per-status summary counters from count aggregations (the trigger-maintained counters are at-least-once and can drift on retries). Five count() reads - the total and one per non-new status; new is the remainder, exactly as unknown / absent statuses are counted. One update of the form doc. Scope: write on store/{sid}/forms/{form}. 404 when the form doc does not exist.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
form | string | required | The form id (slug). |
Response
object — Returns {form, summary - {new, open, done, unresolved, closed}} - the map written to the form doc.
Sample request
{
"form": "contact"
}Download one file a form submission uploaded (.dev/plans/forms-v2.md Part A). Uploaded files are PRIVATE objects with no public URL - this is the only read path; the admin streams it through its proxy. Answers the bytes as an attachment (Content-Disposition, the stored content type, no-store). Scope: read on the record. 404 for a missing record, a key that holds no upload, or a missing object.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
form | string | required | The form id (slug). |
id | string | required | The record id. |
key | string | required | The payload key holding the upload ref. |
index | number | optional | For a files variable - which entry of the array (default 0). |
Response
file — The file bytes (Content-Type = the stored type, Content-Disposition attachment).
Sample request
{
"form": "contact",
"id": "abc123",
"key": "cv"
}The console's pinned side menu and Overview (dashboard) widgets of the store, with the vocabularies to write them with - the page routes a pinned item may link, the item kinds, the widget kinds with their discriminators, the day windows. Both docs are SHARED by Demo and PROD (one console settings store). overview: null = never customized (the console shows the default widgets). Listing columns are not here - they are setup/data collections (registerCollection). A staff role or the project.console grant (403 role_forbidden).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
Response
object — Returns {menu - {groups}, overview - {widgets, days} | null, pages, itemKinds, widgets - [{kind, discriminator?, extra?, limit?}], days, shared - true, updated - {menu?, overview? - {at, by}}}.
Sample request
{
"store": "instore"
}Replace the store's console Overview widgets (whole document - read it first, edit, write the full list back). Widgets {id?, kind, label?, size? - tile|half|full, …} (at most 24); the kind's discriminator must be one of console/read's values (tile.metric, chart.metric, ga4-tile.metric, ga4-chart.report, stats-tile.metric, stats-chart.chart, form-records.form, module.module + widget); limit 1-50 on the list kinds; days 1-365 (7 / 30 / 90 are the console's options - others warn). 400 console_invalid {errors, warnings}. Shared by Demo and PROD. Owner / master, or the project.console grant (403 role_forbidden); writes an activity row.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
widgets | list | required | The full widget list: [{id?, kind, label?, size?, metric? | report? | chart? | form? | module?, widget?, status?, limit?}]. |
days | number | optional | The shared metric window in days (default 30). |
Response
object — Returns {ok - true, overview - the stored {widgets, days}, warnings, shared - true}.
Errors
400 console_invalid {errors, warnings}; 403 role_forbidden | store_mismatch | module_token_forbidden; 503 console_unavailable.
Sample request
{
"widgets": [
{
"kind": "tile",
"metric": "orders"
},
{
"kind": "activity",
"limit": 10
}
],
"days": 7
}List the store's services (.dev/plans/modules/services.md D1) - the service docs (no code), each with its route health from meta/health and has_secrets (declared secret name -> whether a value is stored; never a value). enabled = the store's plan has the services feature (the console shows the gate). Read scope on store/{sid}/services; a staff role or the project.services grant (403 role_forbidden).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
Response
object — {services: [ServiceEntity + {health?, has_secrets}], enabled}
Errors
403 role_forbidden | module_token_forbidden | Path not in token scope.
One service - the doc, its versions newest first (WITHOUT code unless version asks for one: a number, or live for the published one), the route health, has_secrets and url (the live address https://{demo-}{sid}.inbs.dev/fn/{id}/). 404 service_not_found / version_not_found. Read scope on store/{sid}/services/{id}.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
id | string | required | The service id (3-32 chars, a DNS label). |
version | string | optional | A version number, or live - that version's entry carries its code. |
versions | number | optional | How many versions to list (default 20, max 100). |
Response
object — {service, versions: [{version, hash, size, note?, check, created_at, created_by, code?}], health?, has_secrets, url}
Sample request
{
"id": "echo",
"version": "live"
}Create (a new id) or update a service's settings - name, routes [{method GET|POST|PUT|DELETE, path with :params, guarded?, identity?}], egress hosts, secret NAMES, env (non-secret UPPER_CASE strings), origins (exact https:// origins allowed cross-origin) and enabled. New services start {routes: [], egress: [], secrets: [], env: {}, origins: [], enabled: true, published: 0}. A dropped secret name takes its stored value with it. Fires the services hook. 400 invalid_service {problems}. Write scope on store/{sid}/services/{id} and the tier feature services (403 tier_required).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
id | string | required | The service id - immutable, part of the /fn/{id}/ address. |
name | string | optional | Display name (defaults to the id on create). |
routes | list | optional | At most 20 routes: [{method, path, guarded?, identity?, access?}]. access - who may call it on /fn (.dev/plans/modules/service-access.md): public (absent, anyone) | site (this project's pages, the listed origins and non-browser callers - not authentication) | signed-in (a visitor identity token; implies identity) | private (never on /fn - console, agent and MCP only). |
egress | list | optional | Exact host names drop.fetch may reach (HTTPS only, at most 20). |
secrets | list | optional | Declared secret names (UPPER_CASE, at most 20). Values are set with services/secrets. |
env | object | optional | Non-secret settings (drop.env), at most 8 KiB. |
origins | list | optional | Exact https:// origins that may call /fn cross-origin (CORS) and that pass the site access check (at most 10). |
enabled | boolean | optional | false stops every /fn call of the service (404). |
Response
object — {service}
Errors
400 invalid_service {problems} | invalid_id; 403 tier_required | role_forbidden.
Sample request
{
"id": "echo",
"name": "Echo",
"routes": [
{
"method": "GET",
"path": "/ping"
}
]
}Check code without publishing (D13): the static checks here (size <= 512 KiB, UTF-8, the declarations) and - when those pass - the dry run on the check runner (parse, web keys within the declared routes, every GET route run with recording fakes). A runner that cannot be reached is the error finding check_unavailable. Nothing is written. Write scope + the tier feature.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
id | string | required | The service id. |
code | string | required | One ES module: export default {web: {'<path>': handler}} or a single function. |
Response
object — {report: {ok, findings: [{level: error|warn|info, code, message, route?}], checked_at}}
Check, then store code as the next version and make it live (D13) - versions/{n} (n = the highest + 1) and published = n in ONE transaction. An error finding (check_unavailable included) refuses the publish: ok false with the report, answered 200, nothing written. Fires the services hook. Write scope + the tier feature.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
id | string | required | The service id. |
code | string | required | The code (at most 512 KiB, no imports). |
note | string | optional | A version note (at most 500 chars). |
Response
object — {ok, version?, report}
Sample request
{
"id": "echo",
"code": "export default {web: {ping: () => ({pong: true})}}",
"note": "first version"
}Make an existing version live again (published = version). 404 version_not_found. Fires the services hook. Write scope + the tier feature.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
id | string | required | The service id. |
version | number | required | The version number to make live. |
Response
object — {service}
Set or clear a service's secret VALUES (D11) - stored write-only as service_{id}_{NAME} in the store's secrets and never answered anywhere (/secure/secret withholds them too). Only declared names (400 secret_undeclared); null clears. Bumps the service's secrets_rev, which fires the services hook. MANAGERS ONLY - the master or owner role (403 manager_only); agents and MCP declare names but never set values.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
id | string | required | The service id. |
values | object | required | {NAME: string | null}. |
Response
object — {has_secrets: {NAME: boolean}}
Errors
400 secret_undeclared | secret_value_invalid; 403 manager_only | tier_required.
A service's log lines newest first (7-day TTL) - one line per call (route, status, ms, host site|core|studio, level, message, version) plus the code's own drop.log lines. level narrows to error or warn, before (epoch ms) pages; next is the cursor of the following page. Read scope.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
id | string | required | The service id. |
limit | number | optional | At most 100 (default 50). |
before | number | optional | Only lines older than this (epoch ms) - the previous answer's next. |
level | string | optional | error | warn. |
Response
object — {logs: [{at, route, status, ms?, host, level, message, version?}], next?}
Delete a service with every version, its kv state, logs, health and its secret values. Fires the services hook (delete). Write scope; works without the tier feature (cleaning up is always allowed).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
id | string | required | The service id. |
Response
object — {removed: true}
List the project's backups (manifests, newest first) - each with its parts, size, kind (manual | pre_restore | imported; scheduled on backups made before scheduling was removed), status and progress. Also the per-store total size + count (one aggregate read), the running job and enabled (the plan has the backups feature).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
limit | number | optional | 1-50, default 20 |
cursor | string | optional | The last id of the previous page |
kind | string | optional | Filter by kind |
Response
object — {items: [BackupEntity], next?, total_size, count, running?: {job, op}, enabled}
Errors
403 role_forbidden | module_token_forbidden | Path not in token scope; 400 backup_unsupported_store (incore)
Per-part document counts for the create dialog - count / aggregate reads only (and the folders' summaries for the file bytes). Also the part catalog.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
Response
object — {parts: {[part]: {docs, file_bytes?, allowed - false when the token scope cannot read the part}}, catalog}
Errors
403 role_forbidden | module_token_forbidden | Path not in token scope; 400 backup_unsupported_store (incore)
Take a backup: a point-in-time archive of the named parts (project, web, files, forms, catalog, content, urls, services, commerce, customers, form_records; content takes a {collections, locales} selection). Ledgers (orders, invoices, stock, credits, payments, module records) are never captured. Answers at once with the queued backup; poll backup/status. One job per project at a time.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
name | string | required | Up to 80 characters |
note | string | optional | Up to 500 characters |
parts | array | required | Part keys, or {key, selection?} objects |
via | string | optional | console | agent | mcp (the activity label) |
Response
object — {id, seq, status: queued}
Errors
403 role_forbidden | module_token_forbidden | Path not in token scope; 400 backup_unsupported_store (incore); 403 tier_required; 409 backup_busy {job, op}; 400 backup_parts_required | backup_part_unknown | backup_name_required
One backup's manifest (progress while it runs), or - with job - a restore / preview job (its preview, result or error).
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
id | string | optional | The backup id |
job | string | optional | A preview / restore job id |
Response
object — BackupEntity | BackupJobEntity
Errors
403 role_forbidden | module_token_forbidden | Path not in token scope; 400 backup_unsupported_store (incore); 404 backup_not_found | backup_job_not_found
Rename a backup, change its note, pin or unpin it (a pinned backup is skipped by a multi-delete). Owners and admins.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
id | string | required | The backup id |
name | string | optional | The new name |
note | string | optional | The new note (empty clears it) |
pinned | boolean | optional | Pin / unpin |
Response
object — BackupEntity
Errors
403 role_forbidden | module_token_forbidden | Path not in token scope; 400 backup_unsupported_store (incore); 409 backup_deleting
Delete backups: their archives go, and so do the stored files no remaining backup references (a sweep job). A multi-delete skips pinned backups; a running backup cannot be deleted. Owners and admins.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
id | string | optional | One backup id |
ids | array | optional | Up to 20 backup ids |
Response
object — {deleting: [id], skipped: [id]}
Errors
403 role_forbidden | module_token_forbidden | Path not in token scope; 400 backup_unsupported_store (incore); 400 backup_id_required
The document paths of one part of a backup, from its index (no bodies): paged, filterable by path prefix.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
id | string | required | The backup id |
part | string | required | The part key |
prefix | string | optional | Path prefix (relative to the project) |
cursor | string | optional | The last path of the previous page |
limit | number | optional | 1-500, default 200 |
Response
object — {part, items: [{path, hash}], total, next?}
Errors
403 role_forbidden | module_token_forbidden | Path not in token scope; 400 backup_unsupported_store (incore); 400 backup_part_missing
One document of a backup with its live version (both as the restorable fields, typed JSON) and same - for the console's diff view. Read scope on the document's path too.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
id | string | required | The backup id |
doc | string | required | The document path relative to the project (data/cs_CZ/blog/a; promo:coupons/x) |
Response
object — {part, path, backup, live, same, module?}
Errors
403 role_forbidden | module_token_forbidden | Path not in token scope; 400 backup_unsupported_store (incore); 404 backup_doc_missing
Start a restore preview: every document of the chosen parts is classified against the backup (create / update / delete / same / skipped), with warnings and blockers. Read-only; poll backup/status {job}. Owners and admins, unrestricted token scope.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
id | string | required | The backup id |
parts | array | optional | {key, mode: replace | merge} (default: every part of the backup, its default mode) |
Response
object — {job, status: queued}
Errors
403 role_forbidden | module_token_forbidden | Path not in token scope; 400 backup_unsupported_store (incore); 403 tier_required | scope_restricted; 409 backup_not_ready | backup_format_unknown; 400 backup_part_missing | backup_mode_unsupported
Apply a finished preview: a backup of the current state of the same parts is taken first (Undo = restoring it), then only the documents that differ are written - quietly (no per-document webhooks, mails or revisions; one bulk event at the end). Documents changed since the preview are skipped as conflicts. confirm = the backup's name. Owners and admins.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
job | string | required | The preview job id |
confirm | string | required | The backup's name |
Response
object — {job, status: queued}
Errors
403 role_forbidden | module_token_forbidden | Path not in token scope; 400 backup_unsupported_store (incore); 403 tier_required | scope_restricted; 409 backup_preview_required | backup_preview_expired | backup_restore_blocked | backup_busy; 400 backup_confirm_mismatch
Restore ONE document of a backup (no preview, no pre-restore backup - a content document's live version goes into its revisions, any other's into the job's undo). Owners and admins.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
id | string | required | The backup id |
doc | string | required | The document path relative to the project (data/cs_CZ/blog/a; promo:coupons/x) |
Response
object — {job, status: queued}
Errors
403 role_forbidden | module_token_forbidden | Path not in token scope; 400 backup_unsupported_store (incore); 403 tier_required | scope_restricted; 409 backup_busy
Package a ready backup as ONE zip (its manifest, every part's chunks + indexes, and the stored files it references under blobs/) - a queued job; poll backup/status {job}, whose answer then carries download.url, a signed link valid 15 minutes (a fresh one on every read). Owners and admins.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
store | string | optional | Preferred in header, but store ID can be also used in request body. |
id | string | required | The backup id |
Response
object — {job, status: queued}
Errors
403 role_forbidden | module_token_forbidden | Path not in token scope; 400 backup_unsupported_store (incore); 409 backup_not_ready