---
title: REST API
description: The HTTP API every collection gets, with its filters, sorting, paging and errors.
section: Data
order: 9
---

# REST API

<p class="lead">Every collection has an API the moment it exists. The browser client (<code>sluurp.js</code>) is a thin layer over it, so anything the client does, a script or another program can do with plain HTTP.</p>

A request is made as whoever its token says: `Authorization: Bearer <token>`, from signing in. Without one it is made as a visitor. Either way, the collection's [rules](/docs/collections) decide what it may read and write.

## Records

| Method and path | What it does |
|---|---|
| `GET /api/collections/{name}/records` | A page of records |
| `POST /api/collections/{name}/records` | Create one; answers `201` and the record |
| `GET /api/collections/{name}/records/{id}` | One record |
| `PATCH /api/collections/{name}/records/{id}` | Change the fields given, and only those |
| `DELETE /api/collections/{name}/records/{id}` | Delete it, into the recycle bin; answers `{ "bin": … }` |
| `GET /api/collections/{name}/aggregate` | Count, sum, average, minimum or maximum, by group |

A list takes these in its query string:

| Parameter | |
|---|---|
| `filter` | Which records: `status = "open" && priority > 2` (see below) |
| `sort` | Fields, comma separated, `-` for descending: `-created,title` |
| `page`, `perPage` | Which page, and how many on it |
| `search` | Words to find in the collection's searchable fields |
| `asOf` | The collection as it stood then, for one that keeps [history](/docs/history): `2026-06-30` |
| `near`, `on` | Rows closest in meaning to these words first ([Search by meaning](/docs/search)); `on` names the fields compared |

```http title="HTTP"
GET /api/collections/grades/records?filter=value%20%3E%3D%208&sort=-date&perPage=20
Authorization: Bearer eyJ…
```

A page answers with the records and where they are in the whole: `items`, `page`, `perPage`, `totalItems`, `totalPages`.

## Filters

A small language, compiled into the query, so a filter never reaches the database as text of its own. Field names are checked against the collection, and values are always bound.

| | |
|---|---|
| Compare | `=` `!=` `>` `>=` `<` `<=` |
| Contains, or doesn't | `~` `!~`: `title ~ "trip"` |
| Combine | `&&` `\|\|` and parentheses |
| Values | `"text"` or `'text'`, numbers, `true`, `false`, `null` |

```text title="filter"
(status = "open" || status = "waiting") && priority >= 2 && title !~ "test"
```

## Search by meaning

`near=museum trips` answers the rows closest in meaning first, each with its `_score` (1 is the same). It compares the collection's searchable fields, or the ones named in `on=title,body`. Rules and `filter` apply as on any list. See [Search by meaning](/docs/search) for how it works, and for setting up an embeddings model.

```js title="client"
const { items } = await sluurp.collection("notes").list({ near: "museum trips" });
```

## Counting and summing

`GET /api/collections/{name}/aggregate` takes `op` (`count`, `sum`, `avg`, `min`, `max`), `field` for all but `count`, `group` (a field to group by, and more to group further), and `filter` and `asOf` as a list does:

```http title="HTTP"
GET /api/collections/grades/aggregate?op=avg&field=value&group=subject
```

## History and the recycle bin

For a collection that keeps its history:

| Method and path | |
|---|---|
| `GET …/records/{id}/history` | Every version of a record, newest first, with who and why |
| `POST …/records/{id}/history/{seq}/restore` | Put a version back |
| `GET /api/collections/{name}/changes?since=N` | Every change since the one numbered `N`, in order, to follow along |

A write can say why it was made, in an `X-Sluurp-Reason` header, and the reason is kept with the version. A deleted record goes into the recycle bin, and `POST /api/bin/{bin}/restore` puts it back, with what the delete took with it.

## Files

| Method and path | |
|---|---|
| `GET /api/files/{collection}/{id}/{field}` | The file. For an image, `?w=`, `?h=`, `?fit=`, `?format=` and `?q=` resize and convert it |
| `POST /api/files/{collection}/{id}/{field}` | Upload one, as multipart form data in a part called `file` |
| `DELETE /api/files/{collection}/{id}/{field}` | Remove it |

## Signing in

| Method and path | |
|---|---|
| `POST /api/collections/{name}/auth-with-password` | `{ "identity", "password" }`: answers a token and the record |
| `POST /api/collections/{name}/auth-refresh` | A fresh token for the one sent |
| `POST /api/collections/{name}/signup` | Make an account, where the collection allows it |
| `POST /api/collections/{name}/request-password-reset` | Mail a link to set a new password |

## Errors

An error is a status and a sentence: `{ "status": 404, "message": "record grades/x not found" }`. `400` is a request that doesn't make sense, `401` needs signing in, `403` is refused by a rule, `404` isn't there (or isn't there for you), `409` conflicts with what exists, and `429` is too much at once: try again shortly.

## And more

Several writes as one, all or none: [`POST /api/batch`](/docs/batch). A server function: `GET` or `POST /api/fn/{name}` ([Server functions](/docs/server-functions)). Live changes, over one WebSocket: [Sync](/docs/sync). Events from outside: [`POST /api/events/{name}`](/docs/hooks-and-events).
