Docs

Schema: /v2/schema

Your project's models, fields and relations as JSON, and as ready-made TypeScript types, for code generation.

View as Markdown

Two routes describe the content model of the project your key belongs to. GET /v2/schema returns every model, field and relation as JSON, with a checksum. GET /v2/schema/types returns the same models as TypeScript interfaces.

capa-codegen reads these routes when you run it with a legacy key. See Codegen.

Keys and hosts

Both routes take a legacy key (pk_, sk_, or an older key with no prefix) in x-api-key. Any permission reads them, read included. A cap_ key is refused with 401 {"error":"Invalid API key"}, as on every legacy route. See Keys and scopes.

Send them to https://api.capacms.com. They are not served through the CDN, unlike /v2/api and /v3/api.

StatusBodyWhen
401{"error":"API key required"}no x-api-key header
401{"error":"Invalid API key"}an unknown, inactive or cap_ key
402{"success":false,"error":"…"}the project has no active subscription
500{"error":"Failed to fetch schema","message":"…"}something failed on our side

GET /v2/schema

curl https://api.capacms.com/v2/schema -H "x-api-key: $CAPA_KEY"
{
  "models": [
    {
      "id": "2c8b0a11-7f4e-4d2a-9c31-51a2b3c4d5e6",
      "namespace": "blog_post",
      "modelName": "Blog Post",
      "isSingleInstance": false,
      "allowDynamicFields": false,
      "category": { "id": "6a1d2c3b-4e5f-4a7b-8c9d-0e1f2a3b4c5d", "name": "Editorial" },
      "fields": [
        {
          "id": "41a2b3c4-d5e6-4f70-8192-a3b4c5d6e7f8",
          "namespace": "title",
          "name": "title",
          "type": "string",
          "arrayType": null,
          "required": true,
          "relationRef": null,
          "enumValues": null,
          "directEmbed": false,
          "sortOrder": 0
        },
        {
          "id": "9e8d7c6b-5a4f-4e3d-2c1b-0a9f8e7d6c5b",
          "namespace": "author",
          "name": "Author",
          "type": "relation",
          "arrayType": null,
          "required": false,
          "relationRef": "author",
          "enumValues": null,
          "directEmbed": false,
          "sortOrder": 1
        }
      ]
    }
  ],
  "relations": [
    { "fromModel": "blog_post", "fromField": "author", "toModel": "author", "type": "one" }
  ],
  "checksum": "3f6c1b4e2a714d0e",
  "generatedAt": "2026-10-01T14:02:11.004Z"
}

models holds every model of the project that is not deleted, sorted by modelName. Each model's fields are in their sortOrder.

category is null for a model with no category. arrayType is set only on an array field, relationRef only on a relation, and enumValues only on an enum with values.

relations lists every relation field once. type is one for a relation field and many for an array of relations. toModel is the target model's namespace, or null when the target no longer exists.

The checksum

checksum is 16 hexadecimal characters, a hash of models and relations. It changes whenever anything in models or relations does. The same value is the response's ETag, so you can ask whether anything moved without downloading the schema again:

curl -i https://api.capacms.com/v2/schema \
  -H "x-api-key: $CAPA_KEY" \
  -H 'If-None-Match: "3f6c1b4e2a714d0e"'
# HTTP/1.1 304 Not Modified

The response is sent with Cache-Control: private, max-age=60. It is specific to your key's project, so no shared cache stores it.

GET /v2/schema/types

curl https://api.capacms.com/v2/schema/types -H "x-api-key: $CAPA_KEY" -o capa-types.ts

The body is TypeScript, sent as text/plain with Content-Disposition: attachment; filename="capa-types.ts". It starts with four shared interfaces, CapaImage, CapaVideo, CapaFile and CapaInstance, then one interface per model, named after its namespace in PascalCase:

export interface BlogPost {
  title: string;
  author?: Author;
  tags?: string[];
  views?: number;
}

A required field has no ?. Field types map like this:

Field typeTypeScript
string, markdown, html, code, color, datestring
numbernumber
true_falseboolean
image, video, fileCapaImage, CapaVideo, CapaFile
enum with valuesa union of the values, such as 'draft' | 'live'
relationthe target model's interface, or unknown when the target no longer exists
arraythe item type followed by []
anything elseunknown

Which to use

For new code on /api/, run capa-codegen with a cap_ key instead. It writes the same interfaces from the key's GraphQL schema, with no project id. See Codegen.