# Common errors and fixes

> Look up the cause and fix from a symptom or error code (402, 409, 451, and more). Covers issues hit both by the people building the site and by the owners using the dashboard.

URL: https://siteon.dev/docs/troubleshooting

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.

## Values don't appear (the HTML text stays as is)

1. **Wait a moment.** With Siteon hosting, changes appear within 15 seconds. With the relay and `data-embed`, the public JSON is cached for 60 seconds, so it can take up to 1 minute
2. **Check the relay's environment variables.** If either `CMS_SITE` or `CMS_ORIGIN` is missing, the relay does nothing
3. **Check with a GET request.** If `curl -sD - -o /dev/null https://example.com/ | grep -i sitekit` prints `x-sitekit-rendered: 1`, the relay is working. `curl -I` (HEAD) does not get this header, so don't take its absence as a sign the relay isn't working
4. **Check the key spelling.** `data-cms="hours.days"` is the combination of `groups[].key` and `fields[].key` in schema.json
5. **If a list is empty, check the publish status.** Draft articles are not listed

## Part of the page breaks, or there are extra closing tags

A marked element contains an element with the same tag name. `<div data-cms="x"><div>…</div></div>` breaks. Use a different tag inside, such as `<span>`.

## Attributes aren't set

- Did you put two `data-cms-attr` entries on one element (`href:url data-color:color`)? With two, neither is set. Split them across elements
- Uppercase tag names or attribute names are ignored
- Attributes that take a URL won't accept values starting with `javascript:` and similar schemes

## The news section stays empty (not even the "nothing yet" message)

If `data-cms-filter` leaves zero items, neither rows nor `data-cms-empty` are shown. `data-cms-empty` only checks whether the list itself is empty. On tag pages, the relay uses the already-filtered list ([Editable areas](https://siteon.dev/docs/cms#tags)).

## The article page's `<title>` or OGP shows site-wide values, or `<title>` appears twice

Put a `<template data-cms-entry="news">` in `<head>` as well, and write `<title>` and `<meta>` inside it. Add `data-cms-fallback="news"` to the static `<title>` ([A page per article](https://siteon.dev/docs/cms#a-page-per-article)).

## Text colors or tag colors don't apply

Your site's CSS doesn't define the six colors such as `.sk-c-red`, or `.sk-tag[data-color=…]`. The attributes are output, but there are no rules for them. Define all six colors from the start.

## Values are inserted twice, or list values look wrong

On a site that uses the relay, the script also has `data-embed`. Embedding can't run twice. Remove `data-embed`.

## "Please check your input" appears (`invalid_request`)

- A `required` field is empty
- A value breaks a type constraint (`text` is limited to 200 characters, `email` is not in email format, and so on)
- Check that each input's `name` matches `fields[].key` in schema.json. Fields with errors get `aria-invalid="true"`

## Submitting returns `not_found`

The value of `data-sitekit-form="…"` doesn't match `forms[].key` in schema.json, or the site ID in `data-site` is wrong.

## Clicking submit shows "Could not send"

- If the browser's developer tools show a CORS error, or the response is `403 origin_not_allowed`, that page's origin isn't in the site's allowed origins (`www` or not, scheme and port must match exactly). This submission was not saved. See the next section
- `rate_limited` means more than 20 submissions in 10 minutes from the same source. Wait a while and it will go through

## Forms return 403 (`origin_not_allowed`)

The domain of the page with the form is not in the site's allowed origins. The submission is not saved and does not count toward your quota.

- In the dashboard, add the page's domain exactly as `location.origin` prints it in the console to "Settings" → "Domains and site name" → "Domains that may submit forms". `https://www.example.com` and `https://example.com` are different
- A Free site can only register development domains (`*.pages.dev` and similar). To add your own domain, move the site to a paid plan (`402 upgrade_required`)
- A custom domain on Siteon hosting is added automatically once it is "Active"

Full steps are in [Fixing 403 origin_not_allowed](https://siteon.dev/docs/domains#origin-not-allowed).

## Only one checkbox value arrives

When several inputs share the same `name`, only the last value is sent. Use separate keys (`topic_a`, `topic_b`).

## A submission was sent but never arrived

- **Is the honeypot (`_hp`) filled in?** Submissions with it filled in look successful on screen but are not saved. Add `autocomplete="off"` and `tabindex="-1"`, and place it off-screen
- If the submission is in the dashboard's inquiry list, it was saved. What didn't arrive is the notification (see the next section)

## Notifications don't arrive

Open the inquiry's details to see whether each notification succeeded. A ✗ means "the notification didn't get through", not "the inquiry is gone". Failed notifications can be resent.

- **Nothing is recorded** — That notification isn't configured. In the dashboard, go to "Settings → Notifications → each form", enter a destination, and turn it on
- **Slack** — If the channel was deleted or the app was removed, issue a new Webhook URL and enter it again
- **The thank-you email isn't sent** — It is sent only when the form has a `type: "email"` field and it has a value
- **You edited schema.json but the destination didn't change** — Destinations aren't set in schema.json. The dashboard settings are the source of truth

## GTM doesn't count form submissions

- Did you change the completion event name in the dashboard but leave the GTM trigger on the old name?
- On a site that uses the relay, setting both `data-gtm` and a GTM ID in the dashboard loads conflicting containers. Remove `data-gtm`
- Submit the form for real in GTM preview mode and check that the completion event appears in the event list

## Webhooks don't arrive or fail

Check the latest delivery results in the dashboard under "Settings → Advanced → Webhooks" (or `siteon webhooks list`).

- **You can't add one** — Is the site on a paid plan (not available on Free)? Is the URL `https://`, without an explicit port, an IP address, or an internal name like `localhost` or `.local` ([destination rules](https://siteon.dev/docs/webhooks#destination-rules))?
- **It redirects** — 3xx isn't followed and counts as a failure. Register the final URL instead
- **It times out** — Return 2xx within 10 seconds. Queue heavy work and answer first
- **The signature doesn't match** — Compute it over the body before parsing it as JSON, and use the whole secret including `whsec_` ([verifying the signature](https://siteon.dev/docs/webhooks#verifying-the-signature))
- **The same inquiry arrives twice** — That's a retry. Use `Idempotency-Key` to skip IDs you've already processed

## Preview pages show up in search results

List only your production hosts in the relay's `CMS_INDEXABLE_HOSTS` ([Hosting and delivery](https://siteon.dev/docs/hosting#keeping-out-of-search-cms_indexable_hosts)).

## Deploys stop

The CLI stops before uploading anything when the problem can be detected in advance. With `--json`, the code is in `error`.

| Code | Cause | Fix |
|---|---|---|
| `dev_publish_paused` (503) | Publishing on Free sites is temporarily paused by the operator (for example, against abuse) | Try again later. If it is urgent, move that site to a paid plan |
| `billing_inactive` (402) | Payment is 8 or more days overdue | Check the payment method under billing in the dashboard. Things resume once payment is confirmed |
| `deploy_too_large` / `too_many_files` (402) | Exceeds the plan's storage or file count | Remove unneeded files (source maps, original images, and so on), or upgrade the plan |
| `file_too_large` (413) | A single file exceeds 25 MiB | Host videos elsewhere and embed them. Shrink images |
| `file_type_not_allowed` (400) | A file type that can't be published (`exe`, `zip`, and so on) | Remove that file. Allowed extensions are in [Files you can deploy](https://siteon.dev/docs/hosting#files-you-can-deploy) |
| `reserved_path` (400) | A path starting with `admin/`, `v1/`, or `_sk/` | Rename the folder |
| `hosting_disabled` (409) | Hosting isn't enabled | Run `siteon hosting enable` first |
| `subdomain_required` (409) | The site ID can't be used as a subdomain (too short, a reserved word, contains a brand name, already in use, and so on; the reason is in `reason`) | `siteon hosting enable --subdomain another-name` |
| `subdomain_taken` / `subdomain_quarantined` (409) | Another site uses it, or it was released less than 90 days ago | Choose a different name |
| `subdomain_change_limit` (429) | The subdomain was changed more than 3 times this month | Wait until next month |
| `blob_busy` (409) | The server is cleaning up old files | The CLI waits and retries automatically. If it persists, wait a bit and run it again |
| `deploy_expired` / `blobs_missing` (409) | The upload timed out (24 hours) or was interrupted | Run `siteon deploy` again |
| `no_previous_deploy` / `deploy_not_retained` (409) | There is no version to roll back to, or it is past the retention period | Check retained versions with `siteon deploys` |
| `insufficient_scope` (403) | The key lacks `deploy:write` | Issue a new key with that permission in the dashboard |

## A custom domain can't be added or doesn't become active (coming soon)

| Code / status | Cause | Fix |
|---|---|---|
| `plan_required` (402) | The site is on Free | Move the site to a paid plan |
| `quota_hostnames` (402) | The plan's slots are used up (Lite 1, Standard 3, Business 10; a `www` host and its bare form count as one) | Remove a domain you don't use, or upgrade |
| `invalid_hostname` (400) | The domain is malformed | Enter it as `www.example.com` (no `https://`, no path) |
| `hostname_blocked` (400) | A name commonly used for impersonation | Use a different domain |
| `hostname_reserved` / `hostname_in_use` (409) | Another account has requested or uses it / another site in your account uses it | Request it after the deadline / remove it from that site |
| `too_many_domain_requests` (429) | Up to 5 requests a day | Request it the next day |
| `hosting_disabled` (409) | Hosting isn't enabled | Run `siteon hosting enable` first |
| Stays "Waiting for DNS" | The CNAME or TXT record is missing, mistyped, or not yet visible in DNS | Compare with the records on screen and check with `dig` |
| "Failed" | Not verified within 72 hours of the request | Add it again and replace the TXT record with the new value |

All meanings and fixes are in [Custom domain errors](https://siteon.dev/docs/domains#domain-errors) and [What the statuses mean](https://siteon.dev/docs/domains#status).

## The site still looks old after deploying

- **Wait 15 seconds**, then reload
- For CSS, JS, and images, you are seeing what the browser has cached (5 minutes for CSS and JS, 1 day for images). If your build adds hashes to file names, new files are delivered the moment you deploy
- Run `siteon deploys` to check that the live version (`*`) is the one you just deployed

## The site returns 404, 451, or 503 (hosting)

| Response | Meaning | Fix |
|---|---|---|
| `404` "Not published yet" | Hosting is enabled, but nothing has been deployed yet | `siteon deploy` |
| `404` (shared page) | No file exists at that path, or the site was deleted or the account closed | Check the path and file name (`/about` is `about.html` or `about/index.html`). For a custom 404 page, add `404.html` |
| `451` "This site is currently suspended" | The site was suspended after an abuse report (phishing, for example) or a scan | Check the email sent to the owner. Fix the issue, then appeal. You can still `deploy` while suspended |
| `503` | A temporary outage on the Siteon side | Wait a few seconds and reload |

Pages never stop being served with a `402`. Delivery continues even when payment is overdue or limits are exceeded.

## The dashboard or API returns 402

| Code | Cause | Fix |
|---|---|---|
| `upgrade_required` | You tried to register a production (custom) domain as an allowed origin on a Free site | Move that site to a paid plan, then register it ([What Free sites can register](https://siteon.dev/docs/domains#allowed-origins)) |
| `quota_exceeded` | A limit was reached (CMS item count, photo storage, AI uses, the Free plan's 50 form submissions a month, and so on) | Remove what you don't need, or upgrade the plan ([When you reach a limit](https://siteon.dev/docs/plans#when-you-reach-a-limit)) |
| `billing_inactive` | Payment is more than 8 days overdue, or the subscription has ended | Check billing in the dashboard |

Form submissions return `429 quota_exceeded` when the submission limit is reached (120% of the limit on paid plans). They are not saved.

## Existing articles look different

The body formatting (`**bold**`, `[text](URL)`, `{color:…}`, and auto-linking of bare URLs) also applies to articles written earlier. The most common case is `**` written as literal symbols turning into bold. Search the body text for `**`.

## Dashboard: can't sign in

After too many failed sign-in attempts, sign-in is locked for a while (5 attempts per 10 minutes per login ID and source, 50 attempts per 10 minutes per login ID). **It unlocks on its own after 10 minutes.** If it still fails, check for a wrong password or login ID, or whether you were removed from the site.

## Dashboard: changing the password signed out other devices

This is by design. When you change your password, other devices (such as a tablet at the shop) are signed out for security. Sign in again with the new password.

## Dashboard: can't upload videos or photos

- Only **MP4 (H.264)** videos are accepted. iPhone `.mov` (HEVC) files and HEIC photos can't be played or displayed in browsers, so they aren't accepted. Changing the file extension won't help
- On iPhone, go to "Settings → Camera → Formats → **Most Compatible**" so future photos and videos are saved as JPEG and MP4 (existing ones don't change)
- **Videos over 100 MB** aren't accepted. Upload them to YouTube or similar and paste the link into the video block's "External URL"

## Dashboard: pasting a link doesn't fetch its details

"Fetch details" on a link card reads the other page's `og:title` and similar tags. It can't fetch them if the page has no OGP tags, requires sign-in, blocks bots, doesn't respond within 5 seconds, or isn't HTML. This isn't a fault; enter the details by hand.
