Quickstart
Ten steps: register, wait for the site to be provisioned, take a token, create a block and a page, read the collection back, change it safely, look at a refusal, and watch the page appear on the site's own domain. Nothing here is a sandbox — these are the production hosts, and what you build in the next ten minutes is a real site.
You need curl and jq, and the whole thing is one file:
quickstart.sh. Every block below is a region of it, so what you read is
what runs.
Code
1. Is that address still free?
Sign-up is the platform's only unauthenticated surface, so every call on it is
bot-checked — including this one. From a terminal that means a CAPTCHA proof in
recaptcha_token; from the browser it means the sign-up form at
moover.app does it for you. If you registered that way, skip to
step 4 with the address and password you chose.
Code
It is a question, not a reservation: the answer can go stale, and step 2 is what actually decides.
2. An account, and a site being built
Creating the user creates the account and starts provisioning the first website, so the
response carries three things the user resource itself does not have: the account_id, a
one-time signup_token, and the name of the operation to poll.
Code
The call answers 201 with the resource, not a receipt — see
the standard methods — and request_id makes it replayable: a
timeout you retry with the same value gives back the first attempt's result instead of
provisioning a second site (idempotency).
3. Waiting for the site to be provisioned
The account exists the moment step 2 returns. The site does not: a database, buckets, routing, a view and the seeded content are still being made, and that work outlives the request that started it. So the call names an Operation and hands the polling to you.
Code
metadata.step is the hop the work is on. There is no percentage, because nothing in the
cascade counts anything — a progress bar here would be an invention.
4. A bearer token
Everything a site is made of lives behind Authorization: Bearer <jwt>. Two ways to get
one, and this is the ordinary way: the password the account signs in with.
Code
Straight after a signup there is a second way, which is what the platform's own dashboard
uses so nobody has to retype the password they just chose: trade the one-time
signup_token at users:exchange_handoff. See
authentication.
5. Finding the website
Three lists down the hierarchy the resource names spell out: an account owns projects, a project owns websites, and everything after this hangs off the website.
Code
Every list on the platform answers in the same envelope, with the array named exactly after the collection it came from — one reader handles all three, and there is no per-endpoint map to keep.
6. A block, then a page
A block is a fragment with a handle: pages, themes and rich-text links pull it in by its
identifier, and editing it once changes it everywhere it appears. A page is what a URL
resolves to.
Code
Create answers 201 with the created resource and a Location header, so the client that
just wrote something never has to read it back to learn what the server made of it —
including the etag step 8 needs.
7. Reading the collection back
Four parameters, the same four on every list on the platform.
Code
A field is filterable or sortable only where the resource says so, and the reference page
for each resource lists which — an unknown field is an
INVALID_ARGUMENT naming it, never a silently ignored URL that
returns a full unfiltered page. Lists, filters and ordering has the
grammar.
8. Changing it without overwriting somebody else
PATCH merges: the fields in the body are written, everything else is left alone. The
etag a resource carries is its version — send it back in If-Match and the write is
refused if anybody has changed the page since you read it.
Code
The second write is what a second editor's Save looks like: 409, ABORTED, and the
current resource in the body so the client can show what it lost to.
Concurrency is the whole rule.
9. What a refusal looks like
One shape for every failure on every surface, so a client needs one error reader.
Code
status is the canonical code to branch on, message is for the human reading the log,
and details is where a form finds which field to attach the message to.
Errors has the codes.
10. The page, on the site
Publishing is asynchronous. The write above returned as soon as the page was stored; the rendered site follows a moment later, when the read model behind it has been rebuilt. Polling for it is the whole of the difference.
Code
Where to go from here
- The Tenant API reference — every resource a site is made of, with the fields, filters and examples generated from the service itself.
- Concepts — the rules every one of those endpoints obeys, so you can predict an endpoint you have not read yet.
- For agents — what the site you just made offers a program that reads it.