openapi: 3.1.0 info: title: Obsidian-Like Docs Viewer API version: 1.0.0 description: | Multi-tenant Obsidian-like documentation viewer with full-text search, wikilinks, and tags. ## Authentication All endpoints require Bearer JWT authentication via the `Authorization` header (except in local mode). ## Multi-tenancy All operations are scoped to the authenticated user's vault. Users cannot access other users' notes. ## Path Encoding Note paths in URLs must be URL-encoded (e.g., `api/design.md` becomes `api%2Fdesign.md`). contact: name: API Support license: name: MIT servers: - url: http://localhost:8000 description: Local development server - url: https://your-space.hf.space description: Hugging Face Space deployment security: - BearerAuth: [] tags: - name: Authentication description: User authentication and token management - name: Notes description: CRUD operations for notes - name: Search description: Full-text search and navigation - name: Index description: Index health and rebuild operations paths: /api/tokens: post: summary: Issue JWT token description: Issue a new JWT access token for the authenticated user (90-day expiration) operationId: issueToken tags: - Authentication security: - BearerAuth: [] responses: '200': description: Token issued successfully content: application/json: schema: $ref: '#/components/schemas/TokenResponse' example: token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhbGljZSIsImlhdCI6MTczNjk1NjgwMCwiZXhwIjoxNzQ0NzMyODAwfQ.signature token_type: bearer expires_at: "2025-04-15T10:30:00Z" '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalServerError' /api/me: get: summary: Get current user info description: Retrieve authenticated user information including HF profile operationId: getCurrentUser tags: - Authentication responses: '200': description: User information retrieved content: application/json: schema: $ref: '#/components/schemas/User' example: user_id: alice hf_profile: username: alice name: Alice Smith avatar_url: /static-proxy?url=https%3A%2F%2Fcdn-avatars.huggingface.co%2Fv1%2Falice vault_path: /data/vaults/alice created: "2025-01-15T10:30:00Z" '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalServerError' /api/notes: get: summary: List notes description: List all notes in the user's vault with optional folder filtering operationId: listNotes tags: - Notes parameters: - name: folder in: query description: Filter notes by folder path (e.g., "api" or "guides/tutorials") required: false schema: type: string maxLength: 256 example: api responses: '200': description: List of notes content: application/json: schema: type: array items: $ref: '#/components/schemas/NoteSummary' example: - note_path: api/design.md title: API Design updated: "2025-01-15T14:30:00Z" - note_path: api/endpoints.md title: API Endpoints updated: "2025-01-14T09:15:00Z" '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalServerError' /api/notes/{path}: get: summary: Get note description: Retrieve full note content including metadata, body, and version operationId: getNote tags: - Notes parameters: - $ref: '#/components/parameters/NotePath' responses: '200': description: Note retrieved successfully content: application/json: schema: $ref: '#/components/schemas/Note' example: user_id: alice note_path: api/design.md version: 5 title: API Design metadata: tags: - backend - api project: auth-service body: "# API Design\n\nThis document describes the API architecture...\n\n## Endpoints\n\nSee [[Endpoints]] for details." created: "2025-01-10T09:00:00Z" updated: "2025-01-15T14:30:00Z" size_bytes: 4096 '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: Note not found content: application/json: schema: $ref: '#/components/schemas/Error' example: error: not_found message: Note not found detail: note_path: api/design.md '500': $ref: '#/components/responses/InternalServerError' put: summary: Create or update note description: Create a new note or update an existing note with optional optimistic concurrency control operationId: createOrUpdateNote tags: - Notes parameters: - $ref: '#/components/parameters/NotePath' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/NoteUpdate' example: title: API Design metadata: tags: - backend - api project: auth-service body: "# API Design\n\nThis document describes the API architecture..." if_version: 4 responses: '200': description: Note updated successfully content: application/json: schema: $ref: '#/components/schemas/NoteResponse' example: status: ok path: api/design.md version: 5 '201': description: Note created successfully content: application/json: schema: $ref: '#/components/schemas/NoteResponse' example: status: ok path: api/design.md version: 1 '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '409': $ref: '#/components/responses/Conflict' '413': $ref: '#/components/responses/PayloadTooLarge' '500': $ref: '#/components/responses/InternalServerError' delete: summary: Delete note description: Delete a note and remove it from all indices operationId: deleteNote tags: - Notes parameters: - $ref: '#/components/parameters/NotePath' responses: '200': description: Note deleted successfully content: application/json: schema: $ref: '#/components/schemas/DeleteResponse' example: status: ok path: api/design.md '401': $ref: '#/components/responses/Unauthorized' '403': $ref: '#/components/responses/Forbidden' '404': description: Note not found content: application/json: schema: $ref: '#/components/schemas/Error' example: error: not_found message: Note not found detail: note_path: api/design.md '500': $ref: '#/components/responses/InternalServerError' /api/search: get: summary: Search notes description: Full-text search across all notes with ranking (title 3x weight, body 1x, recency bonus) operationId: searchNotes tags: - Search parameters: - name: q in: query description: Search query (tokenized, case-insensitive) required: true schema: type: string minLength: 1 maxLength: 256 example: authentication - name: limit in: query description: Maximum number of results to return required: false schema: type: integer minimum: 1 maximum: 100 default: 50 responses: '200': description: Search results content: application/json: schema: type: array items: $ref: '#/components/schemas/SearchResult' example: - note_path: api/auth.md title: Authentication Flow snippet: "...describes the authentication process using JWT tokens..." score: 8.5 updated: "2025-01-15T14:30:00Z" - note_path: guides/setup.md title: Setup Guide snippet: "...configure authentication settings in the config file..." score: 3.2 updated: "2025-01-10T09:00:00Z" '400': $ref: '#/components/responses/BadRequest' '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalServerError' /api/backlinks/{path}: get: summary: Get backlinks description: Get all notes that reference the specified note via wikilinks operationId: getBacklinks tags: - Search parameters: - name: path in: path description: URL-encoded note path (includes .md extension) required: true schema: type: string maxLength: 256 example: api%2Fdesign.md responses: '200': description: Backlinks retrieved content: application/json: schema: type: array items: $ref: '#/components/schemas/BacklinkResult' example: - note_path: guides/architecture.md title: Architecture Overview - note_path: api/endpoints.md title: API Endpoints '401': $ref: '#/components/responses/Unauthorized' '404': description: Note not found content: application/json: schema: $ref: '#/components/schemas/Error' example: error: not_found message: Note not found detail: note_path: api/design.md '500': $ref: '#/components/responses/InternalServerError' /api/tags: get: summary: List all tags description: Get all tags used in the user's vault with note counts operationId: listTags tags: - Search responses: '200': description: List of tags content: application/json: schema: type: array items: $ref: '#/components/schemas/Tag' example: - tag: backend count: 15 - tag: api count: 12 - tag: frontend count: 8 '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalServerError' /api/index/health: get: summary: Get index health status description: Retrieve index health metrics including note count and last update timestamps operationId: getIndexHealth tags: - Index responses: '200': description: Index health metrics content: application/json: schema: $ref: '#/components/schemas/IndexHealth' example: user_id: alice note_count: 142 last_full_rebuild: "2025-01-01T00:00:00Z" last_incremental_update: "2025-01-15T14:30:00Z" '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalServerError' /api/index/rebuild: post: summary: Trigger full index rebuild description: Manually rebuild all indices from scratch by re-scanning vault files operationId: rebuildIndex tags: - Index responses: '200': description: Index rebuild completed content: application/json: schema: $ref: '#/components/schemas/RebuildResponse' example: status: ok notes_indexed: 142 duration_ms: 1250 '401': $ref: '#/components/responses/Unauthorized' '500': $ref: '#/components/responses/InternalServerError' components: securitySchemes: BearerAuth: type: http scheme: bearer bearerFormat: JWT description: JWT token obtained from POST /api/tokens parameters: NotePath: name: path in: path description: URL-encoded note path (includes .md extension, e.g., "api%2Fdesign.md") required: true schema: type: string maxLength: 256 example: api%2Fdesign.md schemas: User: type: object required: - user_id - vault_path - created properties: user_id: type: string minLength: 1 maxLength: 64 description: Internal user identifier example: alice hf_profile: $ref: '#/components/schemas/HFProfile' vault_path: type: string description: Absolute path to user's vault directory example: /data/vaults/alice created: type: string format: date-time description: Account creation timestamp (ISO 8601) example: "2025-01-15T10:30:00Z" HFProfile: type: object properties: username: type: string description: Hugging Face username example: alice name: type: string nullable: true description: Display name from HF profile example: Alice Smith avatar_url: type: string format: uri nullable: true description: Profile picture URL example: /static-proxy?url=https%3A%2F%2Fcdn-avatars.huggingface.co%2Fv1%2Falice NoteMetadata: type: object description: Arbitrary frontmatter key-value pairs (YAML) properties: title: type: string nullable: true tags: type: array items: type: string nullable: true project: type: string nullable: true created: type: string format: date-time nullable: true updated: type: string format: date-time nullable: true additionalProperties: true example: tags: - backend - api project: auth-service Note: type: object required: - user_id - note_path - version - title - body - created - updated - size_bytes properties: user_id: type: string description: Owner user ID example: alice note_path: type: string minLength: 1 maxLength: 256 description: Relative path to vault root (includes .md) example: api/design.md version: type: integer minimum: 1 description: Optimistic concurrency version counter example: 5 title: type: string minLength: 1 description: Display title (from frontmatter, H1, or filename) example: API Design metadata: $ref: '#/components/schemas/NoteMetadata' body: type: string description: Markdown content (excluding frontmatter) example: "# API Design\n\nThis document describes..." created: type: string format: date-time description: Creation timestamp (ISO 8601) example: "2025-01-10T09:00:00Z" updated: type: string format: date-time description: Last modification timestamp (ISO 8601) example: "2025-01-15T14:30:00Z" size_bytes: type: integer minimum: 0 maximum: 1048576 description: File size in bytes (max 1 MiB) example: 4096 NoteSummary: type: object required: - note_path - title - updated properties: note_path: type: string example: api/design.md title: type: string example: API Design updated: type: string format: date-time example: "2025-01-15T14:30:00Z" NoteUpdate: type: object required: - body properties: title: type: string nullable: true description: Override title (if not set, will be extracted from frontmatter or body) metadata: $ref: '#/components/schemas/NoteMetadata' body: type: string maxLength: 1048576 description: Markdown content (max 1 MiB UTF-8) if_version: type: integer minimum: 1 nullable: true description: Expected version for optimistic concurrency (409 if mismatch) example: 4 NoteResponse: type: object required: - status - path - version properties: status: type: string enum: - ok path: type: string example: api/design.md version: type: integer example: 5 DeleteResponse: type: object required: - status - path properties: status: type: string enum: - ok path: type: string example: api/design.md SearchResult: type: object required: - note_path - title - snippet - score - updated properties: note_path: type: string example: api/auth.md title: type: string example: Authentication Flow snippet: type: string description: Highlighted excerpt with tags example: "...describes the authentication process using JWT..." score: type: number format: float description: Relevance score (title 3x, body 1x, recency bonus) example: 8.5 updated: type: string format: date-time example: "2025-01-15T14:30:00Z" BacklinkResult: type: object required: - note_path - title properties: note_path: type: string example: guides/architecture.md title: type: string example: Architecture Overview Tag: type: object required: - tag - count properties: tag: type: string description: Tag name (lowercase, normalized) example: backend count: type: integer minimum: 0 description: Number of notes with this tag example: 15 IndexHealth: type: object required: - user_id - note_count properties: user_id: type: string example: alice note_count: type: integer minimum: 0 description: Total notes indexed example: 142 last_full_rebuild: type: string format: date-time nullable: true description: Timestamp of last manual rebuild example: "2025-01-01T00:00:00Z" last_incremental_update: type: string format: date-time nullable: true description: Timestamp of last write/delete operation example: "2025-01-15T14:30:00Z" RebuildResponse: type: object required: - status - notes_indexed - duration_ms properties: status: type: string enum: - ok notes_indexed: type: integer minimum: 0 example: 142 duration_ms: type: integer minimum: 0 description: Rebuild duration in milliseconds example: 1250 TokenResponse: type: object required: - token - token_type - expires_at properties: token: type: string description: JWT access token example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... token_type: type: string enum: - bearer example: bearer expires_at: type: string format: date-time description: Token expiration timestamp (90 days from issuance) example: "2025-04-15T10:30:00Z" Error: type: object required: - error - message properties: error: type: string description: Error code (machine-readable) example: validation_error message: type: string description: Human-readable error message example: Invalid note path format detail: type: object nullable: true description: Additional error context additionalProperties: true example: field: note_path reason: Path must end with .md responses: BadRequest: description: Bad request (invalid input) content: application/json: schema: $ref: '#/components/schemas/Error' examples: invalidPath: summary: Invalid note path value: error: validation_error message: Invalid note path format detail: field: note_path reason: Path must end with .md emptyQuery: summary: Empty search query value: error: validation_error message: Search query cannot be empty detail: field: query reason: Query must be at least 1 character Unauthorized: description: Authentication required or token invalid/expired content: application/json: schema: $ref: '#/components/schemas/Error' examples: missingToken: summary: Missing authorization header value: error: unauthorized message: Authorization header required expiredToken: summary: Expired JWT token value: error: token_expired message: Token expired, please re-authenticate invalidToken: summary: Invalid JWT token value: error: invalid_token message: Invalid token signature Forbidden: description: Forbidden (vault limit exceeded or permission denied) content: application/json: schema: $ref: '#/components/schemas/Error' examples: vaultLimitExceeded: summary: Vault note limit exceeded value: error: vault_note_limit_exceeded message: Vault has reached maximum of 5,000 notes detail: current_count: 5000 max_limit: 5000 pathTraversal: summary: Path traversal attempt value: error: forbidden message: Path escapes vault root detail: note_path: ../../../etc/passwd Conflict: description: Version conflict (optimistic concurrency failure) content: application/json: schema: $ref: '#/components/schemas/Error' example: error: version_conflict message: Note was modified since you opened it detail: expected_version: 4 current_version: 6 note_path: api/design.md PayloadTooLarge: description: Note content exceeds 1 MiB limit content: application/json: schema: $ref: '#/components/schemas/Error' example: error: payload_too_large message: Note content exceeds maximum size of 1 MiB detail: size_bytes: 1572864 max_size_bytes: 1048576 InternalServerError: description: Internal server error content: application/json: schema: $ref: '#/components/schemas/Error' example: error: internal_error message: An unexpected error occurred detail: request_id: req_abc123