- Update FEATURES.md, ARCHITECTURE.md, DEPLOYMENT.md with MCP sections - Add MCP endpoints to OpenAPI spec and regenerate API.md - Add rebase-before-PR best practice to CLAUDE.md git conventions - Update README with MCP/API feature mention Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
10 KiB
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:
{
"password": "password"
}
Response: 201
{
"success": true
}
Errors: 400 Setup already completed or invalid password
POST /api/auth/login
Log in
No authentication required.
Request:
{
"password": "password"
}
Response: 200
{
"success": true
}
Errors: 401 Invalid password
POST /api/auth/logout
Log out
Response: 200
{
"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
[
{
"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:
{
"id": "uuid",
"title": "title",
"content": "content",
"color": "default",
"pinned": true,
"archived": true,
"trashed": true,
"checklistMode": true,
"sortOrder": 1
}
Response: 201
{
"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
{
"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:
{
"id": "uuid",
"title": "title",
"content": "content",
"color": "default",
"pinned": true,
"archived": true,
"trashed": true,
"checklistMode": true,
"sortOrder": 1
}
Response: 200
{
"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
{
"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 | |
thumb |
query | string | no | Set to "1" to return the thumbnail instead of the full image (requires attachmentId) (1) |
Response: 200
[
{
"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
{
"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
{
"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
[
{
"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
[
{
"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
[
{
"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:
{
"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
{
"success": true
}
Errors: 400 Invalid changes payload
Settings
GET /api/settings/api-keys
List API keys
Response: 200
[
{
"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:
{
"name": "name"
}
Response: 201
{
"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
{
"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:
{}
Response: 200
Errors: 400 Invalid session, 401 Invalid or missing API key