Putting a site live
Spaicely hosts what the AI built; it does not build or template anything. A new version is the full list of files with their SHA-256 (sites.deploy); the answer names the files we do not have yet, which go up with sites.files.put. An AI with the site as a folder on disk calls sites.upload_url and sends one tar.gz instead. Nothing is visible until sites.publish, which switches all visitors at once and can roll back.
We keep the files exactly as uploaded; sites.files.get returns them that way. Visitors get an optimised copy: HTML without comments and indentation, CSS and JavaScript minified, large JPEG and PNG images recompressed and scaled to at most 2560 pixels. A file that would not get smaller goes out as it is. sites.versions shows the savings and hints, each with a fix: images without alt or without a size, and what the site still lacks, from sitemap.xml, robots.txt and llms.txt to a page without its own title, description, canonical link or Open Graph tags.
What we expect of every site, with examples of the head of a page, the sitemap, link previews, structured data and more than one language, is in the skill site-building (also in Dutch, German and French). Give it to the AI that builds the site, or add it to Claude Code as a skill.
How to run a site once it is built (versions and rollback, changing one block of a page, connecting the domain, DNS, mail forwarding, people and roles, approval, handover, the hosting plan) is in the skill site-management (also in Dutch, German and French). The Claude Code plugin brings both skills and the domain search guide.
What the AI may do on its own
Reading is always allowed. For every kind of action that changes something (building, publishing, domain and DNS, forms and shop, visitor data, mail, buying and plans, people and roles, the domain at the registry, deleting, billing details) the person chooses self, ask or never at app.spaicely.com/ai; an account owner can set a minimum for everyone. account.whoami returns these settings as ai_permissions. By default buying, people, mail, the registry, deleting and billing details are ask.
A call whose kind is set to ask does not run. It answers with approval_required, an approval id and a url for the person, who approves in Spaicely with a passkey or a recent sign-in, optionally with "don't ask me again" for that kind. We then run the call exactly as it was sent, with the same token, and approvals.status returns its answer. Do not repeat the call: the same call gives the same approval. A request waits 24 hours; a never kind is refused.
Building an integration
Any application can work on a customer's site the way an AI does: a blog tool that publishes posts, a CMS, a booking system that writes its pages, a build service. It uses the same MCP server and the same OAuth, and it can only do what the customer agreed to.
Identify with a metadata document
You do not register with us and there is no client secret. Put a JSON document on your own domain and use its https URL as client_id (Client ID Metadata Document, CIMD). We fetch it when a customer starts connecting, and again after a day, so you change your redirect addresses by changing the file:
{
"client_id": "https://blogtool.example/oauth/spaicely-client.json",
"client_name": "Blogtool",
"redirect_uris": [
"https://blogtool.example/integrations/spaicely/callback",
"http://localhost:3000/integrations/spaicely/callback"
],
"token_endpoint_auth_method": "none"
}
The consent screen then says that the client belongs to blogtool.example, because only the owner of that domain can publish that file; the customer sees the same domain under connected AIs. A client that registers itself with POST /oauth/register works too, but the consent screen says its name is unchecked. Loopback redirects (http://localhost, 127.0.0.1) are allowed for development; every other redirect must be https.
Ask for as little as you need
Send the customer to /oauth/authorize with PKCE S256 and only the scopes you use. A blog tool needs sites:read content:write sites:publish, or leaves out sites:publish and asks for approval with sites.review.request. Add paths to touch only your own files: paths=blog/**,sitemap.xml,feed.xml (comma-separated globs from the site root, at most 20; blog/** is everything under blog/). The consent screen names them, every token keeps them, also after a refresh, and a version that creates, changes or deletes anything else is refused per path with not_allowed. The customer picks the sites; account.whoami tells you which ones, with your rights on each.
Publishing a change
sites.files.listgives the live files with their SHA-256 and size.sites.deploywith that whole list, your new and changed files added or replaced. Unchanged files need onlypath,sha256andsize, up to 2,000 files in one call; send new ones withcontent(UTF-8 or base64, up to 2 MB per call) or upload them withsites.files.put. Leave a file out and it is deleted.sites.publishwith the version number. If someone published in between, your changes are merged onto the newer live version; a clash in the same file givesstale, and you start again at step 1.
Retrying is safe: sites.deploy with the same files returns the version it made before, and publishing a version that is already live makes no new one. A token lasts an hour; refresh it with the refresh token, which is replaced at every use and lapses after 30 days unused. When the customer disconnects you, calls answer 401 and the refresh token stops working: ask the customer to connect again. Some actions may wait for the customer's approval; see What the AI may do on its own.
Regions: who may change what
A role can be limited to actions on paths (create, update, delete, with patterns such as blog/**) and, on a page, to marked regions. A region is a pair of HTML comments around a block:
<!-- spaicely:edit opening-hours --> <p>Monday to Friday 8:00 to 18:00</p> <!-- /spaicely:edit --> <!-- spaicely:append news --> <article>…</article> <!-- /spaicely:append -->
In an edit region anything may change; in an append region only additions are allowed, so what is there stays. Names are lowercase letters, digits, - and _. Someone limited to regions may not change anything else on the page, the markers included. sites.files.list shows the regions of each page, and sites.region.update changes one region on top of the live version without sending the rest of the site.
When someone else published while a version was being made, publishing merges: files only this version changed come along, and a page both changed takes this version's regions, as long as the other version left those regions alone. Anything else is a conflict, reported per path; read the live version and make the change again.