Skip to navigation

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.

Before you begin

Make sure that you have the following:

  • A Runtype API key with the RECORDS:READ and RECORDS:WRITE permissions, or RECORDS:*. Without them, collection requests return 403. For more information, see Managing API keys.
  • Optional: existing records of the target type, if you plan to infer a schema from them.

Manage collections through the Runtype API, the TypeScript SDK, the runtype CLI, or the Runtype Model Context Protocol (MCP) server. The dashboard does not have a collections editor.

What a collection is

A collection is the registration of one 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.

A slug starts with a letter or digit and contains only letters, digits, underscores, dots, and hyphens, up to 255 characters. The slugs client_conversation and types.d.ts are reserved. A record type whose name does not fit these rules cannot have a collection.

Record writes for an unregistered type, or for a collection without a schema, continue without 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.
  • Define no more than 200 properties at the root.
  • Name properties with lowercase letters, digits, and underscores, 1 to 64 characters.
  • Use string, number, boolean, array, or object property types. Each property accepts an optional description.
  • 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. An enum holds up to 100 values. 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 record-id format accepts any string. The API does not check that the referenced record exists.

The API normalizes metadata keys before it validates a write from the Runtype API, the SDK, the CLI, or an agent’s record tools. It lowercases each top-level key, replaces each run of characters other than letters and digits with _, and removes leading and trailing underscores. Declare the normalized names in your schema. For example, a write with signedUpAt is stored and validated as signedupat, and a write with signed-up-at as signed_up_at. Flow record steps store and validate keys exactly as the flow writes them, so use lowercase snake_case keys in flow output.

The following JSON defines a schema for customer metadata:

Example schema
{
"type": "object",
"properties": {
"tier": { "type": "string", "enum": ["free", "pro", "enterprise"] },
"mrr": { "type": "number", "description": "Monthly recurring revenue in USD" },
"signed_up_at": { "type": "string", "format": "date-time" },
"is_active": { "type": "boolean" }
},
"required": ["tier"]
}

Choose a validation mode

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

ModeNonconforming writeschemaValid on the record
offProceeds without schema validationUnchanged
warnProceeds and returns schemaWarningsfalse
enforceReturns 422 with schema_validation_failed errorsNot written

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

In warn mode, a record create or update response includes a schemaWarnings array. Each entry has the same field, code, and message as a 422 detail:

Warn-mode create response (excerpt)
{
"id": "RECORD_ID",
"type": "customers",
"schemaValid": false,
"schemaWarnings": [
{
"field": "tier",
"code": "REQUIRED_FIELD_MISSING",
"message": "Instance does not have required property \"tier\"."
}
]
}

Where validation applies

Every write path validates records against the collection schema. The following table shows what each path does with a nonconforming write:

Write pathIn enforce modeIn warn mode
Create or update a recordReturns 422 and writes nothingWrites and returns schemaWarnings
Bulk editRejects the whole edit with 422, adding recordId and recordName for the failing recordWrites and returns schemaWarnings per record as { recordId, errors }
CSV importCounts the row as failed and imports the other rowsImports the row
Upsert Record and Update Record flow stepsWrites no record. With errorHandling unset, the step continues with its defaultValue. Set errorHandling to fail to stop the flow.Writes and adds schemaWarnings to the step metadata
Agent record toolsReturns an error that contains SCHEMA_VALIDATION_FAILEDWrites and returns schemaWarnings

If Runtype cannot load the collection schema, every write path refuses the write in any mode, including for a record type with no collection. The following table shows what each path returns:

Write pathWhen the schema lookup fails
Create or update a recordReturns 503 with code COLLECTION_LOOKUP_FAILED and writes nothing
Bulk editRejects the whole edit with 503, adding recordId and recordName
CSV importCounts the row as failed with a COLLECTION_LOOKUP_FAILED error and imports the other rows
Upsert Record and Update Record flow stepsWrites no record. errorHandling controls continuation, as it does in enforce mode
Agent record toolsReturns an error that contains COLLECTION_LOOKUP_FAILED

A lookup failure is temporary. Retry the write.

A record update is validated only when it changes metadata or type. Moving a record into an enforced record type with a type change is validated against that type’s schema.

Adopt a schema for an existing record type

Follow these tasks in order. The examples use the customers record type. They read your API key from the RUNTYPE_API_KEY environment variable:

Set the API key
export RUNTYPE_API_KEY=YOUR_API_KEY

Replace YOUR_API_KEY with your Runtype API key. The TypeScript examples use this client:

Create the SDK client
import { RuntypeClient } from '@runtypelabs/sdk'
const client = new RuntypeClient({ apiKey: process.env.RUNTYPE_API_KEY })

For a new record type with no existing records, you can pass schema and validationMode when you create the collection, and skip inference.

Register the collection

Register the customers collection. It starts in off mode, so record writes do not change:

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" }'

Infer a schema from existing records

Request a schema proposal. The API samples records by most recent updatedAt and saves nothing. sample accepts 1 to 1,000 and defaults to 500. You can infer a schema before or after you register the collection.

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 the following fields:

  • schema: the proposed schema, or null if the API cannot infer any field, for example because the type has no records.
  • fields: each field’s type, confidence, presence, and sample values.
  • sampledRecords and totalRecords: how many records the API read and how many exist.
  • skippedKeys: metadata keys that the proposal leaves out, because the key name is not lowercase letters, digits, and underscores or because the values do not map to a supported type.

Review the proposal before you save it. Rename any skipped key that the schema needs.

Save the schema in warn mode

Save the schema and set the mode to warn. Nonconforming writes proceed, return schemaWarnings, and set schemaValid to false.

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"
}'

Dry-run existing records

Check existing records against the saved schema without writing anything. The API checks up to 10,000 records and returns checked, failed, truncated, and failing examples. If truncated is true, the API did not check every record.

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

To test a proposed schema before you save it, send it as schema in the request body. In the CLI, pass --schema-file.

Update the failing records, then run the dry run again until failed is 0.

Switch to enforce mode

Set the mode to enforce. The response includes an enforceCheck summary of up to 1,000 existing records:

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

After you switch, a write that violates the schema returns this 422 response:

422 response
{
"error": "schema_validation_failed",
"collection": "customers",
"schemaVersion": 2,
"details": [
{
"code": "REQUIRED_FIELD_MISSING",
"message": "Instance does not have required property \"tier\".",
"field": "tier",
"path": "metadata.tier"
}
]
}

To list collections with their record counts, run runtype collections list --count.

Manage collections with the MCP server

The Runtype MCP server provides these tools for collections: list_collections, get_collection, create_collection, update_collection, delete_collection, infer_collection_schema, validate_collection_records, and get_collection_types. The Code Mode MCP server, which runs JavaScript against the Runtype API, exposes the matching methods listCollections, getCollection, createCollection, updateCollection, deleteCollection, inferCollectionSchema, validateExistingRecords, and getCollectionTypegen. The runtype://records/collections MCP resource lists each collection’s schema. For more information, see Runtype MCP server.

Generate TypeScript types

Generate a TypeScript declaration file after you add a schema. The file contains one interface for the metadata of each collection that has a schema, and a declare module '@runtypelabs/sdk' block that augments RecordCollections.

To write the declarations to a file, run this CLI command:

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

You can also get the declarations as text in the following ways:

  • Send GET /v1/collections/types.d.ts. The response is text/plain.
  • Call client.collections.typegen() in the TypeScript SDK.
  • Call get_collection_types on the Runtype MCP server, or getCollectionTypegen() in Code Mode.

Commit the generated file and run the same command in CI. Diff the result to detect 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. Use client.records.from('customers') to pin record operations to the collection slug and type metadata:

Typed records
const customers = client.records.from('customers')
// metadata is typed as the customers collection schema
await customers.create({
name: 'Example Organization',
metadata: { tier: 'pro' },
})
const 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.

How agents see your schema

The Upsert Record, Get Record, and List Records built-in tools (runtype_record_upsert, runtype_record_get, and runtype_record_list) list the field names, types, and required flags of each collection that has a schema in their tool descriptions. For more information, see Built-in tools.

Evolve a schema

When you save a schema, the API classifies the change as additive or breaking:

  • Additive changes include new optional fields, widened enums, and removing the schema.
  • Breaking changes include adding a schema to a schemaless collection, removed fields, new required fields, changed types, narrowed enums, closing additionalProperties, and tightened constraints.

The update response returns the collection under collection. When the schema changes, it also returns schemaChange:

Update response (excerpt)
{
"collection": { "slug": "customers", "schemaVersion": 3, "validationMode": "warn" },
"schemaChange": {
"kind": "breaking",
"breakingChanges": ["mrr: property became required"]
}
}

The API rejects a breaking change when the resulting mode is enforce:

400 response
{
"error": "This schema change is breaking; drop validationMode to \"warn\", migrate existing records, then re-enable \"enforce\"",
"code": "BREAKING_SCHEMA_CHANGE_REQUIRES_WARN_MODE",
"details": { "breakingChanges": ["mrr: property became required"] }
}

To apply a breaking change to an enforced collection, follow these steps:

  1. Send the updated schema with validationMode set to warn in one update request.
  2. Update the records that fail the updated schema.
  3. Dry-run existing records until failed is 0 and truncated is false.
  4. Set validationMode to enforce.

To remove a schema, send "schema": null.

Each schema change increments schemaVersion and adds a history entry with version, schema, changedBy, changeKind, and createdAt. To return the history, send GET /v1/collections/customers?includeHistory=true or run runtype collections get customers --history.

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

Troubleshoot collection errors

Collection requests return an error body with error, code, and sometimes details. The following table lists the common codes:

CodeStatusCauseFix
BREAKING_SCHEMA_CHANGE_REQUIRES_WARN_MODE400A breaking schema change while the mode is enforceSend the change with validationMode set to warn
CONCURRENT_MODIFICATION409Another request changed the collection at the same timeGet the collection again and retry the update
NO_SCHEMA_TO_VALIDATE400A dry run with no saved schema and no schema in the bodySave a schema or send one in the request body
SCHEMA_INVALID400The schema does not follow the dialect; details lists issuesFix the schema parts that details names
SLUG_CONFLICT409A collection with this slug existsUpdate the existing collection
SLUG_IMMUTABLE400An update tried to change the slugCreate another collection for the renamed record type
SLUG_INVALID400The slug does not match the slug rulesUse letters, digits, underscores, dots, and hyphens
SLUG_RESERVED400The slug is client_conversation or types.d.tsChoose another record 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.

To delete the customers collection, send this request. The API returns { "success": true }:

Delete the collection
curl --request DELETE https://api.runtype.com/v1/collections/customers \
--header "Authorization: Bearer $RUNTYPE_API_KEY"

In the CLI, run runtype collections delete customers. On the Runtype MCP server, call delete_collection.

Next steps

Continue with one of these topics: