Files
crumbs-original/docs/API.md
T
Frederic CabassutandClaude Opus 4.6 a66256495e docs: add collaboration to FEATURES, ARCHITECTURE, and OpenAPI spec
- Add note sharing section to FEATURES.md
- Document collaboration access control model in ARCHITECTURE.md
- Add collaborators and user search endpoints to openapi.yaml
- Regenerate API.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:12:01 +01:00

776 lines
12 KiB
Markdown

<!-- AUTO-GENERATED from docs/openapi.yaml — do not edit manually -->
<!-- Run: pnpm docs:api -->
# Crumbs API
REST API for Crumbs by Bretzel — a self-hostable, offline-first notes app.
Base URL: `http://localhost:3000`
All endpoints require a `session` cookie (set by login) except where noted.
## Auth
### `POST /api/auth/setup`
First-time password setup
Creates the single user account. Only works once — returns 400 if already set up.
> No authentication required.
**Request:**
```json
{
"password": "password"
}
```
**Response:** `201`
```json
{
"success": true
}
```
**Errors:** `400` Setup already completed or invalid password
---
### `POST /api/auth/login`
Log in
> No authentication required.
**Request:**
```json
{
"password": "password"
}
```
**Response:** `200`
```json
{
"success": true
}
```
**Errors:** `401` Invalid password
---
### `POST /api/auth/logout`
Log out
**Response:** `200`
```json
{
"success": true
}
```
---
## Notes
### `GET /api/notes`
List notes
**Parameters:**
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| `filter` | query | string | no | (all, archived, trashed) — default: `all` |
**Response:** `200`
```json
[
{
"id": "uuid",
"title": "title",
"content": "content",
"color": "default",
"pinned": true,
"archived": true,
"trashed": true,
"trashedAt": "2025-01-01T00:00:00.000Z",
"checklistMode": true,
"sortOrder": 1,
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z",
"version": 1,
"tags": [
"item"
],
"attachments": [
{
"id": "id",
"noteId": "noteId",
"filename": "filename",
"mimeType": "mimeType",
"size": 1,
"thumbnailPath": "thumbnailPath",
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
}
]
```
---
### `POST /api/notes`
Create a note
**Request:**
```json
{
"id": "uuid",
"title": "title",
"content": "content",
"color": "default",
"pinned": true,
"archived": true,
"trashed": true,
"checklistMode": true,
"sortOrder": 1
}
```
**Response:** `201`
```json
{
"id": "uuid",
"title": "title",
"content": "content",
"color": "default",
"pinned": true,
"archived": true,
"trashed": true,
"trashedAt": "2025-01-01T00:00:00.000Z",
"checklistMode": true,
"sortOrder": 1,
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z",
"version": 1,
"tags": [
"item"
],
"attachments": [
{
"id": "id",
"noteId": "noteId",
"filename": "filename",
"mimeType": "mimeType",
"size": 1,
"thumbnailPath": "thumbnailPath",
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
}
```
---
### `GET /api/notes/{id}`
Get a note
**Parameters:**
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| `id` | path | string | yes | |
**Response:** `200`
```json
{
"id": "uuid",
"title": "title",
"content": "content",
"color": "default",
"pinned": true,
"archived": true,
"trashed": true,
"trashedAt": "2025-01-01T00:00:00.000Z",
"checklistMode": true,
"sortOrder": 1,
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z",
"version": 1,
"tags": [
"item"
],
"attachments": [
{
"id": "id",
"noteId": "noteId",
"filename": "filename",
"mimeType": "mimeType",
"size": 1,
"thumbnailPath": "thumbnailPath",
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
}
```
**Errors:** `404` Note not found
---
### `PATCH /api/notes/{id}`
Update a note
Partial update — only include changed fields.
**Parameters:**
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| `id` | path | string | yes | |
**Request:**
```json
{
"id": "uuid",
"title": "title",
"content": "content",
"color": "default",
"pinned": true,
"archived": true,
"trashed": true,
"checklistMode": true,
"sortOrder": 1
}
```
**Response:** `200`
```json
{
"id": "uuid",
"title": "title",
"content": "content",
"color": "default",
"pinned": true,
"archived": true,
"trashed": true,
"trashedAt": "2025-01-01T00:00:00.000Z",
"checklistMode": true,
"sortOrder": 1,
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z",
"version": 1,
"tags": [
"item"
],
"attachments": [
{
"id": "id",
"noteId": "noteId",
"filename": "filename",
"mimeType": "mimeType",
"size": 1,
"thumbnailPath": "thumbnailPath",
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
}
```
**Errors:** `404` Note not found
---
### `DELETE /api/notes/{id}`
Permanently delete a note
**Parameters:**
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| `id` | path | string | yes | |
**Response:** `200`
```json
{
"success": true
}
```
**Errors:** `404` Note not found
---
## Collaboration
### `GET /api/notes/{id}/collaborators`
List collaborators
Returns all collaborators for a note. Requires access (owner or collaborator).
**Parameters:**
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| `id` | path | string | yes | |
**Response:** `200`
```json
[
{
"userId": 1,
"displayName": "displayName",
"email": "email",
"addedAt": "2025-01-01T00:00:00.000Z"
}
]
```
---
### `POST /api/notes/{id}/collaborators`
Add a collaborator
Owner-only. Adds a user as collaborator on the note.
**Parameters:**
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| `id` | path | string | yes | |
**Request:**
```json
{
"userId": 1
}
```
**Response:** `201`
```json
[
{
"userId": 1,
"displayName": "displayName",
"email": "email",
"addedAt": "2025-01-01T00:00:00.000Z"
}
]
```
**Errors:** `400` Invalid request (missing userId, owner as collaborator), `409` User is already a collaborator
---
### `DELETE /api/notes/{id}/collaborators`
Remove a collaborator
Owner can remove anyone. Collaborator can only remove self (leave). Use `userId=self` to leave.
**Parameters:**
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| `id` | path | string | yes | |
| `userId` | query | | yes | |
**Response:** `200`
```json
{
"success": true
}
```
---
## Users
### `GET /api/users/search`
Search users
Search registered users by email or display name. Excludes the current user. Any authenticated user can access.
**Parameters:**
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| `q` | query | string | yes | Search query (matches email or displayName) |
**Response:** `200`
```json
[
{
"id": 1,
"displayName": "displayName",
"email": "email"
}
]
```
---
## Attachments
### `GET /api/notes/{id}/attachments`
List or download attachments
Without `attachmentId`, lists all attachments. With `attachmentId`, returns the file.
**Parameters:**
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| `id` | path | string | yes | |
| `attachmentId` | query | string | no | |
| `thumb` | query | string | no | Set to "1" to return the thumbnail instead of the full image (requires attachmentId) (1) |
**Response:** `200`
```json
[
{
"id": "id",
"noteId": "noteId",
"filename": "filename",
"mimeType": "mimeType",
"size": 1,
"thumbnailPath": "thumbnailPath",
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
```
---
### `POST /api/notes/{id}/attachments`
Upload an image attachment
**Parameters:**
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| `id` | path | string | yes | |
**Request:** `multipart/form-data` with `file` field.
**Response:** `201`
```json
{
"id": "id",
"noteId": "noteId",
"filename": "filename",
"mimeType": "mimeType",
"size": 1,
"thumbnailPath": "thumbnailPath",
"createdAt": "2025-01-01T00:00:00.000Z"
}
```
**Errors:** `400` Invalid file
---
### `DELETE /api/notes/{id}/attachments`
Delete an attachment
**Parameters:**
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| `id` | path | string | yes | |
| `attachmentId` | query | string | yes | |
**Response:** `200`
```json
{
"success": true
}
```
---
## Search
### `GET /api/search`
Full-text search
Searches across note titles, content, and tags. Excludes trashed notes.
**Parameters:**
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| `q` | query | string | yes | |
**Response:** `200`
```json
[
{
"id": "uuid",
"title": "title",
"content": "content",
"color": "default",
"pinned": true,
"archived": true,
"trashed": true,
"trashedAt": "2025-01-01T00:00:00.000Z",
"checklistMode": true,
"sortOrder": 1,
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z",
"version": 1,
"tags": [
"item"
],
"attachments": [
{
"id": "id",
"noteId": "noteId",
"filename": "filename",
"mimeType": "mimeType",
"size": 1,
"thumbnailPath": "thumbnailPath",
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
}
]
```
---
## Tags
### `GET /api/tags`
List all tags
**Response:** `200`
```json
[
{
"id": 1,
"name": "name"
}
]
```
---
## Sync
### `GET /api/sync`
Pull changes since timestamp
**Parameters:**
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| `since` | query | integer | yes | Unix timestamp in milliseconds |
**Response:** `200`
```json
[
{
"id": "uuid",
"title": "title",
"content": "content",
"color": "default",
"pinned": true,
"archived": true,
"trashed": true,
"trashedAt": "2025-01-01T00:00:00.000Z",
"checklistMode": true,
"sortOrder": 1,
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-01-01T00:00:00.000Z",
"version": 1,
"tags": [
"item"
],
"attachments": [
{
"id": "id",
"noteId": "noteId",
"filename": "filename",
"mimeType": "mimeType",
"size": 1,
"thumbnailPath": "thumbnailPath",
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
}
]
```
---
### `POST /api/sync`
Push local changes
**Request:**
```json
{
"changes": [
{
"noteId": "uuid",
"operation": "create",
"timestamp": 1,
"data": {
"id": "uuid",
"title": "title",
"content": "content",
"color": "default",
"pinned": true,
"archived": true,
"trashed": true,
"checklistMode": true,
"sortOrder": 1
}
}
]
}
```
**Response:** `200`
```json
{
"success": true
}
```
**Errors:** `400` Invalid changes payload
---
## Settings
### `GET /api/settings/api-keys`
List API keys
**Response:** `200`
```json
[
{
"id": "uuid",
"name": "name",
"keyPrefix": "keyPrefix",
"createdAt": "2025-01-01T00:00:00.000Z",
"lastUsedAt": "2025-01-01T00:00:00.000Z"
}
]
```
---
### `POST /api/settings/api-keys`
Create API key
**Request:**
```json
{
"name": "name"
}
```
**Response:** `201`
```json
{
"id": "id",
"key": "key",
"name": "name",
"keyPrefix": "keyPrefix"
}
```
---
### `DELETE /api/settings/api-keys/{id}`
Revoke API key
**Parameters:**
| Name | In | Type | Required | Description |
|------|----|------|----------|-------------|
| `id` | path | string | yes | |
**Response:** `200`
```json
{
"success": true
}
```
**Errors:** `404` Key not found
---
## MCP
### `POST /api/mcp`
MCP Streamable HTTP endpoint
Model Context Protocol endpoint. Accepts JSON-RPC 2.0 messages. Authenticate via Bearer token (API key).
**Request:**
```json
{}
```
**Response:** `200`
**Errors:** `400` Invalid session, `401` Invalid or missing API key
---