Skip to main content
A publishable key is readable by anyone who views your page source. What stops someone lifting it and running your workspace’s agent from their own site is the origin check.

The rule

Every widget endpoint resolves the key, then checks the request Origin against the workspace. In production, the check runs in this order:
1

Your workspace domain, if one is set

One workspace, one domain. The request origin’s host must match the domain you set in Settings → Workspace.The comparison strips a leading www. from both sides, so example.com and www.example.com are the same. Everything else must match exactly — a subdomain does not inherit. A request with no Origin header is refused.When a domain is set, this is the whole check. There is no separate list to configure.
2

No domain set — fail closed

Before onboarding sets a domain, localhost, 127.0.0.1 and ::1 are allowed implicitly on any port, so local development works.Any other origin must be on the key’s explicit allowlist. A production key with no domain and no allowlist entries is not open to the internet — it is refused.
Outside production the check does not run at all. A valid key is still required, but any origin is accepted, so a local host page works against a locally-run platform on any port regardless of the workspace’s domain. This never loosens a real deployment.

Getting this right

Set your domain in Settings → Workspace as soon as you have one. Until you do, your key works from localhost and nowhere else, which usually shows up as the widget working in development and silently not appearing in staging.
If your app runs on a subdomain, set the workspace domain to that subdomain. Setting it to the apex domain will not cover app. — the host comparison is exact once www. is normalised away.

How a refusal looks

Most widget endpoints answer a disallowed origin with 403:
Some use a longer form:
Some do not report it as an error at all — the calls that run on page load fall back to safe defaults or empty results instead, because a hard failure there would surface as a console error inside your app. If the widget appears but never does anything, check the origin before you check anything else.

CORS

Peeve echoes your origin in Access-Control-Allow-Origin only when the key resolves and the origin is allowed. Otherwise it answers *, which — combined with the endpoint refusing to act — reveals nothing. Preflight (OPTIONS) is answered *, because it carries no credentials. Allowed request headers: Content-Type, Authorization, X-Peeve-Key. The MCP endpoints also allow Mcp-Session-Id.
The MCP endpoints and the artifact endpoint answer Access-Control-Allow-Origin: * unconditionally. They are read-only and guide-only, so there is nothing to protect with an origin check. See the MCP overview.

What the origin check is for

The origin check bounds what a browser can do with a publishable key it found in someone else’s page. It is not a general access control, and it is not meant to be — a server can send any Origin header it likes. That is why the server API does not rely on it at all: those endpoints require a secret key, which is a credential you keep private, and they send no CORS headers so a page cannot call them in the first place.