# Primitives

> Create, list, rename and delete primitives; snapshots, restore, fork, pause, stats and export.

Source: https://useprimitives.com/docs/api/primitives

A primitive is one database, filesystem, queue, memory store, vault, key-value store or object store inside a group. These endpoints create, find and manage primitives; their data is read and written through the per-type endpoints. See [Groups](/docs/concepts/groups#primitives).

Every primitive has an immutable `id`, a 26-character lowercase ULID, and every endpoint below addresses it by that id. Renaming a primitive never changes its id. A primitive also has a `type` (`db`, `fs`, `queue`, `memory`, `vault`, `kv` or `storage`), a `name` that's unique per group and type, and a `group`. Names are 1–63 characters from `a-z`, `0-9`, `-`, `_` and `.`, starting with a letter or digit. Primitives can't move between groups; fork one into another group instead.

| Method | Path | Description |
| --- | --- | --- |
| `POST` | `/v1/primitives` | Create a primitive |
| `GET` | `/v1/primitives` | List primitives |
| `GET` | `/v1/primitives/{id}` | Get a primitive |
| `PATCH` | `/v1/primitives/{id}` | Rename a primitive |
| `DELETE` | `/v1/primitives/{id}` | Delete a primitive |
| `POST` | `/v1/primitives/{id}/snapshots` | Create a snapshot |
| `GET` | `/v1/primitives/{id}/snapshots` | List snapshots |
| `DELETE` | `/v1/primitives/{id}/snapshots/{snapshot}` | Delete a snapshot |
| `POST` | `/v1/primitives/{id}/restore` | Restore a snapshot |
| `POST` | `/v1/primitives/{id}/fork` | Fork a primitive |
| `POST` | `/v1/primitives/{id}/pause` | Pause a primitive |
| `POST` | `/v1/primitives/{id}/resume` | Resume a primitive |
| `GET` | `/v1/primitives/{id}/stats` | Get a primitive's stats |
| `GET` | `/v1/primitives/{id}/export` | Export a primitive |
| `GET` | `/v1/primitives/{id}/audit` | Read a primitive's audit trail |

Snapshots, restore, fork, pause and stats belong to one primitive, not to the group. A primitive outside a scoped API key's group answers `404 not_found`, so its existence isn't leaked. For the tool each endpoint needs on a scoped key, see [Tool allowlists](/docs/api/authentication#tool-allowlists).

Sections marked **Account.** reject scoped API keys with `403 forbidden`. See [Authentication](/docs/api/authentication).

## Create a primitive

`POST /v1/primitives`

Creates an empty primitive in a group and returns it with its new `id`. Every later call uses that id. Creating a primitive isn't an operation, but it adds storage.

The group must exist (`404 not_found`). If the group already has a primitive of that type and name, it answers `409 already_exists`. An unknown `type` or invalid name is `400 invalid_request`. On Starter, the 100-primitive cap and the storage cap answer `429 plan_limit`. On a scoped API key, `group` must be the key's own group (`403 group_mismatch` otherwise).

Key tool: `primitive_create`.

**Body**

| Parameter | Type | Description |
| --- | --- | --- |
| `name` | string | Required. Unique within the group for this type. |
| `type` | string | Required. `db`, `fs`, `queue`, `memory`, `vault`, `kv` or `storage`. |
| `group` | string | Required. The group name. |

```bash
curl -X POST https://api.useprimitives.com/v1/primitives \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "orders", "type": "db", "group": "agent-42"}'
```

Returns `201` with the PrimitiveInfo:

```json
{
  "id": "01jbz3k8m5q2w7x9v4n6t0r1ce",
  "group": "agent-42",
  "type": "db",
  "name": "orders",
  "created_at": 1760486400000,
  "paused": false,
  "size_bytes": 0,
  "forked_from": null
}
```

## List primitives

`GET /v1/primitives`

Lists primitives across the workspace. Paged; see [Pagination](/docs/api#pagination). The cursor is a primitive id. A scoped API key sees only its own group, whatever `group` says. Listing primitives isn't an operation.

**Query parameters**

| Parameter | Type | Description |
| --- | --- | --- |
| `type` | string | Only primitives of this type. An unknown type is `400 invalid_request`. |
| `group` | string | Only primitives in this group. |
| `limit` | integer | Page size. Default `50`, max `200`. |
| `cursor` | string | The `next_cursor` of the previous page. |

```bash
curl "https://api.useprimitives.com/v1/primitives?group=agent-42&type=db" \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY"
```

```json
{
  "data": [
    {
      "id": "01jbz3k8m5q2w7x9v4n6t0r1ce",
      "group": "agent-42",
      "type": "db",
      "name": "orders",
      "created_at": 1760486400000,
      "paused": false,
      "size_bytes": 24576,
      "forked_from": null
    }
  ],
  "next_cursor": null
}
```

## Get a primitive

`GET /v1/primitives/{id}`

Returns one primitive (PrimitiveInfo). Any key that reaches its group can call it. Works while the primitive is paused.

```bash
curl https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY"
```

```json
{
  "id": "01jbz3k8m5q2w7x9v4n6t0r1ce",
  "group": "agent-42",
  "type": "db",
  "name": "orders",
  "created_at": 1760486400000,
  "paused": false,
  "size_bytes": 24576,
  "forked_from": null
}
```

## Rename a primitive

`PATCH /v1/primitives/{id}`

**Account.** Renames the primitive within its group. The id stays the same. Scoped API keys get `403 forbidden`.

If the name is taken in the group for this type, it answers `409 already_exists`, and an invalid name is `400 invalid_request`. A body with `group` is `400 invalid_request`: primitives can't move between groups, so fork it into the other group instead. A body without `name` changes nothing and returns the primitive as it is.

**Body**

| Parameter | Type | Description |
| --- | --- | --- |
| `name` | string | The new name. |

```bash
curl -X PATCH https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "orders-v1"}'
```

```json
{
  "id": "01jbz3k8m5q2w7x9v4n6t0r1ce",
  "group": "agent-42",
  "type": "db",
  "name": "orders-v1",
  "created_at": 1760486400000,
  "paused": false,
  "size_bytes": 24576,
  "forked_from": null
}
```

## Delete a primitive

`DELETE /v1/primitives/{id}`

**Account.** Deletes the primitive and its data. This can't be undone. Scoped API keys get `403 forbidden`.

```bash
curl -X DELETE https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY"
```

Returns `204` with no body.

## Create a snapshot

`POST /v1/primitives/{id}/snapshots`

Takes a restore point of this primitive. A primitive that was never written snapshots as the empty state. Works while the primitive is paused.

The body is optional. A label that breaks the naming rules is `400 invalid_request`. On Starter it answers `403 plan_limit`.

Key tool: `primitive_snapshot`.

**Body**

| Parameter | Type | Description |
| --- | --- | --- |
| `name` | string | A label, usable instead of the id when restoring, forking or deleting. Same rules as primitive names. |

```bash
curl -X POST https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/snapshots \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "before-deploy"}'
```

Returns `201` with the SnapshotInfo:

```json
{
  "id": "01k7c5r3t5v7x9z1b3d5f7h9j1",
  "name": "before-deploy",
  "created_at": 1760486500000,
  "primitive": "01jbz3k8m5q2w7x9v4n6t0r1ce"
}
```

## List snapshots

`GET /v1/primitives/{id}/snapshots`

Lists this primitive's snapshots, newest first. Any key that reaches the group can call it, and it works on Starter. Every snapshot comes back in one response, so `next_cursor` is always `null`.

```bash
curl https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/snapshots \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY"
```

```json
{
  "data": [
    {
      "id": "01k7c5r3t5v7x9z1b3d5f7h9j1",
      "name": "before-deploy",
      "created_at": 1760486500000,
      "primitive": "01jbz3k8m5q2w7x9v4n6t0r1ce"
    },
    {
      "id": "01k7c4w2y4a6c8e0g2j4l6n8q0",
      "name": null,
      "created_at": 1760486450000,
      "primitive": "01jbz3k8m5q2w7x9v4n6t0r1ce"
    }
  ],
  "next_cursor": null
}
```

## Delete a snapshot

`DELETE /v1/primitives/{id}/snapshots/{snapshot}`

Deletes one snapshot of this primitive. `{snapshot}` is the snapshot's label or id. A snapshot that doesn't exist answers `404 not_found`.

Key tool: `primitive_snapshot`.

```bash
curl -X DELETE https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/snapshots/before-deploy \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY"
```

Returns `204` with no body.

## Restore a snapshot

`POST /v1/primitives/{id}/restore`

Replaces this primitive's data with the snapshot's copy and returns the snapshot. The group's other primitives are untouched, and a database keeps its branches. Works while the primitive is paused.

A snapshot that doesn't exist answers `404 not_found`. On Starter it answers `403 plan_limit`.

Key tool: `primitive_snapshot`.

**Body**

| Parameter | Type | Description |
| --- | --- | --- |
| `snapshot` | string | Required. A snapshot label or id. `name` is accepted as an alias. |

```bash
curl -X POST https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/restore \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"snapshot": "before-deploy"}'
```

```json
{
  "id": "01k7c5r3t5v7x9z1b3d5f7h9j1",
  "name": "before-deploy",
  "created_at": 1760486500000,
  "primitive": "01jbz3k8m5q2w7x9v4n6t0r1ce"
}
```

## Fork a primitive

`POST /v1/primitives/{id}/fork`

Copies this primitive, at one consistent moment, into a new primitive with a new id. Works while the primitive is paused.

The copy's `forked_from` records the source, and its `group` says where it landed. If the target group already has a primitive of that type and name, it answers `409 already_exists`. A copy with the source's own name in the source's own group, or an invalid name, is `400 invalid_request`. A target group that doesn't exist answers `404 not_found`. On Starter it answers `403 plan_limit`. A scoped API key can fork only into its own group; another `group` is `403 forbidden`.

The body is optional.

Key tool: `primitive_fork`.

**Body**

| Parameter | Type | Description |
| --- | --- | --- |
| `name` | string | Name of the copy. Default `<name>-fork-<6 chars>`. |
| `group` | string | Target group name. Default: the source's group. |
| `snapshot` | string | Fork from this snapshot (label or id) instead of the live data. |

```bash
curl -X POST https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/fork \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "orders-try", "snapshot": "before-deploy"}'
```

Returns `201` with the copy's PrimitiveInfo:

```json
{
  "id": "01k7c6b4d6f8h0k2m4p6r8t0v2",
  "group": "agent-42",
  "type": "db",
  "name": "orders-try",
  "created_at": 1760486600000,
  "paused": false,
  "size_bytes": 24576,
  "forked_from": {
    "id": "01jbz3k8m5q2w7x9v4n6t0r1ce",
    "group": "agent-42",
    "type": "db",
    "name": "orders",
    "snapshot": "01k7c5r3t5v7x9z1b3d5f7h9j1"
  }
}
```

What the copy gets depends on the type:

- **Database**: the data as it was at the fork. Branches are not copied.
- **Filesystem**: every file. Files over 64 KiB are shared, not copied.
- **Queue**: every queue and job. Leases are released, so leased jobs are ready again in the copy.
- **Vault**: every secret and grant, re-encrypted under the target group's key.
- **Memory** and **KV**: everything, as it was at the fork.
- **Storage**: every object. The copy doesn't accept the source's signed URLs.

## Pause a primitive

`POST /v1/primitives/{id}/pause`

**Account.** Pauses the primitive and returns it with `paused: true`. Scoped API keys get `403 forbidden`.

While paused, every data call, including export, answers `423 paused`. Info, stats, rename, delete, listing and creating snapshots, restore, fork, pause, resume and audit still work. Pausing survives restarts.

```bash
curl -X POST https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/pause \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY"
```

```json
{
  "id": "01jbz3k8m5q2w7x9v4n6t0r1ce",
  "group": "agent-42",
  "type": "db",
  "name": "orders",
  "created_at": 1760486400000,
  "paused": true,
  "size_bytes": 24576,
  "forked_from": null
}
```

## Resume a primitive

`POST /v1/primitives/{id}/resume`

**Account.** Resumes a paused primitive and returns it with `paused: false`. Scoped API keys get `403 forbidden`.

```bash
curl -X POST https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/resume \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY"
```

```json
{
  "id": "01jbz3k8m5q2w7x9v4n6t0r1ce",
  "group": "agent-42",
  "type": "db",
  "name": "orders",
  "created_at": 1760486400000,
  "paused": false,
  "size_bytes": 24576,
  "forked_from": null
}
```

## Get a primitive's stats

`GET /v1/primitives/{id}/stats`

Returns the primitive's size and item count (PrimitiveStats). Works while the primitive is paused.

Key tool: the type's read tool, one of `db_query`, `fs_read`, `queue_receive`, `memory_recall`, `vault_list`, `kv_get` or `storage_read`.

```bash
curl https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/stats \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY"
```

```json
{
  "id": "01jbz3k8m5q2w7x9v4n6t0r1ce",
  "type": "db",
  "name": "orders",
  "size_bytes": 24576,
  "items": 3,
  "paused": false
}
```

## Export a primitive

`GET /v1/primitives/{id}/export`

Downloads a consistent copy of a database, KV store or object store as a SQLite file. Other types answer `400 wrong_type`. A paused primitive answers `423 paused`. When too many exports are in progress, it answers `429 rate_limited`; retry shortly.

The response is `application/vnd.sqlite3`, with `Content-Disposition: attachment; filename="<group>-<name>.sqlite"`.

Key tool: the type's read tool, one of `db_query`, `kv_get` or `storage_read`.

```bash
curl https://api.useprimitives.com/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/export \
  -H "Authorization: Bearer $PRIMITIVES_API_KEY" \
  -o agent-42-orders.sqlite
```

## Read a primitive's audit trail

`GET /v1/primitives/{id}/audit`

**Account.** Returns every audit entry about this primitive, its data and lifecycle events, newest first, as a paged list of AuditEntry. Takes the audit filters, needs Pro, Team or Enterprise, and works while paused. See [Audit](/docs/api/audit#read-a-primitives-audit-trail).

## Types

The data endpoints are per type: [Database](/docs/api/database), [Filesystem](/docs/api/filesystem), [Queue](/docs/api/queue), [Memory](/docs/api/memory), [Vault](/docs/api/vault), [KV](/docs/api/kv) and [Storage](/docs/api/storage). Calling one on a primitive of another type, such as `…/query` on a filesystem, answers `400 wrong_type`, and the message names its type.

## Shapes

| Shape | Fields |
| --- | --- |
| PrimitiveInfo | `{ id, group, type, name, created_at, paused, size_bytes, forked_from }` |
| SnapshotInfo | `{ id, name, created_at, primitive }` |
| PrimitiveStats | `{ id, type, name, size_bytes, items, paused }` |

- **PrimitiveInfo.** `paused` is a boolean. `size_bytes` is a number, `0` until first use. `forked_from` is `{ id, group, type, name, snapshot }` or `null`. In it, `id` may be `null` for older forks, and `snapshot` is the snapshot id, or `null` for a fork of live data.
- **SnapshotInfo.** `name` is the snapshot's label, or `null`. `primitive` is the id of the primitive the snapshot belongs to.
- **PrimitiveStats.** `items` counts tables (db), files (fs), pending jobs, ready or leased (queue), live memories (memory), secrets (vault), unexpired keys (kv) or objects (storage).
