> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.runtype.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.runtype.com/_mcp/server.

# Managing records programmatically

Create, update, edit in bulk, delete, import, and export Records from code with the Runtype API, the TypeScript SDK, the `runtype` CLI, or the Runtype MCP server. For the same tasks on the **Records** page in the dashboard, see [Creating and managing records](/user-guide/records/creating-and-managing-records).

## Before you begin

Make sure that you have the following:

* A Runtype API key with the **Records (Full)** permission. A key with only **Records (Read)** can list, get, and export Records, and write requests return `403`. For more information, see [Managing API keys](/user-guide/settings/managing-api-keys).
* Optional: the `runtype` CLI, signed in with `runtype login`. For setup, see [Authentication](/developer-guides/getting-started/authentication).

The examples read your API key from the `RUNTYPE_API_KEY` environment variable:

**`Set the API key`**

```bash title="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`**

```typescript title="Create the SDK client"
import { RuntypeClient } from '@runtypelabs/sdk'

const client = new RuntypeClient({ apiKey: process.env.RUNTYPE_API_KEY })
```

The following table shows which interface supports each operation:

| Operation                   | API | TypeScript SDK | CLI               | MCP server |
| --------------------------- | --- | -------------- | ----------------- | ---------- |
| Create, get, update, delete | Yes | Yes            | Yes               | Yes        |
| List                        | Yes | Yes            | Yes               | Yes        |
| Bulk edit, bulk delete      | Yes | Yes            | Yes               | Yes        |
| CSV import                  | Yes | Yes            | No                | No         |
| CSV export                  | Yes | No             | Up to 100 Records | No         |

## Create a Record

Send a type, a name, and an optional `metadata` object. The following examples create a `customer` Record:

#### cURL

```bash
curl --request POST https://api.runtype.com/v1/records \
  --header "Authorization: Bearer $RUNTYPE_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "type": "customer",
    "name": "Dana Kim",
    "metadata": { "email": "dana@example.com", "tier": "standard" }
  }'
```

#### TypeScript

```typescript
const record = await client.records.create({
  type: 'customer',
  name: 'Dana Kim',
  metadata: { email: 'dana@example.com', tier: 'standard' },
})
```

#### CLI

```bash
runtype records create -t customer -n "Dana Kim" \
  -m '{"email":"dana@example.com","tier":"standard"}'
```

The API returns `201` with the created Record, including its `id`. Save the ID to update or delete the Record later.

The same write rules apply as in the dashboard:

* Each combination of type and name is unique. A duplicate returns `409`.
* Runtype converts top-level metadata keys to lowercase snake\_case. The response includes a `warnings` array that lists each renamed key.
* Metadata that violates a Record type schema in `enforce` mode returns `422`. For more information, see [Defining a schema for a record type](/user-guide/records/defining-a-schema).

## List and get Records

To list Records, send `GET /v1/records`. Filter by `type`, and page through results with `limit` and `cursor`. `limit` accepts 1 to 100 and defaults to 25. The response includes `pagination.nextCursor` when more Records exist. The following examples list the first 100 `customer` Records:

#### cURL

```bash
curl --get https://api.runtype.com/v1/records \
  --header "Authorization: Bearer $RUNTYPE_API_KEY" \
  --data-urlencode "type=customer" \
  --data-urlencode "limit=100"
```

#### TypeScript

```typescript
const page = await client.records.from('customer').list({ limit: 100 })
```

`client.records.from('customer')` scopes the list to the `customer` type.

#### CLI

```bash
runtype records list --type customer --limit 100
```

To get one Record with its full metadata, send `GET /v1/records/RECORD_ID`, call `client.records.get(RECORD_ID)`, or run `runtype records get <record-id>`. Replace `RECORD_ID` or `<record-id>` with the Record ID.

To select Records by metadata values, pass a record filter. For the filter syntax, see [Filtering and searching records](/user-guide/records/filtering-and-searching-records).

## Update a Record

Send only the fields that you want to change: `name`, `type`, `metadata`, or `ownerId`. Fields that you omit keep their values.

> **Warning**
>
> An update that includes `metadata` replaces the whole `metadata` object. Include every metadata field that you want to keep. An update that omits `metadata` leaves it unchanged. To change a few fields and keep the rest, use a [bulk edit](#edit-records-in-bulk) with the Record ID instead.

The following examples change the `tier` field of one Record and keep its `email` field:

#### cURL

```bash
curl --request PUT https://api.runtype.com/v1/records/RECORD_ID \
  --header "Authorization: Bearer $RUNTYPE_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "metadata": { "email": "dana@example.com", "tier": "premium" }
  }'
```

#### TypeScript

```typescript
await client.records.update('RECORD_ID', {
  metadata: { email: 'dana@example.com', tier: 'premium' },
})
```

#### CLI

```bash
runtype records update <record-id> \
  -m '{"email":"dana@example.com","tier":"premium"}'
```

Replace `RECORD_ID` or `<record-id>` with the Record ID. A change of name or type that matches another Record returns `409` with `duplicate_record`. An update that replaces `metadata` or changes `type` returns `409` with `record_conflict` if another request changed the Record's metadata or type while the update was applied. The update changes nothing. Get the Record again and retry.

## Edit Records in bulk

A bulk edit sets or removes metadata fields on many Records in one request, and keeps the fields that you don't name. Choose the Records with one of the following targets:

* `recordIds`: an array of Record IDs.
* `recordFilter`: a record filter object. It matches Records of one type.
* `applyToAll`: `true` to edit every Record in your account.

Then describe the change with `updates`, `deleteFields`, or both. `updates` sets each named field to a value. `deleteFields` removes each named field. Both accept dot notation for nested fields, such as `address.city`. Bulk edit doesn't change names or types.

Set `dryRun` to `true` to preview the edit without saving it. The response lists each affected Record with the `before` and `after` values of the changed fields. The following examples preview an upgrade of every `standard` customer to `premium`:

#### cURL

```bash
curl --request POST https://api.runtype.com/v1/records/bulk-edit \
  --header "Authorization: Bearer $RUNTYPE_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "recordFilter": {
      "type": "customer",
      "where": { "field": "tier", "op": "eq", "value": "standard" }
    },
    "updates": { "tier": "premium" },
    "dryRun": true
  }'
```

#### TypeScript

```typescript
const preview = await client.records.bulkEdit({
  recordFilter: {
    type: 'customer',
    where: { field: 'tier', op: 'eq', value: 'standard' },
  },
  updates: { tier: 'premium' },
  dryRun: true,
})
```

#### CLI

```bash
runtype records bulk-edit --from-file upgrade-tier.json
```

The `upgrade-tier.json` file holds the same JSON body as the cURL example.

To apply the edit, send the same request with `dryRun` set to `false`. The response `recordIds` array lists the edited Records.

A bulk edit is all or nothing. If the result for any Record breaks a metadata limit or a Record type schema in `enforce` mode, the API rejects the whole edit and changes no Records. If another request changes the metadata or type of a target Record while the edit is applied, the API returns `409` with `record_conflict` and changes no Records. Send the edit again.

> **Warning**
>
> With `applyToAll` set to `true` and no other target, the edit changes every Record in your account. Run it with `dryRun` first.

## Delete Records

To delete one Record, send `DELETE /v1/records/RECORD_ID`, call `client.records.delete(RECORD_ID)`, or run `runtype records delete <record-id>`. The API returns `204`. In a terminal, the CLI asks you to confirm the delete.

To delete many Records, send their IDs to the bulk delete endpoint:

#### cURL

```bash
curl --request POST https://api.runtype.com/v1/records/bulk-delete \
  --header "Authorization: Bearer $RUNTYPE_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{ "ids": ["RECORD_ID_1", "RECORD_ID_2"] }'
```

#### TypeScript

```typescript
const result = await client.records.bulkDelete(['RECORD_ID_1', 'RECORD_ID_2'])
```

#### CLI

```bash
runtype records bulk-delete --from-file delete-ids.json
```

The `delete-ids.json` file holds the same JSON body as the cURL example.

Replace `RECORD_ID_1` and `RECORD_ID_2` with Record IDs. The response reports `deleted`, the `deletedIds`, and any `notFoundIds`. An ID that doesn't match a Record in your account appears in `notFoundIds`. An ID in the wrong format rejects the whole request with `400`.

Deletes through the API, SDK, CLI, and MCP server are permanent and offer no undo.

## Import Records from a CSV file

Upload a CSV file as `multipart/form-data` to create one Record for each row. The request takes the following fields:

* `file`: the CSV file. It needs a header row and at least one data row.
* `nameColumn`: the column that holds each Record name. The default is `name`.
* `typeColumn`: the column that holds each Record type. The default is `type`.
* `typeValue`: one type for every row. When you send `typeValue`, the API ignores `typeColumn`.

Column names match regardless of letter case. The following examples import a file of customers without a type column:

#### cURL

```bash
curl --request POST https://api.runtype.com/v1/records/upload-csv \
  --header "Authorization: Bearer $RUNTYPE_API_KEY" \
  --form "file=@customers.csv" \
  --form "nameColumn=name" \
  --form "typeValue=customer"
```

#### TypeScript

```typescript
import { readFile } from 'node:fs/promises'

const file = new File([await readFile('customers.csv')], 'customers.csv', {
  type: 'text/csv',
})
const result = await client.records.uploadCsv(file, {
  nameColumn: 'name',
  typeValue: 'customer',
})
```

The import stores every column as a metadata field, as the dashboard import does. An import can partly succeed. The API returns `201` with `created` (the number of Records created), `errors` (the number of rows that failed), and `errorDetails` (one message for each failed row, with its row number). Check `errors` before you treat the import as complete. For the reasons a row fails, see [Import Records from a CSV file](/user-guide/records/creating-and-managing-records#import-records-from-a-csv-file).

If the file has no rows, or a named column isn't in the header, the API returns `400` and creates no Records.

## Export Records as CSV

To download matching Records as a CSV file, send `GET /v1/records/export`. It accepts the same `type`, `search`, `ownerId`, and `recordFilter` query parameters as the list endpoint, and it returns every matching Record in one file. The following request saves every `customer` Record to `customers.csv`:

```bash
curl --get https://api.runtype.com/v1/records/export \
  --header "Authorization: Bearer $RUNTYPE_API_KEY" \
  --data-urlencode "type=customer" \
  --output customers.csv
```

The file has the columns `id`, `type`, `name`, `ownerId`, `createdAt`, and `updatedAt`, then one column for each metadata field. The `X-Record-Count` response header holds the number of exported Records. If no Records match, the API returns `204` with no body.

The `runtype records export` command writes a single page of Records to a JSON or CSV file. It exports up to 100 Records, and a `--limit` value greater than 100 fails. Its CSV format puts all metadata in one JSON column. To export more than 100 Records, use the API endpoint.

## Manage Records with the MCP server

The Runtype MCP server provides these tools for Records: `list_records`, `get_record`, `create_record`, `update_record`, `delete_record`, `bulk_edit_records`, and `bulk_delete_records`. The tools take snake\_case arguments, such as `record_ids`, `record_filter`, `delete_fields`, and `dry_run`. `bulk_edit_records` requires `dry_run`, so a client states whether each call is a preview. Like the API, `update_record` replaces the whole metadata object when you send `metadata`. The MCP server has no tools for CSV import or export. For setup, see [Runtype MCP server](/developer-guides/integrations/runtype-mcp-server).

## Troubleshoot Record requests

The following table lists common errors and how to fix them:

| Error                                                                       | Cause                                                                                            | Fix                                                                          |
| --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
| `403` with `Insufficient permissions`                                       | The API key lacks the permission for the operation.                                              | Give the key the **Records (Full)** permission.                              |
| `409` with `duplicate_record`                                               | Another Record has the same type and name.                                                       | Change the name, or update the existing Record.                              |
| `409` with `record_conflict`                                                | Another request changed the Record's metadata or type while the update or bulk edit was applied. | Get the Record again and retry.                                              |
| `422` on a create, update, or bulk edit                                     | The metadata violates the Record type schema in `enforce` mode.                                  | Fix the fields that the response names.                                      |
| `503` with `COLLECTION_LOOKUP_FAILED` on a create, update, or bulk edit     | Runtype could not load the Record type schema, so it refused the write.                          | Retry the request.                                                           |
| `400` with `Specify recordIds, conditions, recordFilter, or set applyToAll` | The bulk edit has no target.                                                                     | Add a target.                                                                |
| Metadata fields disappear after an update                                   | The update included a `metadata` object, which replaces the whole stored object.                 | Send every field that you want to keep, omit `metadata`, or use a bulk edit. |

## Next steps

* [Creating and managing records](/user-guide/records/creating-and-managing-records): manage Records on the **Records** page.
* [Filtering and searching records](/user-guide/records/filtering-and-searching-records): write the record filters that list, export, and bulk edit use.
* [Defining a schema for a record type](/user-guide/records/defining-a-schema): validate metadata on every write.
* [Using records in flows](/user-guide/records/using-records-in-flows): read and write Records from a Flow.
* [Create record](/api-reference/runtype-api/records/create-record): review every request and response field in the API reference.