File Management
Apps store their files (HTML, CSS, JavaScript, images, etc.) in an associated Library. The file management endpoints provide CRUD operations for app content.
Important Concepts
Library Association: Each app has a libraryId field that references its file storage. Apps without a library cannot use file management endpoints.
Path Structure: Files are organized hierarchically using forward-slash paths (e.g., index.html, css/styles.css, images/logo.png).
Content Types: Files have MIME types that determine how they're served. The system auto-detects types from file extensions.
Timestamp Updates: Any write operation (PUT, PATCH, upload) updates the app's updatedAt and defnUpdatedAt timestamps.
GET /api/apps/{id}/contents/{path*}
Read a file from the app's library.
Authentication: Required (session or token)
Permissions Required: Member role (run permission)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | App UUID or natural ID |
path | string | File path within the library (e.g., index.html, css/styles.css) |
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
download | boolean | false | Force download instead of inline display |
Response:
Returns the file content as a binary stream with appropriate Content-Type header.
Example Request:
GET /api/apps/analytics:sales-dashboard/contents/index.html
Example Response:
HTTP/1.1 200 OK
Content-Type: text/html
<!DOCTYPE html>
<html>
<head><title>Sales Dashboard</title></head>
<body>...</body>
</html>
Download Example:
GET /api/apps/analytics:sales-dashboard/contents/logo.png?download=true
HTTP/1.1 200 OK
Content-Type: image/png
Content-Disposition: attachment; filename="logo.png"
[binary data]
Error Responses:
400 Bad Request- App has no associated library404 Not Found- App doesn't exist, user lacks access, or file not found403 Forbidden- User lacks run permission
PUT /api/apps/{id}/contents/{path*}
Write or create a file in the app's library.
Authentication: Required
Permissions Required: Member+ role (write permission)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | App UUID or natural ID |
path | string | File path within the library |
Payload:
{
"content": "<!DOCTYPE html>\n<html>...",
"contentType": "text/html",
"encoding": "utf8"
}
Payload Fields:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
content | string | Yes | - | File content (can be empty string) |
contentType | string | No | Auto-detect | MIME type |
encoding | string | No | utf8 | utf8 or base64 |
Response:
Returns the created/updated file metadata with status 201 Created (new file) or 200 OK (updated file).
{
"id": "file-uuid-1234",
"libraryId": "lib-uuid-5678",
"filename": "index.html",
"directory": false,
"parentId": null,
"contentType": "text/html",
"size": 1234,
"encoding": "utf8",
"createdAt": "2024-02-13T10:00:00.000Z",
"updatedAt": "2024-02-13T10:15:00.000Z"
}
Example - Create HTML File:
PUT /api/apps/analytics:sales-dashboard/contents/index.html
Content-Type: application/json
{
"content": "<!DOCTYPE html>\n<html>\n <head><title>Dashboard</title></head>\n <body><h1>Sales Dashboard</h1></body>\n</html>",
"contentType": "text/html"
}
Example - Upload Binary File (Base64):
PUT /api/apps/analytics:sales-dashboard/contents/logo.png
Content-Type: application/json
{
"content": "iVBORw0KGgoAAAANSUhEUgAAAAUA...",
"contentType": "image/png",
"encoding": "base64"
}
Side Effects:
- Updates app's
updatedAtanddefnUpdatedAttimestamps - Creates parent directories as needed
- Overwrites existing file if present
Error Responses:
400 Bad Request- App has no library, or invalid payload404 Not Found- App doesn't exist or user lacks access403 Forbidden- User lacks write permission
PATCH /api/apps/{id}/contents/{path*}
Perform a patch operation on an existing file (replace, append, prepend, or insert text).
Authentication: Required
Permissions Required: Member+ role (write permission)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | App UUID or natural ID |
path | string | File path within the library |
Payload:
The payload structure depends on the operation:
Replace Operation
{
"operation": "replace",
"search": "oldText",
"replacement": "newText",
"replaceAll": false
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
operation | string | Yes | - | Must be "replace" |
search | string | Yes | - | Text to search for |
replacement | string | Yes | - | Replacement text (can be empty) |
replaceAll | boolean | No | false | Replace all occurrences (default: first only) |
Append Operation
{
"operation": "append",
"text": "Text to add at end"
}
Prepend Operation
{
"operation": "prepend",
"text": "Text to add at beginning"
}
Insert Operation
{
"operation": "insert",
"text": "Text to insert",
"insertAt": 100
}
| Field | Type | Required | Description |
|---|---|---|---|
operation | string | Yes | Must be "insert" |
text | string | Yes | Text to insert |
insertAt | integer | Yes | Character position (0-based) |
Response:
Returns the updated file metadata.
{
"id": "file-uuid-1234",
"libraryId": "lib-uuid-5678",
"filename": "index.html",
"size": 1456,
"updatedAt": "2024-02-13T10:20:00.000Z"
}
Example - Replace Text:
PATCH /api/apps/analytics:sales-dashboard/contents/index.html
Content-Type: application/json
{
"operation": "replace",
"search": "<title>Old Title</title>",
"replacement": "<title>New Title</title>"
}
Example - Append Script Tag:
PATCH /api/apps/analytics:sales-dashboard/contents/index.html
Content-Type: application/json
{
"operation": "append",
"text": "\n<script src=\"analytics.js\"></script>"
}
Side Effects:
- Updates app's
updatedAtanddefnUpdatedAttimestamps - File must exist (PATCH doesn't create new files)
Error Responses:
400 Bad Request- Invalid operation or missing required fields404 Not Found- App or file doesn't exist403 Forbidden- User lacks write permission
GET /api/apps/{id}/files
List all files in the app's library.
Authentication: Required
Permissions Required: Member+ role (write permission)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | App UUID or natural ID |
Response:
Returns an array of file metadata objects.
[
{
"id": "file-uuid-1",
"libraryId": "lib-uuid-5678",
"filename": "index.html",
"directory": false,
"parentId": null,
"contentType": "text/html",
"size": 1234,
"createdAt": "2024-02-13T10:00:00.000Z",
"updatedAt": "2024-02-13T10:15:00.000Z"
},
{
"id": "file-uuid-2",
"libraryId": "lib-uuid-5678",
"filename": "styles.css",
"directory": false,
"parentId": "dir-uuid-1",
"contentType": "text/css",
"size": 567,
"createdAt": "2024-02-13T10:00:00.000Z",
"updatedAt": "2024-02-13T10:00:00.000Z"
},
{
"id": "dir-uuid-1",
"libraryId": "lib-uuid-5678",
"filename": "css",
"directory": true,
"parentId": null,
"createdAt": "2024-02-13T10:00:00.000Z",
"updatedAt": "2024-02-13T10:00:00.000Z"
}
]
Key Fields:
| Field | Description |
|---|---|
directory | true if this is a directory, false for files |
parentId | UUID of parent directory (null for root level) |
contentType | MIME type (only present for files) |
size | File size in bytes (only present for files) |
Error Responses:
404 Not Found- App doesn't exist or user lacks access403 Forbidden- User lacks write permission
DELETE /api/apps/{id}/files/{fileId}
Delete a file or directory from the app's library.
Authentication: Required
Permissions Required: Member+ role (write permission)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | App UUID or natural ID |
fileId | string | File UUID (from the files list) |
Response:
Returns 204 No Content on success.
Behavior:
- Deleting a directory also deletes all contained files and subdirectories
- Updates app's
updatedAtanddefnUpdatedAttimestamps
Error Responses:
404 Not Found- App or file doesn't exist403 Forbidden- User lacks write permission
POST /api/apps/{id}/_upload
Upload a file using chunked multipart upload. This endpoint is designed for large file uploads from web browsers.
Authentication: Required
Permissions Required: Member+ role (write permission)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | App UUID or natural ID |
Payload:
Multipart form data with file chunks. Refer to the chunked upload implementation for specific field requirements.
Response:
Returns upload status and file metadata.
Use Case:
This endpoint is used by the file upload UI for:
- Large binary files
- Progress tracking
- Resume capability
Error Responses:
404 Not Found- App doesn't exist or user lacks access403 Forbidden- User lacks write permission
Common File Operations
Create a Simple App
// 1. Create the app
const app = await POST('/api/apps', {
type: 'report',
name: 'My Dashboard'
});
// 2. Add HTML file
await PUT(`/api/apps/${app.id}/contents/index.html`, {
content: '<!DOCTYPE html><html>...',
contentType: 'text/html'
});
// 3. Add CSS file
await PUT(`/api/apps/${app.id}/contents/styles.css`, {
content: 'body { margin: 0; }',
contentType: 'text/css'
});
// 4. Add JavaScript file
await PUT(`/api/apps/${app.id}/contents/app.js`, {
content: 'console.log("Ready");',
contentType: 'application/javascript'
});
Update a File
// Read current content
const response = await GET('/api/apps/analytics:sales-dashboard/contents/index.html');
const currentHTML = await response.text();
// Modify it
const updatedHTML = currentHTML.replace('Old Title', 'New Title');
// Write back
await PUT('/api/apps/analytics:sales-dashboard/contents/index.html', {
content: updatedHTML,
contentType: 'text/html'
});
Bulk Replace Across File
// Replace all occurrences of a string
await PATCH('/api/apps/analytics:sales-dashboard/contents/index.html', {
operation: 'replace',
search: 'oldClassName',
replacement: 'newClassName',
replaceAll: true
});
List and Delete Old Files
// Get all files
const files = await GET('/api/apps/analytics:sales-dashboard/files');
// Find old temp files
const tempFiles = files.filter(f => f.filename.startsWith('temp-'));
// Delete them
for (const file of tempFiles) {
await DELETE(`/api/apps/analytics:sales-dashboard/files/${file.id}`);
}