Documentation
Three ways to read content out, and a reference generated from the routes themselves so it cannot describe an endpoint that does not exist.
Quickstarts
Plain fetch
No SDK, no build step. This is the whole integration for a list of posts.
const response = await fetch(
"https://api.schemahaus.com/v1/content/blog-post?locale=ka&limit=10",
{ headers: { authorization: `Bearer ${process.env.SCHEMAHAUS_TOKEN}` } },
);
const { data, meta } = await response.json();
// data is only what is PUBLISHED in Georgian. Nothing is substituted
// unless you ask: add &fallback=chain, and check item.localeFallback.Next.js
The SDK plus a revalidation webhook, so an editor pressing publish rebuilds the page rather than waiting for a cache to expire.
// app/[locale]/blog/[slug]/page.tsx
import { createClient } from "@schemahaus/client";
const cms = createClient({
token: process.env.SCHEMAHAUS_TOKEN,
site: process.env.SCHEMAHAUS_SITE,
});
export default async function Post({ params }) {
const { locale, slug } = await params;
const post = await cms.document("blog-post", slug, { locale });
if (!post) notFound();
return <article dangerouslySetInnerHTML={{ __html: post.data.body }} />;
}
// Publishing fires a webhook. Point a nextjs_revalidate webhook at your
// deploy and the page rebuilds itself when an editor presses publish.Jekyll, at build time
For a static site that has no runtime at all: pull the content during the build and write the files the generator expects.
# _plugins/schemahaus.rb — runs at build time, writes _posts/
require "net/http"
require "json"
site = ENV.fetch("SCHEMAHAUS_SITE")
token = ENV.fetch("SCHEMAHAUS_TOKEN")
uri = URI("https://api.schemahaus.com/v1/content/blog-post?locale=en")
request = Net::HTTP::Get.new(uri)
request["authorization"] = "Bearer #{token}"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
JSON.parse(response.body)["data"].each do |post|
File.write("_posts/#{post["publishedAt"][0, 10]}-#{post["slug"]}.md",
"---\ntitle: #{post["data"]["title"].inspect}\n---\n" +
post["data"]["body"])
endAPI reference
Read from the route tree at build time. An endpoint appears here when the file exists and disappears when it is deleted, so this cannot document something that is not there.
Delivery
Published content, read with a delivery token. Safe to put in a consumer site's environment: it can read and nothing else.
GET/api/v1/authorsThe site's authors, with their per-language biographies.
GET/api/v1/content/{apiId}Published documents of one content type. Filter, sort, paginate by `page` or by cursor (`?cursor=` to start, then each page's `meta.nextCursor`); `?locale=` is strict unless `fallback` says otherwise.
GET/api/v1/content/{apiId}/{identifier}One published document, by slug or by document id.
GET/api/v1/media/{id}One media asset's metadata and variant URLs.
POST/api/v1/preview/verifyExchange a preview token for the draft it was minted for. Needs a delivery token with draft access.
GET/api/v1/siteThe site's settings and its content languages.
GET/api/v1/site/typesThe content types on the site, with their fields. Addressed by apiId; no internal ids are returned.
Management
Writes, bound to a membership, so a key can never do more than the person who minted it. This is what the import scripts use.
GETPOST/api/v1/manage/authorsList or create authors.
PATCHDELETE/api/v1/manage/authors/{id}Edit or remove one author.
POST/api/v1/manage/documentsCreate a document in one language.
PATCHDELETE/api/v1/manage/documents/{id}Edit one language version, or soft-delete the whole document. Editing takes the revision you last read.
POST/api/v1/manage/documents/{id}/localesStart a translation of a document.
POST/api/v1/manage/documents/{id}/transitionMove one language version through the workflow, including publishing it.
GET/api/v1/manage/documents/lookupFind a document by slug, drafts included — how an import decides whether it has already brought a post across.
POST/api/v1/manage/mediaReserve an upload and get a presigned PUT for object storage.
POST/api/v1/manage/media/{id}/completeConfirm the bytes arrived. Idempotent, so a retried import does not count the storage twice.
GET/api/v1/manage/typesContent types WITH their internal ids, which every write below needs.
Strapi compatibility
Strapi's query shape over the same published content, so an existing site can move before it is rewritten.
GET/api/strapi/api/{plural}The Strapi compatibility layer: Strapi's query shape over the same published content, so an existing site can move before it is rewritten.
Four rules worth knowing before you start
- Delivery is strict by default
- `?locale=ka` returns only what is published in Georgian. Missing content looks like missing content rather than silently becoming English. Add `fallback=default` or `fallback=chain` to opt in, and check `localeFallback` on each item to see what was substituted.
- The API serves published data, never drafts
- A document being edited keeps serving its last published version until somebody publishes again. If a change is not appearing, it has not been published — that is the feature.
- Media keys never change
- Replacing an image creates a new asset with a new URL. The old one keeps working, because edge caches and already-built sites still point at it.
- Each language publishes separately
- Publishing English does not publish German. There is no single publish state for a document — every language version has its own.