Build with Stratafold
Structured content, published snapshots, signed events and reviewable repository changes.
Delivery API
Create a Delivery key in Workspace settings → Developer tools. Each key selects its workspace; tenant and workspace headers cannot change that scope. Use a Preview key only on a trusted server.
curl 'https://api.stratafold.com/delivery/v1/content/blog_post?page=1&pageSize=20&order=publishedAt:desc' \
-H 'Authorization: Bearer YOUR_DELIVERY_KEY'All paths below begin with https://api.stratafold.com/delivery/v1 and require Bearer authorization.
| Method | Path | Purpose |
|---|---|---|
| GET | /content-types | Available schemas and fields |
| GET | /content/:typeApiId | Published entries; page, pageSize, slug, order, locale, fallback, include |
| GET | /content/:typeApiId/:idOrSlug | One entry; preview, locale, fallback, include |
| GET | /pages | Published page tree |
| GET | /pages/by-path?path=/about | Page at its website address |
| GET | /menus | Menus; locale and fallback |
| GET | /menus/:apiId | One menu |
| GET | /redirects/resolve?from=/old-address | Redirect destination and status |
| GET | /search?q=flowers | Published-content search |
| GET | /alerts | Currently active site banners |
| GET | /events | Event occurrences |
| GET | /events/:id.ics | Calendar download |
| GET | /forms/:key | Published form fields and captcha requirements |
| POST | /forms/:key/uploads | Multipart file upload; response contains assetId and url |
| POST | /forms/:key/submissions | Validated answers, honeypot and captcha token |
| POST | /subscriptions | Double-opt-in subscription request |
Entry lists return items, page, pageSize, total, totalPages. Each entry contains id, contentType, title, slug, path, fields, seo, publishedAt, updatedAt, version. List page size is 1–100; the default is 20. Supported order values: publishedAt:desc, publishedAt:asc, title:asc, title:desc and updatedAt:desc.
Set preview=true with a Preview key to read drafts. Published responses read published fields and search metadata. Set include=1 for one level of references; locale=fr-FR chooses a language, and fallback=false prevents language fallback.
Errors have {error:{code,message,details?}}. Handle 401 for invalid keys, 403 for insufficient scope or disallowed browser origins, 404 for missing content, and 429 with Retry-After. Keep private preview keys out of client bundles; a public Delivery key may be used with the workspace browser-origin allowlist.
Typed TypeScript client
Download the dependency-free client and keep it in your server integration.
import { createStratafoldClient } from './stratafold-client';
type Content = { blog_post: { title: string; body: string } };
const cms = createStratafoldClient<Content>({ key: process.env.STRATAFOLD_DELIVERY_KEY! });
const posts = await cms.list('blog_post', { pageSize: 20 });
for (const post of posts.items) console.log(post.fields.title);Forms and subscriptions
Read a form definition, render its fields and visibility rules, then submit answers keyed by each field’s apiId. File questions upload first and reference the returned assetId. When requireCaptcha is true, render Cloudflare Turnstile using captchaSiteKey and supply its token. Keep contactMethod empty for the honeypot.
{
"answers": {
"name": "Alex",
"email": "alex@example.com",
"message": "Please tell me more."
},
"contactMethod": "",
"captchaToken": "TOKEN_IF_REQUIRED"
}POST subscriptions with email, topics (public topic keys), optional name, frequency, locale and sourceUrl. A successful request sends a confirmation email; it does not imply a confirmed subscriber.
Verify webhook deliveries
Configure an HTTPS endpoint in developer tools. Save the signing secret shown at creation. Signatures use HMAC-SHA256 with the secret’s SHA-256 hexadecimal digest as the key, over the timestamp, a dot, and the exact raw request body. Reject timestamps outside five minutes, compare in constant time, and deduplicate delivery IDs in durable storage.
import { createHash, createHmac, timingSafeEqual } from 'node:crypto';
const timestamp = request.headers.get('x-stratafold-timestamp') ?? '';
const signature = request.headers.get('x-stratafold-signature') ?? '';
const rawBody = await request.text();
if (!/^\d+$/.test(timestamp) || Math.abs(Date.now()/1000 - Number(timestamp)) > 300)
return new Response('Expired', { status: 400 });
const signingKey = createHash('sha256').update(process.env.STRATAFOLD_WEBHOOK_SECRET!).digest('hex');
const expected = createHmac('sha256', signingKey).update(timestamp + '.' + rawBody).digest();
if (!/^v1=[0-9a-f]{64}$/.test(signature)) return new Response('Invalid', { status: 400 });
const supplied = Buffer.from(signature.slice(3), 'hex');
if (!timingSafeEqual(expected, supplied)) return new Response('Invalid', { status: 400 });
const event = JSON.parse(rawBody);
// Atomically record event.deliveryId, then enqueue your work once.
return new Response(null, { status: 204 });Headers: x-stratafold-event, x-stratafold-delivery, x-stratafold-timestamp and x-stratafold-signature. The JSON body contains event, data and deliveryId. Return a 2xx response after durably accepting work; failed attempts are retried.
GitHub publishing contract
Commit stratafold.publish.json at the repository root before connecting it. Stratafold manages data in the named directory, creates branches under stratafold/, and opens reviewable changes; it does not directly rewrite the production branch.
{
"version": 1,
"productionBranch": "main",
"managedDirectory": "content/posts",
"format": "json",
"contentType": "blog_post",
"liveUrlTemplate": "https://example.com/blog/{slug}",
"liveMarkerAttribute": "data-stratafold-entry",
"liveRevisionAttribute": "data-stratafold-revision",
"managedImageDirectory": "public/stratafold-images"
}Replace example.com with your production host and blog_post with the content type’s API ID. Content and image directories must be separate, non-overlapping safe paths. Supported formats are json and markdown; branch and directory values are validated when connecting.
Render each published document’s ownership markers on its live page:
<article
data-stratafold-entry={document.stratafoldEntryId}
data-stratafold-revision={document.stratafoldRevision}
>
<h1>{document.title}</h1>
{/* Render the document's typed blocks through your components. */}
</article>Stratafold checks the live URL and both markers after merge before reporting the change as live. Keep the generated markers intact; review and deploy repository changes through your normal workflow.