REST API Reference
Resource-oriented URLs. JSON-encoded responses. ABAC permissions on every grant. Hash-chained audit log. All endpoints are prefixed with /api/v1.
API Conventions
| Base path | /api/v1/ |
| Authentication | Authorization: Bearer <JWT> on every non-PUBLIC endpoint (ZITADEL or Keycloak) |
| Dev bypass | With DEV_BYPASS_AUTH=true: Bearer dev-bypass (admin) or Bearer lt_<N> (user N) |
| Content type | application/json for bodies; multipart/form-data for file uploads |
| Pagination | page (1-based) + size query params → { items, total, page, page_size, pages } |
| Error format | { "detail": "<message>", "error_code": "<CODE>" } with appropriate HTTP status |
| Soft deletes | DELETE performs soft-delete; rows remain in DB with deleted=true |
| Timestamps | ISO 8601 UTC on every datetime field |
| IDs | All resource IDs are 64-bit integers |
| API explorer | Swagger UI: /api/docs · ReDoc: /api/redoc · OpenAPI JSON: /api/openapi.json |
Authentication Header
Pagination Envelope
Endpoint Reference
Health
2 endpoints| Method | Path |
|---|---|
| GET | /api/v1/health |
| GET | /api/v1/health/ready |
Authentication & Profile
9 endpoints| Method | Path |
|---|---|
| POST | /api/v1/auth/callback |
| POST | /api/v1/auth/logout |
| GET | /api/v1/auth/me |
| PUT | /api/v1/auth/me |
| GET | /api/v1/auth/me/notifications/preferences |
| PUT | /api/v1/auth/me/notifications/preferences |
| POST | /api/v1/auth/me/2fa/enable |
| POST | /api/v1/auth/me/2fa/verify |
| DELETE | /api/v1/auth/me/2fa |
Users
4 endpoints| Method | Path |
|---|---|
| GET | /api/v1/users |
| GET | /api/v1/users/{user_id} |
| PUT | /api/v1/users/{user_id}/role |
| DELETE | /api/v1/users/{user_id} |
Organizations
6 endpoints| Method | Path |
|---|---|
| GET | /api/v1/organizations/me |
| PUT | /api/v1/organizations/me |
| GET | /api/v1/organizations/me/stats |
| GET | /api/v1/organizations |
| GET | /api/v1/organizations/me/idp-role-mapping |
| PUT | /api/v1/organizations/me/idp-role-mapping |
Departments
5 endpoints| Method | Path |
|---|---|
| GET | /api/v1/departments |
| GET | /api/v1/departments/{dept_id} |
| POST | /api/v1/departments |
| PUT | /api/v1/departments/{dept_id} |
| DELETE | /api/v1/departments/{dept_id} |
Documents
22 endpoints| Method | Path |
|---|---|
| GET | /api/v1/documents |
| POST | /api/v1/documents |
| POST | /api/v1/documents/batch |
| GET | /api/v1/documents/batch/{batch_id} |
| GET | /api/v1/documents/starred |
| GET | /api/v1/documents/{document_id} |
| PUT | /api/v1/documents/{document_id} |
| DELETE | /api/v1/documents/{document_id} |
| PATCH | /api/v1/documents/{document_id}/status |
| POST | /api/v1/documents/{document_id}/upload |
| GET | /api/v1/documents/{document_id}/download |
| POST | /api/v1/documents/{document_id}/lock |
| POST | /api/v1/documents/{document_id}/unlock |
| POST | /api/v1/documents/{document_id}/star |
| DELETE | /api/v1/documents/{document_id}/star |
| GET | /api/v1/documents/{document_id}/retention |
| PUT | /api/v1/documents/{document_id}/retention |
| DELETE | /api/v1/documents/{document_id}/retention |
| POST | /api/v1/documents/{document_id}/normalize |
| GET | /api/v1/documents/{document_id}/thumbnail |
| GET | /api/v1/documents/{document_id}/ai-suggestions |
| POST | /api/v1/documents/{document_id}/apply-ai-suggestions |
Folders
8 endpoints| Method | Path |
|---|---|
| GET | /api/v1/folders |
| POST | /api/v1/folders |
| GET | /api/v1/folders/{folder_id} |
| PUT | /api/v1/folders/{folder_id} |
| DELETE | /api/v1/folders/{folder_id} |
| GET | /api/v1/folders/{folder_id}/children |
| POST | /api/v1/folders/{folder_id}/move |
| GET | /api/v1/folders/{folder_id}/path |
Permissions
8 endpoints| Method | Path |
|---|---|
| GET | /api/v1/permissions/document/{document_id} |
| GET | /api/v1/permissions/folder/{folder_id} |
| POST | /api/v1/permissions/document |
| POST | /api/v1/permissions/folder |
| PUT | /api/v1/permissions/{permission_id} |
| DELETE | /api/v1/permissions/{permission_id} |
| GET | /api/v1/permissions/my/document/{document_id} |
| GET | /api/v1/permissions/my/folder/{folder_id} |
Workflows
5 endpoints| Method | Path |
|---|---|
| POST | /api/v1/workflows |
| GET | /api/v1/workflows/{workflow_id} |
| POST | /api/v1/workflows/{workflow_id}/steps/{step_id}/approve |
| POST | /api/v1/workflows/{workflow_id}/steps/{step_id}/reject |
| DELETE | /api/v1/workflows/{workflow_id} |
Search
2 endpoints| Method | Path |
|---|---|
| GET | /api/v1/search |
| GET | /api/v1/search/semantic |
Chat & AI
4 endpoints| Method | Path |
|---|---|
| POST | /api/v1/chat/document/{document_id} |
| GET | /api/v1/chat/sessions |
| GET | /api/v1/ai/documents/{document_id} |
| POST | /api/v1/ai/reindex/{document_id} |
Invitations
3 endpoints| Method | Path |
|---|---|
| POST | /api/v1/users/invite |
| GET | /api/v1/invitations/{token} |
| POST | /api/v1/invitations/{token}/use |
Notifications
3 endpoints| Method | Path |
|---|---|
| GET | /api/v1/notifications/unread-count |
| GET | /api/v1/notifications |
| POST | /api/v1/notifications/{notification_id}/read |
Legal Holds
6 endpoints| Method | Path |
|---|---|
| POST | /api/v1/legal-holds |
| GET | /api/v1/legal-holds |
| GET | /api/v1/legal-holds/{hold_id} |
| POST | /api/v1/legal-holds/{hold_id}/documents |
| DELETE | /api/v1/legal-holds/{hold_id}/documents/{doc_id} |
| DELETE | /api/v1/legal-holds/{hold_id} |
Document Types & Schemas
10 endpoints| Method | Path |
|---|---|
| GET | /api/v1/document-types |
| POST | /api/v1/document-types |
| GET | /api/v1/document-types/{code} |
| PUT | /api/v1/document-types/{code} |
| DELETE | /api/v1/document-types/{code} |
| GET | /api/v1/document-types/{code}/schemas |
| POST | /api/v1/document-types/{code}/schemas |
| GET | /api/v1/document-types/{code}/policies |
| POST | /api/v1/document-types/{code}/policies |
| DELETE | /api/v1/document-types/{code}/policies/{policy_id} |
Audit Logs
2 endpoints| Method | Path |
|---|---|
| GET | /api/v1/audit-logs/timeline |
| GET | /api/v1/audit-logs/verify |
Audit Exports
2 endpoints| Method | Path |
|---|---|
| POST | /api/v1/audit-exports |
| GET | /api/v1/audit-exports/{export_id} |
WebSockets
2 endpoints| Method | Path |
|---|---|
| WS | /api/v1/ws/collaborate/{document_id}?token=<jwt> |
| WS | /api/v1/ws/notifications?token=<jwt> |
Role Hierarchy
Roles are ordered. Higher roles inherit every capability of lower roles. The numeric `level` is what the auth dependency compares against on every request.
| Role | Level | Scope |
|---|---|---|
| SUPER_ADMIN | 100 | Cross-organization administration, audit verification |
| ADMIN | 80 | Full org management, role changes, legal holds, audit exports |
| MANAGER | 60 | Department management, invite users, create workflows |
| EDITOR | 40 | Create and edit documents, grant up to WRITE permission |
| USER | 30 | Standard document access per explicit permission grants |
| VIEWER | 20 | Read-only access to documents granted to them |
| GUEST | 10 | Minimal / public-only access |
Permission Levels
Document and folder access is controlled by explicit grants, evaluated after role checks. Four levels — capabilities accumulate going down.
| Level | view | comment | edit | share | manage |
|---|---|---|---|---|---|
| READ | ✓ | ||||
| COMMENT | ✓ | ✓ | |||
| WRITE | ✓ | ✓ | ✓ | ||
| ADMIN | ✓ | ✓ | ✓ | ✓ | ✓ |
Permission Resolution Waterfall
- Document exists? — 404 if not.
- Owner shortcut — always allowed (bypasses ABAC conditions).
- Public document — READ allowed.
- Direct document permission grant.
- Folder permission grant (when no direct grant).
- Department permission grant (when the user has a department).
- Expiry check — denied if `expires_at` is in the past.
- ABAC conditions — `ip_range`, `time_window`, `require_mfa`.
Cache key: vyxlo:permissions:{user_id}:read:{doc_id} with a 5-minute TTL. Always invalidated on grant/revoke; bypassed when an ABAC RequestContext is in play.
Data Models
Document Object
Permission Grant Object (with ABAC conditions)
All ABAC conditions are AND-ed. conditions: null means unrestricted. Document owners always bypass ABAC conditions.
Workflow Object
Audit Log Entry (hash-chained)
Each row’s hash commits to the previous row’s previous_hash. GET /audit-logs/verify walks the chain and returns first_tampered_id if any row has been altered.
Share Link Object
Real-Time Protocols
WSWebSocket — Document Collaboration
Authenticated using the OIDC JWT (passed as a query param or `Authorization` header).
WSWebSocket — Notifications
Per-user notification stream. Each event carries a running `unread_count`, so the client never has to re-poll.
SSEServer-Sent Events — Document Q&A
The chat endpoint streams the LLM response token by token (text/event-stream). Citations and the token count arrive on the final `done` event.
File Downloads — Presigned URLs
File bytes never flow through the VyXlo API server. The download endpoint returns a 15-minute presigned MinIO URL; the client fetches the file directly from object storage. This scales to very large files without API server memory pressure.
Error Reference
Every error follows the same shape: {"detail": "...", "error_code": "..."} with the appropriate HTTP status.
| Status | Class | When |
|---|---|---|
| 400 | BadRequestException | Malformed request or business rule violation |
| 401 | UnauthorizedException | Missing or invalid JWT |
| 403 | ForbiddenException | Authenticated but insufficient permission (or ABAC condition failed) |
| 404 | ResourceNotFoundException | Entity not found, or belongs to a different organization |
| 409 | ConflictException | State conflict (locked document, duplicate slug, active workflow exists) |
| 410 | Gone | Expired token or share link |
| 422 | Validation Error | Pydantic schema validation failed (invalid enum, missing required field) |
| 500 | Internal Server Error | Unexpected server error — request_id is in server logs |
Sections
Resource Groups
/api/docs
Comments
5 endpoints