API Documentation¶
Complete reference for the ocmonica REST and gRPC APIs.
Table of Contents¶
- Overview
- Base URL
- Authentication
- REST API
- Health Check
- File Operations
- Directory Operations
- Filesystem Operations
- Search Operations
- gRPC API
- FileService
- DirectoryService
- SearchService
- PlaybackPositionService
- FilesystemService
- Data Models
- Error Handling
- Examples
Overview¶
ocmonica provides two API interfaces:
- REST API: Standard HTTP JSON API for web and mobile clients
- gRPC API: High-performance RPC API with streaming support via ConnectRPC
Both APIs share the same business logic and data models, providing consistent behavior across interfaces.
API Versions¶
- Current: v1
- Stability: Alpha
- Base Path:
/api/v1(REST),/ocmonica.v1.*(gRPC)
Base URL¶
Development¶
Production¶
Docker Container¶
Authentication¶
Current Status: ✅ Fully Implemented
ocmonica supports two authentication methods: - JWT Tokens: Bearer token authentication with RS256 (4096-bit RSA keys) - API Keys: Long-lived API keys with scoped permissions
Authentication Methods¶
JWT Bearer Tokens¶
After registration or login, you'll receive an access token and refresh token:
# Register or login
curl -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"SecurePass123!"}'
# Use access token
curl http://localhost:8080/api/v1/files \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
Token Details: - Access Token: Short-lived (15 minutes default) - Refresh Token: Long-lived (7 days default) - Algorithm: RS256 with 4096-bit RSA keys - Claims: user_id, organization_id, role, permissions
API Keys¶
API keys provide programmatic access with scoped permissions:
# Create API key (requires authentication)
curl -X POST http://localhost:8080/api/v1/auth/api-keys \
-H "Authorization: Bearer <access-token>" \
-H "Content-Type: application/json" \
-d '{
"name": "production-key",
"organization_id": "org_...",
"permissions": ["file:read", "file:write"]
}'
# Use API key
curl http://localhost:8080/api/v1/files \
-H "X-API-Key: key_..."
API Key Features: - Scoped permissions (subset of user's permissions) - Expiration dates (optional) - Revocable at any time - Max 10 keys per user (configurable)
Multi-tenancy¶
All authenticated requests are scoped to the user's organization. Users belong to one or more organizations, and all file operations are isolated within the active organization context.
REST API¶
Health Check¶
GET /health¶
Check if the server is running and healthy.
Response:
Status Codes:
- 200 OK: Server is healthy
- 503 Service Unavailable: Server is unhealthy
Authentication Endpoints¶
All authentication endpoints are prefixed with /api/v1/auth.
POST /api/v1/auth/register¶
Register a new user account.
Request Body:
Example:
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"admin","email":"admin@example.com","password":"SecurePass123!"}'
Response (201 Created):
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "eyJhbGciOiJSUzI1NiIs...",
"user": {
"id": "usr_...",
"username": "admin",
"email": "admin@example.com",
"is_active": true,
"created_at": "2025-10-09T12:00:00Z"
}
}
Error Responses:
- 400 Bad Request: Invalid input or weak password
- 409 Conflict: Username or email already exists
- 403 Forbidden: Registration disabled
POST /api/v1/auth/login¶
Login with username and password.
Request Body:
Response (200 OK):
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "eyJhbGciOiJSUzI1NiIs...",
"user": {
"id": "usr_...",
"username": "admin",
"email": "admin@example.com"
}
}
Error Responses:
- 401 Unauthorized: Invalid credentials
- 403 Forbidden: User account is inactive
POST /api/v1/auth/refresh¶
Refresh access token using refresh token.
Request Body:
Response (200 OK):
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"refresh_token": "eyJhbGciOiJSUzI1NiIs...",
"user": { ... }
}
Error Responses:
- 401 Unauthorized: Invalid or expired refresh token
POST /api/v1/auth/logout¶
Revoke a refresh token (logout from current device).
Headers:
- Authorization: Bearer <access-token>
Request Body:
Response (200 OK):
POST /api/v1/auth/logout-all¶
Revoke all refresh tokens for the user (logout from all devices).
Headers:
- Authorization: Bearer <access-token>
Response (200 OK):
POST /api/v1/auth/change-password¶
Change user password.
Headers:
- Authorization: Bearer <access-token>
Request Body:
Response (200 OK):
Error Responses:
- 401 Unauthorized: Invalid current password
- 400 Bad Request: Weak new password
POST /api/v1/auth/api-keys¶
Create a new API key.
Headers:
- Authorization: Bearer <access-token>
Request Body:
{
"name": "production-key",
"organization_id": "org_...",
"permissions": ["file:read", "file:write"],
"expires_at": "2026-01-01T00:00:00Z"
}
Response (201 Created):
{
"id": "key_...",
"key": "ocm_live_...",
"name": "production-key",
"permissions": ["file:read", "file:write"],
"created_at": "2025-10-09T12:00:00Z",
"expires_at": "2026-01-01T00:00:00Z"
}
Note: The key value is only returned once. Store it securely.
GET /api/v1/auth/api-keys¶
List user's API keys.
Headers:
- Authorization: Bearer <access-token>
Response (200 OK):
{
"keys": [
{
"id": "key_...",
"name": "production-key",
"permissions": ["file:read", "file:write"],
"created_at": "2025-10-09T12:00:00Z",
"last_used_at": "2025-10-09T14:30:00Z",
"expires_at": "2026-01-01T00:00:00Z"
}
]
}
DELETE /api/v1/auth/api-keys/:id¶
Revoke an API key.
Headers:
- Authorization: Bearer <access-token>
Response (200 OK):
File Operations¶
All file endpoints are prefixed with /api/v1/files.
POST /api/v1/files¶
Upload a new file.
Request (multipart/form-data):
Example (curl):
Response (201 Created):
{
"id": "file-abc123",
"name": "document.pdf",
"path": "/documents/document.pdf",
"parent_id": "dir-123",
"type": "file",
"size": 1048576,
"mime_type": "application/pdf",
"hash": "sha256:abc123...",
"created_at": "2025-10-06T12:00:00Z",
"updated_at": "2025-10-06T12:00:00Z",
"accessed_at": "2025-10-06T12:00:00Z",
"permissions": "",
"tags": [],
"is_deleted": false
}
Error Responses:
- 400 Bad Request: Missing or invalid file
- 413 Payload Too Large: File exceeds size limit
- 500 Internal Server Error: Upload failed
GET /api/v1/files/:id¶
Get file metadata by ID.
Parameters:
- id (path): File ID
Example:
Response (200 OK):
{
"id": "file-abc123",
"name": "document.pdf",
"path": "/documents/document.pdf",
"parent_id": "dir-123",
"type": "file",
"size": 1048576,
"mime_type": "application/pdf",
"hash": "sha256:abc123...",
"created_at": "2025-10-06T12:00:00Z",
"updated_at": "2025-10-06T12:00:00Z",
"accessed_at": "2025-10-06T12:00:00Z",
"permissions": "",
"tags": [],
"is_deleted": false
}
Error Responses:
- 404 Not Found: File does not exist
GET /api/v1/files/:id/download¶
Download file content.
Parameters:
- id (path): File ID
Example:
Response (200 OK):
- Binary file content with appropriate Content-Type header
- Content-Disposition: attachment; filename="document.pdf"
Error Responses:
- 404 Not Found: File does not exist
GET /api/v1/files/:id/content¶
Get file content for preview (text files only).
Parameters:
- id (path): File ID
Example:
Response (200 OK):
Supported MIME Types:
- text/plain
- text/markdown
- application/json
- text/html
- text/css
- text/javascript
- All text/* types
Error Responses:
- 404 Not Found: File does not exist
- 400 Bad Request: File is not a text file (binary files not supported)
- 413 Payload Too Large: File exceeds preview size limit (10MB)
Use Case: This endpoint is designed for file preview in the frontend. It returns text content that can be rendered directly in the UI with syntax highlighting or Markdown rendering.
GET /api/v1/files/:id/stream¶
Stream file content for media playback (video/audio).
Parameters:
- id (path): File ID
Headers:
- Range (optional): Byte range for partial content (e.g., bytes=0-1023)
Example:
# Full file
curl http://localhost:8080/api/v1/files/file-abc123/stream
# Partial content (seeking)
curl -H "Range: bytes=1024-2047" http://localhost:8080/api/v1/files/file-abc123/stream
Response (200 OK or 206 Partial Content):
- Binary media content with appropriate Content-Type header
- Accept-Ranges: bytes header (indicates seeking support)
- Content-Range header (for partial responses)
- Content-Length header
Supported MIME Types:
- Video: video/mp4, video/webm, video/ogg
- Audio: audio/mpeg, audio/ogg, audio/wav, audio/mp4, audio/x-m4a
HTTP Range Requests (RFC 7233):
This endpoint implements HTTP Range Requests, enabling video/audio seeking:
- Client sends Range: bytes=start-end header
- Server responds with 206 Partial Content status
- Server includes Content-Range: bytes start-end/total header
- Enables smooth seeking in media players (react-player, HTML5 video/audio)
Error Responses:
- 404 Not Found: File does not exist
- 416 Range Not Satisfiable: Invalid byte range
- 400 Bad Request: File is not a media file
Use Case: This endpoint is designed for streaming video and audio files with seeking support. Frontend media players (react-player, react-h5-audio-player) automatically use Range requests for seeking, providing a smooth playback experience.
GET /api/v1/files¶
List files with optional filtering and pagination.
Query Parameters:
- parent_id (optional): Filter by parent directory ID
- limit (optional, default: 50): Number of results per page
- offset (optional, default: 0): Number of results to skip
Example:
Response (200 OK):
{
"files": [
{
"id": "file-1",
"name": "file1.txt",
"path": "/documents/file1.txt",
"type": "file",
...
},
{
"id": "file-2",
"name": "file2.txt",
"path": "/documents/file2.txt",
"type": "file",
...
}
],
"total": 42
}
Error Responses:
- 400 Bad Request: Invalid query parameters
DELETE /api/v1/files/:id¶
Delete a file (soft delete).
Parameters:
- id (path): File ID
Example:
Response (200 OK):
Error Responses:
- 404 Not Found: File does not exist
PUT /api/v1/files/:id/move¶
Move a file to a different parent directory.
Parameters:
- id (path): File ID
Request Body:
Example:
curl -X PUT http://localhost:8080/api/v1/files/file-abc123/move \
-H "Content-Type: application/json" \
-d '{"new_parent_id":"dir-456"}'
Response (200 OK):
{
"id": "file-abc123",
"name": "document.pdf",
"path": "/archive/document.pdf",
"parent_id": "dir-456",
...
}
Error Responses:
- 404 Not Found: File or parent directory does not exist
- 400 Bad Request: Invalid parent ID
POST /api/v1/files/:id/copy¶
Copy a file to a new location.
Parameters:
- id (path): File ID
Request Body:
Example:
curl -X POST http://localhost:8080/api/v1/files/file-abc123/copy \
-H "Content-Type: application/json" \
-d '{"destination_parent_id":"dir-789","new_name":"document-copy.pdf"}'
Response (201 Created):
{
"id": "file-xyz789",
"name": "document-copy.pdf",
"path": "/backups/document-copy.pdf",
"parent_id": "dir-789",
...
}
Error Responses:
- 404 Not Found: File or destination directory does not exist
- 400 Bad Request: Invalid parameters
PATCH /api/v1/files/:id¶
Update file metadata.
Parameters:
- id (path): File ID
Request Body:
Example:
curl -X PATCH http://localhost:8080/api/v1/files/file-abc123 \
-H "Content-Type: application/json" \
-d '{"name":"renamed-document.pdf"}'
Response (200 OK):
{
"id": "file-abc123",
"name": "renamed-document.pdf",
"path": "/documents/renamed-document.pdf",
...
}
Error Responses:
- 404 Not Found: File does not exist
- 400 Bad Request: Invalid filename or parent ID
Directory Operations¶
All directory endpoints are prefixed with /api/v1/directories.
POST /api/v1/directories¶
Create a new directory.
Request Body:
Example:
curl -X POST http://localhost:8080/api/v1/directories \
-H "Content-Type: application/json" \
-d '{"name":"documents","parent_id":"root"}'
Response (201 Created):
{
"file": {
"id": "dir-123",
"name": "documents",
"path": "/documents",
"parent_id": "root",
"type": "directory",
"size": 0,
"mime_type": "inode/directory",
"created_at": "2025-10-06T12:00:00Z",
...
},
"child_count": 0
}
Error Responses:
- 400 Bad Request: Missing or invalid name
- 404 Not Found: Parent directory does not exist
GET /api/v1/directories/:id¶
Get directory metadata with child count.
Parameters:
- id (path): Directory ID
Example:
Response (200 OK):
{
"file": {
"id": "dir-123",
"name": "documents",
"path": "/documents",
"type": "directory",
...
},
"child_count": 42
}
Error Responses:
- 404 Not Found: Directory does not exist
GET /api/v1/directories/:id/children¶
List immediate children of a directory.
Parameters:
- id (path): Directory ID
Query Parameters:
- limit (optional, default: 50): Number of results per page
- offset (optional, default: 0): Number of results to skip
Example:
Response (200 OK):
{
"files": [
{
"id": "file-1",
"name": "file1.txt",
"type": "file",
...
},
{
"id": "dir-456",
"name": "subfolder",
"type": "directory",
...
}
],
"total": 15
}
Error Responses:
- 404 Not Found: Directory does not exist
- 400 Bad Request: Invalid query parameters
GET /api/v1/directories/:id/tree¶
Get directory tree with nested children.
Parameters:
- id (path): Directory ID
Query Parameters:
- depth (optional, default: 0 = unlimited): Maximum depth to traverse
Example:
Response (200 OK):
{
"directory": {
"file": {
"id": "dir-123",
"name": "documents",
...
},
"child_count": 2
},
"children": [
{
"directory": {
"file": {
"id": "dir-456",
"name": "subfolder",
...
},
"child_count": 3
},
"children": [...]
}
]
}
Error Responses:
- 404 Not Found: Directory does not exist
- 400 Bad Request: Invalid depth parameter
DELETE /api/v1/directories/:id¶
Delete a directory.
Parameters:
- id (path): Directory ID
Query Parameters:
- recursive (optional, default: false): Delete all contents
Example:
Response (200 OK):
Error Responses:
- 404 Not Found: Directory does not exist
- 400 Bad Request: Directory not empty and recursive=false
Filesystem Operations¶
GET /api/v1/filesystem/path-suggestions¶
Get filesystem path suggestions for autocomplete functionality.
Query Parameters:
- prefix (required): Path prefix to search for (e.g., /Users/j)
- limit (optional, default: 10): Maximum number of suggestions (1-50)
Example:
curl "http://localhost:8080/api/v1/filesystem/path-suggestions?prefix=/Users&limit=5" \
-H "Authorization: Bearer <access-token>"
Response (200 OK):
Features:
- Returns only directories (not files)
- Filters by prefix matching
- Sorted alphabetically
- Security: Blocks access to system directories (/etc, /root, /sys, /proc, /dev)
- Security: Prevents path traversal attacks (.. patterns rejected)
- 5-second timeout per request
- Requires org:read permission
Error Responses:
- 400 Bad Request: Missing or invalid prefix parameter
- 400 Bad Request: Invalid path (contains .. or invalid characters)
- 400 Bad Request: Invalid limit (must be 1-50)
- 403 Forbidden: Access to forbidden path (system directories)
- 403 Forbidden: Missing required permission (org:read)
- 404 Not Found: Prefix path does not exist
- 500 Internal Server Error: Failed to read directory or operation timeout
Use Case: This endpoint is designed for path autocomplete in UI components (e.g., watched folder path input). It provides fast, secure directory suggestions as users type.
Search Operations¶
GET /api/v1/search¶
Search for files with filters and pagination.
Query Parameters:
- q (optional): Search query (searches name and path)
- path (optional): Filter by path prefix
- type (optional): Filter by file type (file, directory, symlink)
- tags (optional): Comma-separated list of tag IDs
- min_size (optional): Minimum file size in bytes
- max_size (optional): Maximum file size in bytes
- start_date (optional): Filter files created after this date (ISO 8601)
- end_date (optional): Filter files created before this date (ISO 8601)
- limit (optional, default: 50): Number of results per page
- offset (optional, default: 0): Number of results to skip
- sort_by (optional, default: name): Sort field (name, size, created_at, updated_at)
- sort_order (optional, default: asc): Sort order (asc, desc)
Example:
curl "http://localhost:8080/api/v1/search?q=document&type=file&min_size=1024&sort_by=size&sort_order=desc&limit=20"
Response (200 OK):
{
"files": [
{
"id": "file-1",
"name": "large-document.pdf",
"size": 5242880,
...
},
{
"id": "file-2",
"name": "medium-document.pdf",
"size": 2097152,
...
}
],
"total": 42,
"page": 1,
"page_size": 20,
"total_pages": 3
}
Error Responses:
- 400 Bad Request: Invalid query parameters
gRPC API¶
The gRPC API uses ConnectRPC for HTTP/2-based RPC with JSON support. All services are available at /ocmonica.v1.*.
Protocol¶
- Transport: HTTP/2 (with h2c for development)
- Encoding: Protocol Buffers (binary) or JSON
- Base URL: Same as REST API
FileService¶
Service path: /ocmonica.v1.FileService/
UploadFile (Client Streaming)¶
Upload a file using streaming for large files.
Request (stream):
message UploadFileRequest {
oneof data {
FileMetadata metadata = 1; // First message
bytes chunk = 2; // Subsequent messages
}
}
Response:
Example (ConnectRPC Web):
const client = createPromiseClient(FileService, transport);
const stream = client.uploadFile();
await stream.send({ metadata: { name: "file.pdf" } });
await stream.send({ chunk: buffer1 });
await stream.send({ chunk: buffer2 });
const response = await stream.complete();
DownloadFile (Server Streaming)¶
Download a file using streaming.
Request:
Response (stream):
message DownloadFileResponse {
oneof data {
File metadata = 1; // First message
bytes chunk = 2; // Subsequent messages
}
}
GetFile (Unary)¶
Get file metadata.
Request:
Response:
ListFiles (Unary)¶
List files with pagination.
Request:
Response:
DeleteFile (Unary)¶
Delete a file.
Request:
Response:
MoveFile (Unary)¶
Move a file to a different parent.
Request:
Response:
CopyFile (Unary)¶
Copy a file.
Request:
message CopyFileRequest {
string id = 1;
string destination_parent_id = 2;
optional string new_name = 3;
}
Response:
UpdateFile (Unary)¶
Update file metadata.
Request:
message UpdateFileRequest {
string id = 1;
optional string name = 2;
optional string parent_id = 3;
}
Response:
DirectoryService¶
Service path: /ocmonica.v1.DirectoryService/
CreateDirectory (Unary)¶
Create a new directory.
Request:
Response:
GetDirectory (Unary)¶
Get directory metadata with child count.
Request:
Response:
ListChildren (Unary)¶
List immediate children of a directory.
Request:
Response:
GetTree (Unary)¶
Get directory tree.
Request:
Response:
DeleteDirectory (Unary)¶
Delete a directory.
Request:
Response:
SearchService¶
Service path: /ocmonica.v1.SearchService/
Search (Unary)¶
Search for files with filters.
Request:
Response:
SearchStream (Server Streaming)¶
Streaming search for large result sets.
Request:
Response (stream):
PlaybackPositionService¶
Service path: /ocmonica.v1.PlaybackPositionService/
GetPlaybackPosition (Unary)¶
Get the saved playback position for a media file.
Request:
Response:
message GetPlaybackPositionResponse {
string file_id = 1;
double position_seconds = 2; // Playback position in seconds
double duration_seconds = 3; // Total duration in seconds
google.protobuf.Timestamp last_updated = 4;
}
Example (ConnectRPC Web):
import { createPromiseClient } from "@connectrpc/connect";
import { PlaybackPositionService } from "@/gen/ocmonica/v1/playback_position_service_connect";
const client = createPromiseClient(PlaybackPositionService, transport);
// Get playback position
const response = await client.getPlaybackPosition({
fileId: "file-abc123",
});
console.log(`Resume from: ${response.positionSeconds}s / ${response.durationSeconds}s`);
Error Codes:
- NOT_FOUND (5): No playback position saved for this file
- INVALID_ARGUMENT (3): Missing or invalid file_id
Use Case: Retrieve the last saved playback position for a video or audio file, enabling "resume from where you left off" functionality for audiobooks and videos.
SavePlaybackPosition (Unary)¶
Save the current playback position for a media file.
Request:
message SavePlaybackPositionRequest {
string file_id = 1;
double position_seconds = 2; // Current position in seconds
double duration_seconds = 3; // Total duration in seconds
}
Response:
Example (ConnectRPC Web):
// Save position every 5 seconds while playing
setInterval(() => {
const currentPosition = player.getCurrentTime();
const duration = player.getDuration();
await client.savePlaybackPosition({
fileId: "file-abc123",
positionSeconds: currentPosition,
durationSeconds: duration,
});
}, 5000);
Error Codes:
- INVALID_ARGUMENT (3): Missing required fields or invalid values
- INTERNAL (13): Failed to save position
Use Case: Automatically save playback progress as users watch videos or listen to audio files. Frontend media players use this endpoint to persist position every few seconds, enabling seamless resume across devices and sessions.
FilesystemService¶
Service path: /ocmonica.v1.FilesystemService/
GetPathSuggestions (Unary)¶
Get host filesystem directory paths for autocomplete functionality when selecting watched folder paths.
Request:
message GetPathSuggestionsRequest {
string prefix = 1; // Path prefix (e.g., "/Users/j")
int32 limit = 2; // Max suggestions (1-50, default 10)
}
Response:
message GetPathSuggestionsResponse {
repeated string suggestions = 1; // Array of absolute directory paths
}
Example (ConnectRPC Web):
import { createPromiseClient } from "@connectrpc/connect";
import { FilesystemService } from "@/gen/ocmonica/v1/filesystem_service_connect";
import { createConnectTransport } from "@connectrpc/connect-web";
const transport = createConnectTransport({
baseUrl: "http://localhost:8080",
});
const client = createPromiseClient(FilesystemService, transport);
// Get path suggestions
const response = await client.getPathSuggestions({
prefix: "/Users",
limit: 5,
});
console.log("Directory suggestions:", response.suggestions);
// Output: ["/Users/alice", "/Users/bob", "/Users/charlie", ...]
Features:
- Returns only directories (not files)
- Filters by prefix matching
- Sorted alphabetically
- Security: Blocks access to system directories (/etc, /root, /sys, /proc, /dev)
- Security: Prevents path traversal attacks (.. patterns rejected)
- 5-second timeout per request
- Requires org:read permission
Error Codes:
- INVALID_ARGUMENT (3): Missing prefix, invalid path (contains ..), or invalid limit
- PERMISSION_DENIED (7): Access to forbidden path or missing org:read permission
- NOT_FOUND (5): Prefix path does not exist
- INTERNAL (13): Failed to read directory or operation timeout
Use Case: This endpoint is designed for path autocomplete in UI components when users configure watched folder paths. It provides fast, secure directory suggestions as users type, preventing typos and ensuring valid paths are selected.
Data Models¶
File¶
{
id: string; // Unique identifier
name: string; // File name
path: string; // Full path
parent_id?: string; // Parent directory ID (null for root)
type: "file" | "directory" | "symlink";
size: number; // Size in bytes
mime_type: string; // MIME type
hash: string; // SHA-256 hash
created_at: string; // ISO 8601 timestamp
updated_at: string; // ISO 8601 timestamp
accessed_at: string; // ISO 8601 timestamp
permissions: string; // Future use
tags: Tag[]; // Associated tags
is_deleted: boolean; // Soft delete flag
deleted_at?: string; // Deletion timestamp
}
Directory¶
DirectoryNode¶
Tag¶
{
id: string; // Unique identifier
name: string; // Tag name
color: string; // Color code (hex)
created_at: string; // ISO 8601 timestamp
}
SearchQuery¶
{
query?: string; // Search text
path?: string; // Path filter
file_type?: "file" | "directory" | "symlink";
tags?: string[]; // Tag IDs
min_size?: number; // Minimum size (bytes)
max_size?: number; // Maximum size (bytes)
start_date?: string; // ISO 8601 date
end_date?: string; // ISO 8601 date
limit: number; // Results per page
offset: number; // Results to skip
sort_by: string; // Sort field
sort_order: "asc" | "desc"; // Sort direction
}
SearchResult¶
{
files: File[]; // Matched files
total: number; // Total matches
page: number; // Current page
page_size: number; // Results per page
total_pages: number; // Total pages
}
Error Handling¶
REST API Errors¶
All errors follow a consistent JSON format:
{
"error": {
"code": "ERR_FILE_NOT_FOUND",
"message": "File not found",
"details": {
"id": "file-abc123"
}
}
}
HTTP Status Codes¶
200 OK: Success201 Created: Resource created400 Bad Request: Invalid input404 Not Found: Resource not found413 Payload Too Large: File too large500 Internal Server Error: Server error503 Service Unavailable: Server unhealthy
gRPC Errors¶
gRPC errors use standard gRPC status codes:
OK(0): SuccessINVALID_ARGUMENT(3): Bad inputNOT_FOUND(5): Resource not foundALREADY_EXISTS(6): Resource existsRESOURCE_EXHAUSTED(8): Rate limit or quotaINTERNAL(13): Server errorUNAVAILABLE(14): Service unavailable
Example gRPC Error:
Examples¶
Upload and Download a File (REST)¶
# Upload
curl -X POST http://localhost:8080/api/v1/files \
-F "file=@myfile.pdf" \
-o upload-response.json
# Extract ID
FILE_ID=$(cat upload-response.json | jq -r '.id')
# Download
curl -O http://localhost:8080/api/v1/files/$FILE_ID/download
Create Directory Structure (REST)¶
# Create root directory
curl -X POST http://localhost:8080/api/v1/directories \
-H "Content-Type: application/json" \
-d '{"name":"documents"}' \
| jq -r '.file.id' > root_id.txt
# Create subdirectory
curl -X POST http://localhost:8080/api/v1/directories \
-H "Content-Type: application/json" \
-d "{\"name\":\"work\",\"parent_id\":\"$(cat root_id.txt)\"}"
Search and Filter (REST)¶
# Search for large PDF files
curl "http://localhost:8080/api/v1/search?q=report&type=file&min_size=1048576&sort_by=size&sort_order=desc" \
| jq '.files[] | {name, size, created_at}'
Using gRPC with ConnectRPC (TypeScript)¶
import { createPromiseClient } from "@connectrpc/connect";
import { FileService } from "./gen/ocmonica/v1/file_service_connect";
import { createConnectTransport } from "@connectrpc/connect-web";
// Create transport
const transport = createConnectTransport({
baseUrl: "http://localhost:8080",
});
// Create client
const client = createPromiseClient(FileService, transport);
// List files
const response = await client.listFiles({
parentId: "dir-123",
limit: 10,
offset: 0,
});
console.log(`Found ${response.total} files`);
response.files.forEach(file => {
console.log(`- ${file.name} (${file.size} bytes)`);
});
Error Handling (TypeScript)¶
import { ConnectError } from "@connectrpc/connect";
try {
const response = await client.getFile({ id: "nonexistent" });
} catch (err) {
if (err instanceof ConnectError) {
if (err.code === "not_found") {
console.log("File not found");
} else {
console.error("gRPC error:", err.message);
}
}
}
Observability Endpoints¶
GET /metrics¶
Prometheus metrics endpoint (no authentication required).
Response (text/plain):
# HELP ocmonica_file_uploads_total Total number of file uploads
# TYPE ocmonica_file_uploads_total counter
ocmonica_file_uploads_total{mime_type="application/pdf",status="success"} 42
# HELP ocmonica_auth_attempts_total Total number of authentication attempts
# TYPE ocmonica_auth_attempts_total counter
ocmonica_auth_attempts_total{method="password",status="success"} 156
# HELP ocmonica_db_query_duration_seconds Duration of database queries in seconds
# TYPE ocmonica_db_query_duration_seconds histogram
ocmonica_db_query_duration_seconds_bucket{operation="select",le="0.001"} 1024
...
Available Metrics:
- ocmonica_file_uploads_total - File upload counter (by status, mime_type)
- ocmonica_file_downloads_total - File download counter
- ocmonica_file_deletions_total - File deletion counter
- ocmonica_auth_attempts_total - Authentication attempts (by method, status)
- ocmonica_api_key_usage_total - API key usage counter
- ocmonica_db_query_duration_seconds - Database query latency histogram
- ocmonica_search_queries_total - Search query counter
- ocmonica_active_users - Active users gauge
Rate Limiting¶
Current Status: ✅ Implemented
Rate limits are enforced per IP address and per authenticated user:
Limits: - Per IP: 100 requests/minute - Per User (authenticated): 1000 requests/minute - File Uploads: No specific limit (controlled by general rate limit)
Rate Limit Headers: All responses include rate limit headers:
Error Response (429 Too Many Requests):
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Try again in 60 seconds."
}
}
Versioning¶
The API uses URL-based versioning (/api/v1). Breaking changes will increment the version number (/api/v2).
Backward Compatibility¶
Within a major version: - New fields may be added to responses - New optional parameters may be added - New endpoints may be added - Existing fields will not be removed or changed
Deprecation Policy¶
- Deprecated features will be announced 6 months in advance
- Deprecated endpoints will return a
Deprecationheader - Documentation will clearly mark deprecated features
Support¶
For API questions or issues: - GitHub Issues: https://github.com/j-pye/ocmonica/issues - Documentation: See DEVELOPMENT.md - Testing Guide: See TESTING.md