Skip to content
Siteon
Contents Public API

Docs

Public API

Specs and response examples for the two endpoints you can use without logging in (published content as JSON, and form submissions).

Siteon is infrastructure for AI-built websites: it adds hosting, a CMS your client can edit, contact forms, analytics and control from AI agents (CLI and MCP) around static HTML built with AI or by hand, so it is ready to ship.

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.

MethodPathWhat it does
GET/v1/sites/<site>/content?locale=<locale>Published content (JSON)
POST/v1/sites/<site>/forms/<form>Form submissions
GET/sitekit.jsThe 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 locale is omitted, the primary locale is used. A locale not listed in locales in schema.json returns 400 {"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" }
}
KeyContents
valuesGroup values. Keys are <group>.<field>. If a translation is missing or empty, the primary locale's value is used
listsPublished 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 fieldsAbsolute URL of the photo
select fieldsThe stored value, plus <field>_label, and <field>_color only for options that have a color
tagsFor each collection with tags, the tags in use and their counts (most used first)
tags_metaColors of tags that have one. Tags without a color have no key at all
trackingGTM 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/contact

What to send

  • application/json or application/x-www-form-urlencoded. multipart is not accepted
  • The body can be up to 16KB
  • Send fields by their key in forms[].fields in 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.

FieldContents
_hpHoneypot. If it has a non-empty value, the submission is treated as a bot and not saved
idempotency_keyUp to 100 characters. A resend with the same key returns the original submission instead of creating a second one
attributionTraffic 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-responseTurnstile token

Success

SituationResponse
Accepted200 {"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_key200 {"ok":true,"id":"<original id>","duplicate":true,"gtm_event":"…"}
Sent as form-urlencoded and schema.json has success_url303 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":"…"}.

StatuserrorMeaning
400invalid_requestInvalid 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
403turnstile_failedThe Turnstile token is missing or can't be verified
403origin_not_allowedThe sending origin is not in the site's allowed origins. Not saved (how to fix)
404not_foundThe site or form doesn't exist
413payload_too_largeOver 16KB
429rate_limitedMore 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).

This page is also available as Markdown. For AI agents, pass llms.txt (contents) or llms-full.txt (full text).