Public API
Hypervalent Studio is a headless CMS: everything you model and publish in Studio is served by a REST API at https://api.hypervalent.io/v1, authenticated with an organization API token. Site builds read it; agents and integrations can also write through it.
| Base URL | https://api.hypervalent.io/v1 |
| Authentication | Authorization: Bearer atk_… or X-API-Key: atk_… — see Authentication |
| Format | JSON in and out, camelCase keys; Content-Type: application/json on writes |
| Reference | Endpoint reference · OpenAPI document |
| Errors | { status, code, message } — see Errors |
New here? Follow the quickstart — it goes from a token to rendered content in five requests.
How the API is organized
Every URL names the organization and, for content, the project:
/orgs/{org_id}/token who am I
/orgs/{org_id}/projects the projects this token can reach
/orgs/{org_id}/projects/{projectSlug}/doctypes the content model
/orgs/{org_id}/projects/{projectSlug}/doctypes/{doctypeSlug}/documents
/orgs/{org_id}/projects/{projectSlug}/taxonomies
/orgs/{org_id}/projects/{projectSlug}/settings
/orgs/{org_id}/projects/{projectSlug}/design-tokens
/orgs/{org_id}/projects/{projectSlug}/media uploadorg_idis the organization's public id, shown in Studio → Settings → API tokens. It is not the organization's uuid.projectSlugis a required path segment — every content table is project-scoped, so there is no cross-project read and no "default project". Discover slugs withGET /orgs/{org_id}/projects.- Document and term reads nest under their document type or taxonomy (
/doctypes/{doctypeSlug}/documents/{slug}), because slugs are unique per type, not per project.
What a token can see
A token is an organization credential, optionally narrowed to one project. The URL is checked against it on every request, in this order: credential first, then organization, then project.
| Token | May read | Naming another project |
|---|---|---|
| Organization-wide | Every live project in the organization | — |
| Project-narrowed | Exactly its project | 403 TOKEN_PROJECT_MISMATCH |
A read token sees published content only. A read of a draft, or of anything that does not exist, is the same plain 404 NOT_FOUND — the API never confirms that draft work exists to a read token. A read_write token on a plan with API writes can add ?status=draft to document, taxonomy and term reads to get the working view: every non-trashed row, draft or published, with its latest working version. See Authentication for scopes.
The content model in one paragraph
A document type (/doctypes) defines a kind of document: its root atoms — the top-level fields, each an atom (/atoms) binding with a default, required and locked flags — the taxonomies it offers, and the molecules (/molecules, reusable blocks) its body may nest. A document carries a version: rootAtoms (values keyed by atom uuid) and content (the body, a tree of nodes). Taxonomies hold hierarchical terms, which have versions of their own. Settings are admin-defined config values; design tokens are the project's design system. Reads resolve everything by uuid: a root-atom value's uuid is an atom, an image field holds image uuids you look up in the response's referencedImages, and a file field holds file uuids you look up in referencedFiles (url, filename, mime, size). A file field value written before file fields picked from the media library is a plain URL string instead. A video field holds video uuids — also in referencedFiles — and/or plain URL strings for videos hosted elsewhere (YouTube, Vimeo).
A link field holds a string (an array of strings when the field is multiple): an http(s) URL, a protocol-relative URL (//cdn.example.com/a), a path (/about, docs/intro), a query string (?ref=x), a fragment (#team), or a mailto: / tel: link. When a document or term version is saved, a link value with any other scheme (javascript:, data:) or a control character is refused: a single link is saved as "" and a refused entry of a multiple link is removed. There is no url field type any more: it was merged into link, so an atom's or a setting's fieldType that used to read url now reads link. Stored values were not rewritten by the merge (a value saved before it is checked the next time its document or term is saved), so escape link values like any other text when you render them.
Pagination
Two list shapes exist, and the reference names which one each route answers with:
- Paged lists — documents of a type, a taxonomy's terms, projects — take
?limit=(max 50, default 10) and?page=(1-based) or?offset=, and answer{ results, count, limit, offset, page }wherecountis the total, not the page size. - Full-set lists — document types, taxonomies, atoms, molecules, settings, design tokens — answer everything in one
{ results, count }.
Branding
Organizations on plans without the removeBranding entitlement get a poweredBy string on every read response (and the stamp in the CSS and W3C design-token exports). Nothing else in the body changes; writes are never stamped.
Rate limits
Limits are keyed by the credential, not the caller's IP, so one busy consumer cannot throttle another organization. A refused request answers 429 with a Retry-After header.
| Limit | Applies to |
|---|---|
| 120 requests / minute | Every read |
| 60 / minute and 2,000 / day | Every write — the daily cap stops a runaway loop, and only affects writes |
CORS and caching
Public reads answer Access-Control-Allow-Origin: * without credentials, so a browser app on any domain can call the API with its token in a header (never a cookie). Every response carries Vary: Authorization, X-API-Key; credentialed responses are Cache-Control: private, no-store. Cache on your side — a site build should fetch once and render, not call the API per page view.
Regenerable, not hand-written
The endpoint reference and the OpenAPI document are generated from the API server's route table and its API-token allow-list. A route that is not on that reviewed list cannot be reached with a token, and cannot appear here.