Defining a schema for a record type

Use collections to define the metadata shape for a record type. Register a record type, attach a constrained JSON Schema, and choose how the API handles writes that do not conform. The default validation mode is off, so registration does not change record writes until you choose warn or enforce.

What a collection is

Register one collection for each record type. Set the collection’s slug to the record’s type value. A collection stores the following information:

  • Display metadata: displayName, description, and icon.
  • An optional schema: defines the fields in metadata.
  • A validation mode: off, warn, or enforce. The default is off.
  • Schema history: each schema change increments schemaVersion and adds a history entry.

Record writes for an unregistered type or a collection without a schema continue without collection-schema validation.

Schema dialect

Use the following constrained JSON Schema dialect for collection schemas:

  • Define the root as an object with properties, an optional required list, and an optional additionalProperties flag.
  • Use string, number, boolean, array, or object property types.
  • Define one nested object level. Object properties can contain scalar properties. Array items can be scalar properties or one nested object.
  • Use enum, format, and maxLength with string properties. Supported formats are date-time, email, uri, and record-id.
  • Use minimum and maximum with number properties.
  • Leave additionalProperties unset to accept extra keys, or set it to false to reject them.
  • Do not use $ref, oneOf, anyOf, allOf, if, then, patternProperties, or pattern. The API returns issue details for unsupported or invalid schema parts.

The following JSON defines a schema for customer metadata:

Example schema
1{
2 "type": "object",
3 "properties": {
4 "tier": { "type": "string", "enum": ["free", "pro", "enterprise"] },
5 "mrr": { "type": "number" },
6 "signed_up_at": { "type": "string", "format": "date-time" },
7 "is_active": { "type": "boolean" }
8 },
9 "required": ["tier"]
10}

Choose a validation mode

Use the following table to choose how the API handles a nonconforming write:

ModeNonconforming writeRecord stamp
offProceeds without schema validationUnchanged
warnProceeds and returns schemaWarningsschemaValid: false
enforceReturns 422 with schema_validation_failed errorsNot written

In warn and enforce modes, conforming writes set schemaValid to true. Filter records with the schemaValid pseudo-field to find nonconforming records.

Follow an adoption path

For an existing record type, follow these steps:

  1. Register the collection. The collection starts in mode off, so record writes do not change.
  2. Infer a schema from existing records. The API samples records in descending updatedAt order and returns a proposal with per-field confidence and sample values. It does not save the proposal.
  3. Save the schema in warn mode. Nonconforming writes proceed, return schemaWarnings, and set schemaValid to false.
  4. Dry-run existing records. The API checks up to 10,000 records and returns counts, examples, and a truncated value.
  5. Switch to enforce. The update response includes an enforceCheck summary for up to 1,000 existing records. Review the summary and fix any failing records.

Use collections

Use collections through the Runtype API, the TypeScript SDK, the runtype CLI, the Runtype MCP server, or Code Mode. The Runtype MCP server implements the Model Context Protocol (MCP). The following examples use the customers record type.

REST

Set the RUNTYPE_API_KEY environment variable before you send requests:

Set the API key
$export RUNTYPE_API_KEY=YOUR_API_KEY

Replace YOUR_API_KEY with your Runtype API key. Each subsequent request reads RUNTYPE_API_KEY.

To register the customers collection with the default off mode, send this request:

Create the collection
$curl --request POST https://api.runtype.com/v1/collections \
> --header "Authorization: Bearer $RUNTYPE_API_KEY" \
> --header "Content-Type: application/json" \
> --data '{
> "slug": "customers",
> "displayName": "Customers"
> }'

To propose a schema from 500 customers records ordered by updatedAt, send this request:

Infer a schema from existing records
$curl --request POST https://api.runtype.com/v1/collections/customers/infer-schema \
> --header "Authorization: Bearer $RUNTYPE_API_KEY" \
> --header "Content-Type: application/json" \
> --data '{ "sample": 500 }'

The response includes schema, fields, sampledRecords, totalRecords, and skippedKeys. Review the proposal before you save it.

To save the schema in warn mode, send this request:

Save the schema in warn mode
$curl --request PATCH https://api.runtype.com/v1/collections/customers \
> --header "Authorization: Bearer $RUNTYPE_API_KEY" \
> --header "Content-Type: application/json" \
> --data '{
> "schema": {
> "type": "object",
> "properties": {
> "tier": { "type": "string" }
> },
> "required": ["tier"]
> },
> "validationMode": "warn"
> }'

To check existing records against the saved schema without writing, send this request:

Dry-run existing records
$curl --request POST https://api.runtype.com/v1/collections/customers/validate-existing \
> --header "Authorization: Bearer $RUNTYPE_API_KEY"

To switch the collection to enforce mode, send this request:

Enforce
$curl --request PATCH https://api.runtype.com/v1/collections/customers \
> --header "Authorization: Bearer $RUNTYPE_API_KEY" \
> --header "Content-Type: application/json" \
> --data '{ "validationMode": "enforce" }'

When a write violates the enforced schema, the API returns this 422 response:

422 response
1{
2 "error": "schema_validation_failed",
3 "collection": "customers",
4 "schemaVersion": 2,
5 "details": [
6 {
7 "code": "REQUIRED_FIELD_MISSING",
8 "message": "Instance does not have required property \"tier\".",
9 "field": "tier",
10 "path": "metadata.tier"
11 }
12 ]
13}

TypeScript SDK

Use the SDK methods to create, infer, save, validate, and enforce a collection:

TypeScript SDK
1import { RuntypeClient } from '@runtypelabs/sdk'
2
3const client = new RuntypeClient({
4 apiKey: process.env.RUNTYPE_API_KEY,
5})
6
7await client.collections.create({
8 slug: 'customers',
9 displayName: 'Customers',
10})
11
12const proposal = await client.collections.inferSchema('customers', {
13 sample: 500,
14})
15if (proposal.schema) {
16 await client.collections.update('customers', {
17 schema: proposal.schema,
18 validationMode: 'warn',
19 })
20}
21
22const dryRun = await client.collections.validateExisting('customers')
23if (dryRun.failed === 0) {
24 const result = await client.collections.update('customers', {
25 validationMode: 'enforce',
26 })
27 console.log(result.enforceCheck)
28}

Set RUNTYPE_API_KEY in your environment before you run this example.

CLI

Run the following commands to follow the same workflow with the runtype CLI:

CLI
$runtype collections create --slug customers --name "Customers"
$runtype collections infer customers --output customers-schema.json
$runtype collections update customers \
> --schema-file customers-schema.json \
> --mode warn
$runtype collections validate customers
$runtype collections update customers --mode enforce
$runtype collections list --count

MCP

Call the following tools on the Runtype MCP server to manage collections: list_collections, get_collection, create_collection, update_collection, delete_collection, infer_collection_schema, validate_collection_records, and get_collection_types. Code Mode exposes the corresponding methods listCollections, getCollection, createCollection, updateCollection, deleteCollection, inferCollectionSchema, validateExistingRecords, and getCollectionTypegen.

Generate TypeScript types

Generate a TypeScript declaration file after you add a schema. The file supplies types for metadata on each schematized collection.

REST

Request GET /v1/collections/types.d.ts to receive a text/plain declaration file. The file contains one interface per schematized collection and a declare module '@runtypelabs/sdk' block that augments RecordCollections.

CLI

Use the runtype CLI to write the declarations to a file:

CLI
$runtype records typegen --output runtype-records.d.ts

Commit the generated file and run the same command in CI. Diff the result to detect collection-schema drift:

CI drift check
$runtype records typegen --output runtype-records.d.ts
$git diff --exit-code runtype-records.d.ts

Use typed record access

Include the generated file in the paths that tsconfig includes. The declare module block augments the SDK’s RecordCollections map. Use client.records.from('customers') to pin record operations to the collection slug and type metadata:

Typed records
1const customers = client.records.from('customers')
2
3// metadata is typed as the customers collection schema
4await customers.create({
5 name: 'Example Organization',
6 metadata: { tier: 'pro' },
7})
8
9const record = await customers.get('RECORD_ID')

Replace RECORD_ID with a record ID. For an unregistered slug, client.records.from(slug) uses Record<string, unknown> for metadata.

Fetch the declaration file with get_collection_types on the MCP server or getCollectionTypegen() in Code Mode. The runtype://records/collections MCP resource exposes each collection’s schema. The runtype_record_upsert, runtype_record_get, and runtype_record_list tool descriptions include field names, types, and required flags for schematized collections.

Evolve a schema

Classify schema changes when you save them:

  • Additive changes include new optional fields and widened enums.
  • Breaking changes include adding a schema to a schemaless collection, removed fields, new required fields, changed types, narrowed enums, and tightened constraints. The API rejects a breaking change when the resulting mode is enforce with BREAKING_SCHEMA_CHANGE_REQUIRES_WARN_MODE.

To apply a breaking change, set the mode to warn, migrate records, update the schema, and set the mode to enforce.

Each schema change increments schemaVersion and adds a history entry. Request GET /v1/collections/customers?includeHistory=true to return the history.

Collection slug values are immutable. To rename a record type, create another collection and migrate records to that type.

Delete a collection

Delete a collection only when you no longer need its schema. Deletion removes the registration and leaves records of that type unchanged. The records return to schemaless behavior. Delete records with a separate operation.

Deleting a collection permanently discards its schema and version history.

Next steps

Continue with one of these topics: