Identify your site
How a public request is matched to one website — verified domain, X-Site, and the deprecated X-Subdomain — and what a draft or paused site answers.
An organization can run several public websites at once: a real-estate site, a stays portal, a company profile. A public request therefore resolves a site, not just a tenant, and every answer is the content of that one site.
Resolution order
The API tries three things, in this order, and stops at the first that works.
- The domain the request came from. The
Originheader (browser) orHostheader (server) is matched against the domains added to a site under Settings → Websites → Domains. A match resolves the site with no header at all. X-Site: <site key>. The site key from the Connect tab. This is the explicit way, and the one to prefer.X-Subdomain: <org subdomain>— deprecated. It resolves the organization's default site. It still works, and it will be removed after an announced window; see the changelog.
The two can combine: when the request arrives from a verified domain and
carries X-Site, the key must name a site belonging to that domain's
organization. If it names someone else's site you get 403 SITE_ORIGIN_MISMATCH
— which is the point: a domain cannot read another tenant's content by guessing
a key.
GET /api/public/properties?page=1&perPage=24 HTTP/1.1
Host: crm.example.com
X-Site: my-site
X-Api-Key: pk_my-site_0123456789abcdef01234567Why a preview deployment needs X-Site
A Vercel preview runs on a generated hostname —
my-site-git-feature-team.vercel.app — that is not one of your site's domains
and never will be: you would be adding a new one per branch. So rule 1 cannot
fire, and without X-Site the request identifies nothing and is refused before
your code sees it.
Set NEXT_PUBLIC_SITE_KEY in the preview environment as well as production, and
every deployment resolves regardless of the host it happens to be served from.
Your browser calls also need CORS, which only the site's domains get — so a
preview that calls the API from the browser should either go through your own
server (a route handler) or run against a host you have added.
Draft and paused sites
A site has a status, and the public API respects it:
| Status | Public answer |
|---|---|
| LIVE | Normal. |
| DRAFT | 404 SITE_NOT_FOUND — nothing is published here yet. An organization with no site at all answers the same way. |
| PAUSED | 503 SITE_PAUSED — "This website is temporarily paused." |
Both are deliberate: a draft site must be indistinguishable from a site that does not exist, and a paused one must say "come back later" rather than "gone".
If a freshly created site answers 404, it is almost certainly still a draft — publish it from the site header in the CRM.
A domain is live the moment you add it
Adding a domain in Settings → Websites → Domains makes it usable immediately: it resolves the site, and it is reflected for CORS. There is no TXT record to publish and no verification step to wait for. The first domain you add becomes the primary one.
A host can belong to only one site across the whole platform — adding one that
another organization already has answers 409 DOMAIN_TAKEN.
Next
- Keys and CORS — the key that rides along with the site.
- Errors —
SITE_NOT_FOUND,SITE_PAUSED,SITE_ORIGIN_MISMATCHand the rest.
Quick start
Read your company profile and run a search from a Next.js app — environment variables, headers, the response envelope and pagination.
Keys and CORS
Publishable and secret API keys, the origin rule, rotation and revocation, how CORS follows your site's domains, and the monitor-then-enforce rollout.