Developers

Built for developers.
Ready for their agents.

REST or GraphQL from one typed schema. An MCP server and a CLI your agents already know how to use. Dated versions, and published content served from the nearest edge, typically in 20 to 50 ms.

~/sitecdn.capacms.com
Agents

Point your agent at Capa.
Ship the query it writes.

The Capa MCP server gives Claude, Claude Code, Codex and Cursor your schema and your content, read with your key. Ask for the query a page needs and get one that has already run.

More on the MCP server
Connect your agent

In your terminal. The key stays in your shell, out of the repo.

$ claude mcp add capa \
  -e CAPA_API_URL=https://cdn.capacms.com \
  -e CAPA_KEY="$CAPA_KEY" \
  -e CAPA_API_VERSION=2026-10-01 \
  -- npx -y @capacms/mcp
  • 19 tools, 17 of them reads. The two that write change editor layouts and workspaces, never content.
  • Three prompts built in: explore-content, write-query and fix-query-error.
  • Sees what your key sees. A production key reads published entries only, and every answer says which key read it.
Claude Code

Write the query for the blog index: the 10 newest featured articles with their author’s name. Give me the SDK code for a Next.js server component.

  1. capa_graphql_schema{ "model": "articles" }Running
  2. capa_explore_data{ "model": "articles", "field": "featured", "sample": 0 }Running
  3. capa_graphql_build{ "model": "articles", "fields": ["title", { "field": "author", "fields": ["name"] }], "filter": { "featured": { "eq": true } }, "sort": ["publishedAt_DESC"], "first": 10, "run": true, "code": "next" }Running

Here is the server component. It reads through graphql() from @capacms/sdk/nextjs, tagged with every model the query reads, so a publish refreshes the page.

And a CLI they can run

The SDK ships capa. Your agent keeps types in step with your models while it works, and stores the queries it wrote so production sends only their hash.

More on the SDK and CLI
Terminal
$ npx capa codegen --graphql --watch --out src/capa-graphql.ts
capa-codegen: wrote src/capa-graphql.ts: the schema's types and 3 typed documents.
capa-codegen: watching src for changes to your GraphQL documents. Stop with Ctrl-C.
$ npx capa persist --release "$GIT_COMMIT" --env production
capa persist: pinning for release 4f1c2ab in production (from --release and --env).
835d679d63340f4b6d37577a80a5e40112db519662aec969d4927a6516c4793b  Latest  stored, pinned
capa persist: 1 of 1 documents stored, pinned.
REST or GraphQL

Two ways in.
One answer.

Write REST's select grammar or a typed GraphQL query. Every GraphQL root field runs as a REST read, on the same planner, with the same keys, limits, errors and cache. Pick per page, not per project.

More on the Content API

Same entries, same cursor, same cache

Request
GET /api/entries/articles?select=title,author(name)&limit=2
Host: cdn.capacms.com
x-api-key: cap_live_…
Capa-Version: 2026-10-01
select = "title,author(name)"  # author, expanded
limit  = 2                     # 1 to 200 a page
From @capacms/sdk
await capa.entries.list("articles", {
  select: ["title", { author: ["name"] }],
  limit: 2,
});
Response200
{ "data": [
    { "id": "…2e", "model": "articles",
      "status": "published",
      "fields": {
        "title": "Winter Field Guide 2026",
        "author": { "id": "…12", "model": "authors",
          "status": "published",
          "fields": { "name": "Brin Cole" } } } },
    { "id": "…2c", "model": "articles",
      "status": "published",
      "fields": {
        "title": "Merino or Synthetic?",
        "author": { "id": "…13", "model": "authors",
          "status": "published",
          "fields": { "name": "Cody Marsh" } } } } ],
  "page": { "limit": 2, "hasNext": true,
    "next": "c1.eyJ2Ijoi…", "hasPrev": false, "prev": null },
  "meta": { "version": "2026-10-01", "contract": 1,
    "environment": "production", "requestId": "req_0f3c…" } }
  • select=title,author(name,books(title,limit:3))Expand relations in the read: up to 12 a request, 4 hops deep.
  • filter[author.name][eq]=Ada ValeFilter on a field, or one hop across a relation.
  • where={"or":[{"featured":{"eq":true}},{"views":{"gt":100}}]}And, or and not, as JSON.
  • sort=-views,title&limit=50&after=c1.eyJ2…Up to three sort keys. Cursor pages of 1 to 200.
  • GraphQL schema typed from what the key can read
  • Persisted queries, cached at the edge
  • A cost budget of 5,000 entries per operation
  • Flat responses that carry each related entry once
  • Dated API versions, so our releases never change your site
Speed

Always in cache.
Never rebuilt.

Published reads are served from the edge. A publish purges only the keys it touched, and the page is live in under three seconds. No ISR to tune. No rebuild to wait for.

More on the edge cache
Edge logLive

A request log: reads answered from the edge cache, typically in 20 to 50 ms, and a publish that purges only the keys it changed.

  • 20–50 msA typical cached read from the nearest edge, over REST or GraphQL.
  • < 3 sFrom Publish to live. Purge by key, then the edge refills.
  • 0 rebuildsFetch at request time and still answer fast, because the read is already cached.
  • RewarmWhen content falls out of cache, Capa puts it back before a visitor asks. On Pro and up.
Image API

Every size,
from one URL.

Upload once. Ask the CDN URL for the width, crop, pixel density and format you need, and get it back cached for a year at the edge. Replace the file and every size refreshes.

  • Resize and crop up to 4096 px, with fit to keep, fill or pad the box.
  • WebP and AVIF, with a quality dial and blur placeholders.
  • Rotated by EXIF, then stripped of metadata unless you keep it.
  • SVG made safe: sanitised as a file, rendered as a raster on request.
More on the Image API
cdn.capacms.com

/files/brand/hero.jpg?dpr=2&fit=cover&format=webp&height=450&width=800

An illustration of snowy peaks at dawn above a pine forest, as the Image API would serve it.

1600 × 900 px, cropped to fill, as WebP

Width
Pixel density
Crop
Format

What the formats cost

One 600 × 400 JPEG, asked for at 400 px wide. Measured on Capa’s image pipeline in September 2026.

  1. Source JPEGno transform7,243 B
  2. JPEG?width=4004,222 B
  3. AVIF?format=avif&width=4001,740 B
  4. WebP?format=webp&width=4001,380 B
  5. WebP, quality 50?format=webp&quality=50&width=400892 B
SDK and visual editing

Typed from your models.
Editable on the page.

@capacms/sdk reads REST and GraphQL with types generated from your schema, caches by tag in Next.js, and turns on visual editing when you hand the site to editors, clients or another team.

More on the SDK
app/blog/page.tsx
import { draftMode, headers } from "next/headers";
import { capaAttrs } from "@capacms/sdk/next";
import { graphql, tagsFor } from "@capacms/sdk/nextjs";
// written by capa-codegen --graphql
import { BlogIndexModels } from "./capa-graphql";

const BLOG_INDEX = `#graphql
  query BlogIndex($first: Int) {
    articles(first: $first, sort: [publishedAt_DESC]) {
      nodes { id model title author { name } }
    }
  }
`;

export default async function Blog() {
  const { data } = await graphql(BLOG_INDEX, { first: 5 }, {
    draftMode,
    headers,
    // articles, and authors for author { name }
    tags: tagsFor({ namespace: BlogIndexModels }),
    revalidate: 60,
  });
  return (
    <ul>
      {data?.articles?.nodes.map((a) => (
        <li key={a.id} {...capaAttrs(a, "title")}>{a.title}</li>
      ))}
    </ul>
  );
}

Hand it over. Let them click.

Tag a field with capaAttrs and render the overlay in edit mode. Editors click the text on their own site and Capa opens that field. Visitors get none of it: no tags, no overlay script.

More on visual editing
pnpm add @capacms/sdk@next
Webhooks

Hear about
every change.

Signed events for publishes, models, media and scheduled runs, sent to your endpoint over HTTPS. The SDK checks each delivery and revalidates the right cache tags.

  • instance.published
  • instance.unpublished
  • instance.deleted
  • model.updated
  • media.updated
  • publish.scheduled
  • publish.batch.completed
  • webhook.test
  • Signed with HMAC-SHA256 in Capa-Signature, with an Idempotency-Key that holds across retries.
  • Retried for hours, then paused only after 25 failures in a row over a day.
  • Redeliver any event from the log, or resume with a backfill.
More on webhooks
app/api/capa/route.ts
import { revalidateTag } from "next/cache";
import { revalidateFromWebhook } from "@capacms/sdk/nextjs";
import { verifyWebhookSignature } from "@capacms/sdk";

export async function POST(request: Request) {
  const raw = await request.text();
  const ok = await verifyWebhookSignature({
    payload: raw,
    header: request.headers.get("capa-signature") ?? "",
    secret: process.env.CAPA_WEBHOOK_SECRET!,
  });
  if (!ok) return new Response("bad signature", { status: 400 });

  await revalidateFromWebhook({
    payload: JSON.parse(raw),
    revalidateTag,
  });

  return new Response("ok");
}
  1. Attempt 1
  2. +1 min
  3. +5 min
  4. +30 min
  5. +2 h
  6. +6 h

Six attempts. The last lands 8 h 36 min after the first, so a receiver that is back inside a working day catches up on its own.

Also in the box

  • Scoped keys

    Live and test keys with scopes, allowed origins, expiry, and rotation with a grace window. A key can read one model and nothing else.

  • Every client, one login

    Each client is its own project with its own models, keys and members. Switch between them without signing out.

  • Explorer and dev sheet

    Try a query against real content in the admin, then copy the request behind any screen as fetch.

  • Errors that say what to do

    Every error has a code, a hint and a docs link. A limit you hit states the number you sent and the value that fits.

Start building
this afternoon.

Free to start. Bring your framework, your agent and your terminal.

pnpm add @capacms/sdk@next