openapi: 3.1.0 info: title: Crumbs API description: REST API for Crumbs by Bretzel — a self-hostable, offline-first notes app. version: 0.1.0 contact: name: Bretzel url: https://bretzel.app servers: - url: http://localhost:3000 description: Local development security: - sessionCookie: [] paths: /api/auth/setup: post: summary: First-time password setup description: Creates the single user account. Only works once — returns 400 if already set up. tags: [Auth] security: [] requestBody: required: true content: application/json: schema: type: object required: [password] properties: password: type: string minLength: 8 responses: '201': description: Account created, session cookie set content: application/json: schema: $ref: '#/components/schemas/Success' '400': description: Setup already completed or invalid password /api/auth/login: post: summary: Log in tags: [Auth] security: [] requestBody: required: true content: application/json: schema: type: object required: [password] properties: password: type: string responses: '200': description: Logged in, session cookie set content: application/json: schema: $ref: '#/components/schemas/Success' '401': description: Invalid password /api/auth/logout: post: summary: Log out tags: [Auth] responses: '200': description: Session cleared content: application/json: schema: $ref: '#/components/schemas/Success' /api/notes: get: summary: List notes tags: [Notes] parameters: - name: filter in: query schema: type: string enum: [all, archived, trashed] default: all responses: '200': description: Array of notes content: application/json: schema: type: array items: $ref: '#/components/schemas/Note' post: summary: Create a note tags: [Notes] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/NoteInput' responses: '201': description: Created note content: application/json: schema: $ref: '#/components/schemas/Note' /api/notes/{id}: parameters: - name: id in: path required: true schema: type: string format: uuid get: summary: Get a note tags: [Notes] responses: '200': description: Note with tags content: application/json: schema: $ref: '#/components/schemas/Note' '404': description: Note not found patch: summary: Update a note description: Partial update — only include changed fields. tags: [Notes] requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/NoteInput' responses: '200': description: Updated note content: application/json: schema: $ref: '#/components/schemas/Note' '404': description: Note not found delete: summary: Permanently delete a note tags: [Notes] responses: '200': description: Deleted content: application/json: schema: $ref: '#/components/schemas/Success' '404': description: Note not found /api/notes/{id}/attachments: parameters: - name: id in: path required: true schema: type: string format: uuid get: summary: List or download attachments description: Without `attachmentId`, lists all attachments. With `attachmentId`, returns the file. tags: [Attachments] parameters: - name: attachmentId in: query schema: type: string - name: thumb in: query description: Set to "1" to return the thumbnail instead of the full image (requires attachmentId) schema: type: string enum: ['1'] responses: '200': description: Attachment list (without attachmentId) or file content (with attachmentId) content: application/json: schema: type: array items: $ref: '#/components/schemas/Attachment' post: summary: Upload an image attachment tags: [Attachments] requestBody: required: true content: multipart/form-data: schema: type: object required: [file] properties: file: type: string format: binary description: Image file (max 10MB) thumbnail: type: string format: binary description: Pre-generated thumbnail (WebP, ~200px) responses: '201': description: Attachment metadata content: application/json: schema: $ref: '#/components/schemas/Attachment' '400': description: Invalid file delete: summary: Delete an attachment tags: [Attachments] parameters: - name: attachmentId in: query required: true schema: type: string responses: '200': description: Deleted content: application/json: schema: $ref: '#/components/schemas/Success' /api/search: get: summary: Full-text search description: Searches across note titles, content, and tags. Excludes trashed notes. tags: [Search] parameters: - name: q in: query required: true schema: type: string responses: '200': description: Matching notes content: application/json: schema: type: array items: $ref: '#/components/schemas/Note' /api/tags: get: summary: List all tags tags: [Tags] responses: '200': description: Array of tags content: application/json: schema: type: array items: $ref: '#/components/schemas/Tag' /api/sync: get: summary: Pull changes since timestamp tags: [Sync] parameters: - name: since in: query required: true schema: type: integer description: Unix timestamp in milliseconds responses: '200': description: Notes updated since the given timestamp content: application/json: schema: type: array items: $ref: '#/components/schemas/Note' post: summary: Push local changes tags: [Sync] requestBody: required: true content: application/json: schema: type: object required: [changes] properties: changes: type: array items: $ref: '#/components/schemas/SyncChange' responses: '200': description: Changes applied content: application/json: schema: $ref: '#/components/schemas/Success' '400': description: Invalid changes payload /api/settings/api-keys: get: summary: List API keys tags: [Settings] responses: '200': description: List of API keys (without hashes) content: application/json: schema: type: array items: $ref: '#/components/schemas/ApiKey' post: summary: Create API key tags: [Settings] requestBody: required: true content: application/json: schema: type: object required: [name] properties: name: type: string responses: '201': description: API key created (key shown only once) content: application/json: schema: type: object properties: id: type: string key: type: string name: type: string keyPrefix: type: string /api/settings/api-keys/{id}: delete: summary: Revoke API key tags: [Settings] parameters: - name: id in: path required: true schema: type: string responses: '200': description: Key revoked content: application/json: schema: $ref: '#/components/schemas/Success' '404': description: Key not found /api/mcp: post: summary: MCP Streamable HTTP endpoint description: Model Context Protocol endpoint. Accepts JSON-RPC 2.0 messages. Authenticate via Bearer token (API key). tags: [MCP] security: - bearerAuth: [] requestBody: required: true content: application/json: schema: type: object description: JSON-RPC 2.0 message responses: '200': description: JSON-RPC response (may be SSE stream) '400': description: Invalid session '401': description: Invalid or missing API key components: securitySchemes: sessionCookie: type: apiKey in: cookie name: session bearerAuth: type: http scheme: bearer description: API key (crumbs_...) schemas: Success: type: object properties: success: type: boolean Note: type: object properties: id: type: string format: uuid title: type: string content: type: string color: type: string enum: [default, coral, peach, sand, mint, sage, fog, storm, dusk, blossom, clay, chalk] pinned: type: boolean archived: type: boolean trashed: type: boolean trashedAt: type: string format: date-time nullable: true checklistMode: type: boolean sortOrder: type: integer createdAt: type: string format: date-time updatedAt: type: string format: date-time version: type: integer tags: type: array items: type: string attachments: type: array items: $ref: '#/components/schemas/Attachment' NoteInput: type: object properties: id: type: string format: uuid description: Optional — generated if omitted title: type: string content: type: string color: type: string enum: [default, coral, peach, sand, mint, sage, fog, storm, dusk, blossom, clay, chalk] pinned: type: boolean archived: type: boolean trashed: type: boolean checklistMode: type: boolean sortOrder: type: integer Tag: type: object properties: id: type: integer name: type: string Attachment: type: object properties: id: type: string noteId: type: string filename: type: string mimeType: type: string size: type: integer thumbnailPath: type: string nullable: true description: Server path to thumbnail file (null if no thumbnail) createdAt: type: string format: date-time SyncChange: type: object required: [noteId, operation, timestamp] properties: noteId: type: string format: uuid operation: type: string enum: [create, update, delete] timestamp: type: integer description: Unix timestamp in milliseconds data: $ref: '#/components/schemas/NoteInput' ApiKey: type: object properties: id: type: string format: uuid name: type: string keyPrefix: type: string description: First 12 characters of the key for display createdAt: type: string format: date-time lastUsedAt: type: string format: date-time nullable: true