# Introduction

> Base URLs, ids and names, pagination, durations and every /v1 resource of the REST API.

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

The REST API is what both SDKs and the MCP server are built on. It speaks JSON over HTTP and is versioned in the path.

| | Base URL |
| --- | --- |
| Cloud | `https://api.useprimitives.com` |
| Local (`primitives dev`) | `http://localhost:7777` |

All paths below start with `/v1`. The engine also serves `GET /health`, which returns `{ "ok": true, "version": "…" }`.

Every request needs an API key, except against the local engine. See [Authentication](/docs/api/authentication).

## Endpoints

| Resource | Covers |
| --- | --- |
| [Groups](/docs/api/groups) | Create, list, get and delete groups |
| [Audit](/docs/api/audit) | The audit trail of the workspace, a group or a primitive |
| [Primitives](/docs/api/primitives) | Create, list, rename and delete primitives; snapshots, restore, fork, pause, stats, export |
| [Database](/docs/api/database) | SQL queries, batches, tables, branches |
| [Filesystem](/docs/api/filesystem) | Files, directories, move, glob, search, directory forks |
| [Queue](/docs/api/queue) | Send, receive, settle and inspect jobs; topics |
| [Memory](/docs/api/memory) | Remember, recall, supersede, forget, consolidate |
| [Vault](/docs/api/vault) | Secrets, grants and `fetch` with a secret injected |
| [KV](/docs/api/kv) | Keys with TTLs, versions, batches and increments |
| [Storage](/docs/api/storage) | Objects, ranges, conditional writes and presigning |
| [Signed URLs](/docs/api/signed-urls) | Token-free uploads and downloads |

A machine-readable OpenAPI description is served at [`/openapi.json`](/openapi.json).

## Resources and ids

Every primitive (a database, filesystem, queue, memory, vault, key-value store or object store) has an immutable `id`, a 26-character lowercase ULID, and every primitive endpoint addresses it by that id: `/primitives/{id}/…`. Renaming a primitive never changes its id. Primitives can't move between groups; fork one into another group instead.

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`. The group is only a collection: it decides which account owns the primitive and which scoped API keys can reach it. `{group}` is the group name. Group and primitive names are 1–63 characters from `a-z`, `0-9`, `-`, `_` and `.`, starting with a letter or digit, with no `..`.

## Pagination

Endpoints that page take `limit` (default 50, max 200) and `cursor`, and return:

```json
{ "data": [], "next_cursor": null }
```

Pass `next_cursor` back as `cursor` for the next page. When it's `null`, there are no more pages. Key and object listings page the same way, with 100 entries by default and up to 1,000. Snapshots, tables and branches return everything in one response, with `next_cursor: null`. Glob and search return up to their own `limit` (default 50) as `{ "data": [...] }`, with no cursor.

## Durations

Fields such as `delay`, `lease`, `ttl` and `expires_in` take strings like `"250ms"`, `"30s"`, `"5m"`, `"1h"` or `"7d"`; a bare number in a string means seconds. Every duration field also accepts an integer number of milliseconds. Timestamps in responses are milliseconds since the Unix epoch.

## Example: create a database and query it

Create returns the new primitive's `id`; every later call uses it.

```bash
curl -X POST http://localhost:7777/v1/primitives \
  -H "Content-Type: application/json" \
  -d '{"name": "main", "type": "db", "group": "agent-42"}'
```

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

```bash
curl -X POST http://localhost:7777/v1/primitives/01jbz3k8m5q2w7x9v4n6t0r1ce/query \
  -H "Content-Type: application/json" \
  -d '{"sql": "select id, title from tasks where status = ?", "params": ["open"]}'
```

```json
{
  "columns": ["id", "title"],
  "rows": [{ "id": 1, "title": "Write docs" }],
  "rows_affected": 0,
  "last_insert_rowid": null,
  "truncated": false
}
```

## Idempotency

`Idempotency-Key` headers are coming soon. Until then, use `dedupe_key` when sending queue messages, and prefer idempotent SQL such as `insert … on conflict do nothing` for writes you may retry. The SDKs never retry `POST` requests on `5xx`.
