mirror of
https://github.com/bretzel-app/crumbs.git
synced 2026-10-12 20:06:57 +02:00
- GET /api/sync now attaches tags to notes (matching GET /api/notes and GET /api/search behavior) - Add trashedAt field to Note schema in OpenAPI spec - Add optional id field to NoteInput schema - Add 400 error response for POST /api/sync - Add response schema for GET attachments list - Regenerate API.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
501 lines
7.3 KiB
Markdown
501 lines
7.3 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"
|
|
]
|
|
}
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
### `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"
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### `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"
|
|
]
|
|
}
|
|
```
|
|
|
|
**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"
|
|
]
|
|
}
|
|
```
|
|
|
|
**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
|
|
|
|
---
|
|
|
|
## 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 | |
|
|
|
|
**Response:** `200`
|
|
|
|
```json
|
|
[
|
|
{
|
|
"id": "id",
|
|
"noteId": "noteId",
|
|
"filename": "filename",
|
|
"mimeType": "mimeType",
|
|
"size": 1,
|
|
"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,
|
|
"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"
|
|
]
|
|
}
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
## 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"
|
|
]
|
|
}
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
### `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
|
|
|
|
---
|