UnfoldCMS Now Has a Real REST API: 42 Endpoints, Signed Webhooks, Sanctum Tokens

42 endpoints. Three audiences. HMAC-signed webhooks. Shipped to production this morning.

H
HamiPa
May 26, 2026 · 11 min read
UnfoldCMS Now Has a Real REST API: 42 Endpoints, Signed Webhooks, Sanctum Tokens

UnfoldCMS shipped a real REST API today. Forty-two endpoints, three audiences, signed outgoing webhooks. The old /api/blog/posts surface is now a 301 redirect. If you've been waiting for a reason to use UnfoldCMS as your headless CMS, this is it.

TL;DR: The new /api/v1/* namespace ships in Core (every customer install gets it), covers public read, authenticated user, and admin write. Sanctum tokens for mobile apps. Outgoing HMAC-signed webhooks for Next.js ISR revalidation. No SDK packages yet, no GraphQL yet — both are on the roadmap. Try it: curl https://unfoldcms.com/api/v1/health.

Why We Shipped a Real API

For months UnfoldCMS shipped with a tiny public API — two endpoints (/api/blog/posts and /api/blog/posts/{slug}), read-only, no docs. It worked. It also stopped us from honestly calling UnfoldCMS a "headless CMS."

If you're building a Next.js or Astro frontend that needs to fetch your About page, your Contact page, your category nav, your site title — two endpoints aren't enough. You also can't trigger an ISR revalidation when a post publishes. You can't build a mobile app that lets users post comments. You can't write integrations because there's nothing documented to integrate against.

So we built the real thing. Three sessions of work. 42 endpoints. 83 Pest tests. Live on production this morning.

What's in v1 — The Honest List

The API splits into three audiences, each with its own auth model. No magic — the URL prefix tells you what you're getting.

Public read — /api/v1/*

No auth, no token, no key. Rate limited at 60 requests/minute per IP.

Endpoint What it returns
GET /api/v1/health API + CMS version probe (for uptime monitors)
GET /api/v1/posts Paginated published posts. Filter by ?category= and ?q= search
GET /api/v1/posts/{slug} Single post with body + SEO fields
GET /api/v1/posts/{slug}/related 3–5 related posts sharing categories
GET /api/v1/pages Paginated published pages
GET /api/v1/pages/{slug} Single page
GET /api/v1/categories All categories with post counts
GET /api/v1/categories/{slug}/posts Posts in a category
GET /api/v1/search?q= Full-text search across posts and pages
GET /api/v1/menus/{location} Admin-managed nav for header/footer/sidebar
GET /api/v1/settings/public Whitelisted public settings: site name, social links, contact email

That's twelve endpoints just for public reads. Last month it was two.

Authenticated user — /api/v1/me/*

Bearer token from Sanctum. This is the mobile app and SPA API.

Endpoint What it does
POST /api/v1/auth/register Create account, return token (if registration enabled)
POST /api/v1/auth/login Email + password → token
POST /api/v1/auth/logout Revoke current token
GET /api/v1/me Current user profile
PATCH /api/v1/me Update name, email, locale, password
GET /api/v1/me/tokens List own tokens (no plaintext returned)
POST /api/v1/me/tokens Issue a new scoped token
DELETE /api/v1/me/tokens/{id} Revoke a token

Tokens have abilitiesuser, comments:write, tickets:write, newsletter:manage. A read-only mobile token can't accidentally delete a comment. Tokens revoke instantly.

Admin write — /api/v1/admin/*

Bearer token with the admin ability. Full CRUD + webhook management.

Endpoint What it does
GET/POST /api/v1/admin/posts List all (incl. drafts) / create
PATCH/DELETE /api/v1/admin/posts/{id} Update / soft-delete
POST /api/v1/admin/posts/{id}/publish Publish (fires post.published event)
POST /api/v1/admin/posts/{id}/unpublish Take a post offline
/admin/categories/* Full CRUD on blog categories
/admin/settings[/{key}] List / get / update / delete settings
/admin/webhooks[/{id}] Register signed outgoing webhooks
POST /admin/webhooks/{id}/test Send a signed test ping

What Outgoing Webhooks Actually Do (And Why You Need Them)

Most CMS comparison pages talk about "API access" like it's one feature. It's two:

  1. Inbound API — your frontend pulls content from the CMS.
  2. Outbound webhooks — the CMS pushes events to your frontend.

Without #2, your Next.js site has no idea when a new post publishes. You either rebuild the whole site on a schedule (wasteful) or you don't show new content until the next deploy (broken UX). With outgoing webhooks, the CMS tells your frontend: "post 123 just published, revalidate /blog/[slug]."

Here's the full flow:

1. Admin clicks Publish in /admin/posts/123
                 ↓
2. POST /api/v1/admin/posts/123/publish fires PostPublished event
                 ↓
3. WebhookDispatcher looks up all active subscriptions
                 ↓
4. For each subscriber, signs payload with HMAC-SHA256
                 ↓
5. Sends POST {url} with X-Signature header
                 ↓
6. Subscriber verifies signature, then triggers their action
   (Next.js: revalidate cache · Vercel: redeploy · Slack: notify editor)

The signature header lets the subscriber confirm the request actually came from your CMS, not a random attacker who guessed the URL. The signing secret is returned only once on subscription creation — store it immediately, because UnfoldCMS won't show it again.

Registering a Webhook

curl -X POST https://your-site.com/api/v1/admin/webhooks \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Next.js ISR Hook",
    "url": "https://your-nextjs-app.com/api/revalidate",
    "events": ["post.published", "post.updated"]
  }'

Response includes a secret field. Save it now — that's the HMAC key. Future GETs hide it.

Verifying a Webhook in Next.js

// pages/api/revalidate.ts
import crypto from 'crypto';

export default async function handler(req, res) {
  const signature = req.headers['signature'];
  const expected = crypto
    .createHmac('sha256', process.env.UNFOLDCMS_WEBHOOK_SECRET)
    .update(JSON.stringify(req.body))
    .digest('hex');

  if (signature !== `sha256=${expected}`) {
    return res.status(401).end();
  }

  const { event, data } = req.body;
  if (event === 'post.published' || event === 'post.updated') {
    await res.revalidate(`/blog/${data.slug}`);
  }

  return res.json({ revalidated: true });
}

That's it. No SDK, no library. Two npm modules you already have (crypto is built-in, no install).

How To Use The API With Next.js

You don't need an npm package. You don't need a wrapper. You don't need @unfoldcms/next — it doesn't exist yet. You need fetch().

// app/blog/page.tsx — Next.js 14 Server Component

export const revalidate = 60; // ISR every 60 seconds (or use webhook revalidation)

async function getPosts() {
  const res = await fetch('https://your-cms.com/api/v1/posts?per_page=20', {
    next: { revalidate: 60 },
  });
  if (!res.ok) throw new Error('Failed to load posts');
  return res.json();
}

export default async function BlogIndex() {
  const { data: posts } = await getPosts();
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <a href={`/blog/${post.slug}`}>{post.title}</a>
          <p>{post.excerpt}</p>
        </li>
      ))}
    </ul>
  );
}

That's the whole integration. You can build a full Next.js blog against UnfoldCMS in an afternoon. For agencies running multiple client sites, you can wire the same Next.js codebase to N different UnfoldCMS instances by changing one environment variable.

How To Use The API With Astro

---
// src/pages/blog/[slug].astro
export async function getStaticPaths() {
  const res = await fetch('https://your-cms.com/api/v1/posts?per_page=100');
  const { data: posts } = await res.json();
  return posts.map((post) => ({
    params: { slug: post.slug },
    props: { post },
  }));
}
const { post } = Astro.props;
---

<article>
  <h1>{post.title}</h1>
  <div set:html={post.body} />
</article>

Static-site generation with full content. No vendor lock-in. No per-API-call billing.

How To Use The API With SvelteKit

// src/routes/blog/+page.server.ts
export async function load({ fetch }) {
  const res = await fetch('https://your-cms.com/api/v1/posts');
  const { data: posts } = await res.json();
  return { posts };
}

Same shape. Same simplicity. Same fetch().

What's NOT in v1 (The Honest Roadmap)

We could pretend we shipped everything. Instead, here's what we deliberately didn't ship in v1, and roughly when it lands:

Feature Status Why we didn't ship it
@unfoldcms/next npm SDK Roadmap Build it when we know which frameworks dominate usage. OpenAPI spec means you can generate your own client today.
GraphQL endpoint Roadmap REST + filtering covers 95% of cases. GraphQL is significant engineering for a small win.
Real-time subscriptions (WebSocket/SSE) Roadmap Polling at 60s ISR works for blogs. Real-time matters for chat apps, not content.
Multi-language content endpoints Roadmap Whole i18n feature is a separate plan.
Bulk import/export (CSV) Roadmap Useful but not v1-critical.
Admin UI for webhook management Next minor v1 ships API only. UI in v1.3.
Admin UI for API key issuance Next minor .env config is the source for now.
Draft preview tokens Roadmap Needs a token model + middleware. v2 candidate.

What this means in practice: if you're building a Next.js blog or a mobile app against UnfoldCMS today, v1 is enough. If you're building a real-time collaborative editor with three frontends in three languages, you'll hit the roadmap.

Why You'd Use This Instead Of A Hosted Headless CMS

There's a CMS for every use case in 2026. The honest pitch for UnfoldCMS as a headless option:

  • One-time license, no per-API-call billing. Contentful charges $300+/month per project past their free tier. Sanity bills per CDN hit at scale. UnfoldCMS is paid once, runs on your server, calls cost you whatever your server costs.
  • You own the data. GDPR + data sovereignty matter when your CMS holds customer content. Self-hosted means your data never leaves infrastructure you control.
  • Real REST + webhooks. Same shape Stripe, Shopify, GitHub use. No proprietary query language to learn.
  • Same admin you'd build yourself. 205 admin pages, 51 shadcn/ui components. Editors don't need to context-switch into a clunky external dashboard.

The pitch ends here: if your team is happy paying Contentful $500/month and your data residency is fine, stay. If you'd rather own it, the demo is live.

Frequently Asked Questions

Is this a SaaS API I can hit, or do I run it myself?

Both. unfoldcms.com itself uses the same API surface (you can hit https://unfoldcms.com/api/v1/posts right now to see real data). When you install UnfoldCMS on your own server, your install ships with the same /api/v1/* endpoints. The product is the CMS — you bring the hosting.

What happens to the old /api/blog/posts endpoint?

301 redirect to /api/v1/posts. The legacy path will keep working for at least 6 months, with a Deprecation header so well-behaved clients can move at their own pace. Same for /api/blog/posts/{slug}/api/v1/posts/{slug}.

Can I write content via the API, or is it read-only?

You can write. The /api/v1/admin/* namespace covers full CRUD on posts, pages, categories, settings, and webhook subscriptions. You'll need a Sanctum token with the admin ability — issue one to yourself via php artisan tinker or (when v1.3 ships) the upcoming admin UI.

How do I trigger Next.js ISR when a post publishes?

Register a webhook subscription pointing at your Next.js /api/revalidate route. Subscribe to post.published and post.updated. When events fire, UnfoldCMS sends a signed POST to your URL. Verify the signature with HMAC-SHA256, then call res.revalidate(). Full code sample in the webhooks section above.

Why no GraphQL?

Because the cost of building it doesn't match the use cases we see. REST with filtering, includes, and sparse fieldsets handles the headless CMS pattern just fine. We'll add GraphQL when enough customers ask, not before. If you specifically need it, tell us.

Is the API the same in Core and Pro?

Yes. The entire /api/v1/* surface ships in the Core tier. No paywalled endpoints, no rate limit differences. Pro adds features (newsletter, analytics, social posting, ad zones) that have their own API extensions, but the foundation is identical.

What To Do Next

If you've been waiting to evaluate UnfoldCMS for a real Next.js or Astro project, today's the day:

  1. Try the live APIcurl https://unfoldcms.com/api/v1/health returns JSON in <100ms.
  2. Read the Best CMS for Next.js page — now backed by a real API instead of promises.
  3. Spin up the demo — every UnfoldCMS install gets the same /api/v1/* surface.
  4. Check the pricing — one-time license, no per-API-call billing forever.

The full API documentation (markdown reference, OpenAPI spec, framework guides) ships in the next release. Until then, this post is the canonical reference.

Methodology and Sources

This post describes work shipped to https://unfoldcms.com on May 25–26, 2026 across three engineering sessions:

  • Session 1 (May 25): Public read endpoints + maintenance mode JSON handler + legacy redirect (commit ebb1b3a)
  • Session 2 (May 25): Sanctum tokens + /me/* user surface + auth flows (commit 8b9c0ed)
  • Session 3 (May 26): Admin CRUD + outgoing webhooks via spatie/laravel-webhook-server (commit 382d8c2)

Test suite: 83 Pest feature tests, 225 assertions, 10s runtime. Production smoke tests verified all 42 endpoints return correct shapes via direct curl.

Source code: the cms/app/Http/Controllers/Api/V1/ directory in the UnfoldCMS subtree of this website's repository.

Related: The CMS Built on shadcn/ui: Why It Matters · Best CMS for Next.js · Headless CMS and SEO: What Actually Matters in 2026

Share this post:

Discussion

Comments (0)

Leave a Comment

Please log in to leave a comment.

Don't have an account? Register here

No comments yet. Be the first to share your thoughts!

Keep Reading

Related Posts

Back to all posts