Endpoints
Normally the script and the Cloudflare Pages relay call these for you, so you don't need to use them directly. Use them when you want to render content yourself or submit from a server.
| Method | Path | What it does |
|---|---|---|
GET | /v1/sites/<site>/content?locale=<locale> | Published content (JSON) |
POST | /v1/sites/<site>/forms/<form> | Form submissions |
GET | /sitekit.js | The script you add to your site |
The origin is https://cdn.siteon.dev (the same as the script's src).
The management API (creating sites, applying schema, listing inquiries, etc.) is available with an API key (stk_…). You'll usually use it through the CLI and MCP.
Published content (content)
curl -s "https://cdn.siteon.dev/v1/sites/hanaya/content?locale=en"- If
localeis omitted, the primary locale is used. A locale not listed inlocalesin schema.json returns400 {"ok":false,"error":"bad_request"} - A site that doesn't exist returns
404 {"ok":false,"error":"not_found"} - No login required, read-only, with
Access-Control-Allow-Origin: * cache-control: public, max-age=60(changes appear within 1 minute of publishing)- Only published articles are included (drafts are not)
Example response
{
"values": {
"hours.days": "Mon–Fri",
"hours.time": "10:00 - 19:00"
},
"lists": {
"news": [
{
"id": "a1b2c3d4e5f6g7h8i9j0",
"title": "New autumn menu",
"date": "2026-10-01",
"tags": ["autumn"],
"category": "new-items",
"category_label": "New items",
"category_color": "red",
"thumbnail": "https://cdn.siteon.dev/a/hanaya/…",
"body": [{ "type": "…" }]
}
]
},
"tags": { "news": [{ "tag": "autumn", "count": 3 }] },
"tags_meta": { "news": { "autumn": { "color": "gold" } } },
"tracking": { "gtm_id": "GTM-XXXXXXX" }
}| Key | Contents |
|---|---|
values | Group values. Keys are <group>.<field>. If a translation is missing or empty, the primary locale's value is used |
lists | Published articles in collections. Up to 100 of the newest items per list, ordered by sort in schema.json (or by the dashboard order if there is none) |
image fields | Absolute URL of the photo |
select fields | The stored value, plus <field>_label, and <field>_color only for options that have a color |
tags | For each collection with tags, the tags in use and their counts (most used first) |
tags_meta | Colors of tags that have one. Tags without a color have no key at all |
tracking | GTM ID (dashboard setting first, then schema.json) |
The article url (link to the article page) and the conversion of tags to {tag, url, color} are added by the Cloudflare Pages relay, not in this JSON.
Form submissions (forms)
curl -s -X POST -H 'content-type: application/json' \
-d '{"name":"Jane Smith","email":"[email protected]","message":"Can I make a reservation?"}' \
https://cdn.siteon.dev/v1/sites/hanaya/forms/contactWhat to send
application/jsonorapplication/x-www-form-urlencoded. multipart is not accepted- The body can be up to 16KB
- Send fields by their key in
forms[].fieldsin schema.json. Unknown keys are ignored - If the same key appears more than once, the last value is used
These fields can be sent without being defined in schema.json.
| Field | Contents |
|---|---|
_hp | Honeypot. If it has a non-empty value, the submission is treated as a bot and not saved |
idempotency_key | Up to 100 characters. A resend with the same key returns the original submission instead of creating a second one |
attribution | Traffic source, in the form {first, last} (a JSON string for form-urlencoded). Values over 500 characters are dropped. A malformed value doesn't make the submission fail |
cf-turnstile-response | Turnstile token |
Success
| Situation | Response |
|---|---|
| Accepted | 200 {"ok":true,"id":"…","gtm_event":"sitekit_form_submit"} |
| Caught by the honeypot (not saved) | 200 {"ok":true,"id":null,"gtm_event":"…"} (same shape as success, so bots don't notice) |
Resend with an idempotency_key | 200 {"ok":true,"id":"<original id>","duplicate":true,"gtm_event":"…"} |
Sent as form-urlencoded and schema.json has success_url | 303 to that URL (only within the site's allowed origins) |
gtm_event is the completion event name set in the dashboard.
Errors
All errors have the shape {"ok":false,"error":"…"}.
| Status | error | Meaning |
|---|---|---|
| 400 | invalid_request | Invalid input. errors lists the keys of the invalid fields (values are not returned). A body that can't be parsed as JSON also returns this |
| 403 | turnstile_failed | The Turnstile token is missing or can't be verified |
| 403 | origin_not_allowed | The sending origin is not in the site's allowed origins. Not saved (how to fix) |
| 404 | not_found | The site or form doesn't exist |
| 413 | payload_too_large | Over 16KB |
| 429 | rate_limited | More than 20 submissions in 10 minutes from the same source |
Submitting from a browser on another origin (CORS)
For submissions from a browser, the sending origin must be in the site's allowed origins. Submissions without an Origin (curl, servers) are accepted regardless of the allowed origins. Cookies are not used.
Submissions from an Origin not in the allowed origins are not saved and return 403 origin_not_allowed (they do not count toward your quota either, so other people's sites can't use your form and consume it). No CORS headers are returned, so a browser sending Content-Type: application/json stops at the pre-flight check before sending. How to register origins and fix this is in Custom domains and allowed origins. When you reach a limit, the response is 429 quota_exceeded (Plans and limits).