Global
GET /api/health — Service health checkPOST /api/v1/servers/register — Register new server (master token auth)
GET /api/health — Service health checkPOST /api/v1/servers/register — Register new server (master token auth)GET /api/v1/public/{token} — Get share metadataPOST /api/v1/public/{token}/verify — Verify share password: { password }GET /api/v1/public/{token}/download — Download shared fileGET /v1/modules — List all modulesGET /v1/modules/grouped — Modules grouped by menuGET /v1/modules/storage-paths — List storage path namesPOST /v1/modules — Create module: { key, display_name, storage_path, ... }GET /v1/modules/{key} — Get modulePUT /v1/modules/{key} — Update moduleDELETE /v1/modules/{key} — Delete modulePOST /v1/modules/{key}/sync — Queue sync to all serversPOST /v1/modules/{key}/sync-now — Sync immediatelyPOST /v1/modules/sync-all — Queue sync all modulesPOST /v1/modules/sync-all-now — Sync all immediatelyPOST /v1/modules/clear-cache — Clear module cacheGET /v1/{server_code}/ — Server info (includes legacy_mode, is_migrating)PUT /v1/{server_code}/ — Update server: { server_name?, status?, storage_quota_mb?, api_rate_limit? }GET /v1/{server_code}/stats — Storage stats (files_count, directories_count)PUT /v1/{server_code}/storage-config — Set storage config (legacy): { storage_provider, storage_config }GET /v1/{server_code}/storage/configs — List all configured providersPOST /v1/{server_code}/storage/configs/test — Test draft config: { provider, config: { host, port, ... } }POST /v1/{server_code}/storage/configs — Save config: { provider, config, name?, description?, activate? }PUT /v1/{server_code}/storage/configs/{provider} — Update config: { config, name?, activate? }DELETE /v1/{server_code}/storage/configs/{provider} — Delete config (cannot delete active)POST /v1/{server_code}/storage/configs/{provider}/test — Retest saved configPOST /v1/{server_code}/storage/configs/{provider}/activate — Activate provider (requires connection_test_passed)POST /v1/{sc}/storage/upload — Multipart: file (required), path (required), metadata? (JSON string), overwrite? (bool, default true)GET /v1/{sc}/storage/download?path=... — Stream file downloadGET /v1/{sc}/storage/lookup?path=... — File metadata (id, name, mime_type, size, module, record_id)GET /v1/{sc}/storage/list?directory=...&recursive=...&per_page=... — List files/dirs (note: param is "directory" not "path")GET /v1/{sc}/storage/exists?path=...&type=file|directory — Check if file/dir existsDELETE /v1/{sc}/storage?path=... — Delete filePOST /v1/{sc}/storage/copy — { source_path, dest_path }POST /v1/{sc}/storage/move — { source_path, dest_path }POST /v1/{sc}/storage/rename — { path, new_name } (new_name is filename only, not full path)POST /v1/{sc}/storage/mkdir — { path }DELETE /v1/{sc}/storage/rmdir — { path } (deletes dir + all files inside)POST /v1/{sc}/storage/compress — { paths: [...], archive_name?, dest_path? }POST /v1/{sc}/storage/extract — { path (zip file), dest_path? }POST /v1/{sc}/storage/share — { path, expires_at?, password? (min 4), download_limit? }POST /v1/{sc}/storage/bulk-copy — { paths: [...], target_path (directory) }POST /v1/{sc}/storage/bulk-move — { paths: [...], target_path (directory) }POST /v1/{sc}/storage/bulk-delete — { paths: [...] }GET /v1/{sc}/storage/migration/status — Check if migration in progressGET /v1/{sc}/storage/migration/jobs — List last 50 migrationsPOST /v1/{sc}/storage/migration/start — { source_provider, destination_provider, delete_source_files?, overwrite_existing?, preserve_directory_structure?, file_pattern?, activate_destination? }POST /v1/{sc}/storage/migration/{id}/cancel — Cancel running migrationPOST /v1/{sc}/storage/migration/{id}/retry — Retry failed files onlyPOST /v1/{sc}/storage/import — Start import: { provider: "ftp"|"sftp" }GET /v1/{sc}/storage/import/status — Latest import/sync status and progressGET /v1/{sc}/storage/import/history — List past 20 imports/syncsPOST /v1/{sc}/storage/import/sync — Re-sync: { provider: "ftp"|"sftp" } (requires prior completed import)POST /v1/{sc}/storage/import/retry — Retry timed-out directories from last import (no body needed)POST /v1/{sc}/storage/scan-directory — Sync single directory: { path } (legacy_mode only, synchronous)GET /v1/{sc}/modules — List server module configsGET /v1/{sc}/modules/{key} — Get module configPOST /v1/{sc}/modules — Create module configDELETE /v1/{sc}/modules/{key} — Delete module configPOST /v1/{sc}/modules/import — Import module configsPOST /v1/{sc}/modules/backup — Backup configsPOST /v1/{sc}/modules/restore — Restore from backupPOST /v1/{sc}/modules/reset — Reset to defaultsGET /v1/{sc}/modules/export — Export configsGET /v1/{sc}/modules/health — Module health checkGET /v1/{sc}/modules/backups — List backupsGET /v1/{sc}/shares — List all shares for serverGET /v1/{sc}/shares/{shareId} — Get share detailsPUT /v1/{sc}/shares/{shareId} — Update shareDELETE /v1/{sc}/shares/{shareId} — Delete sharePOST /v1/{sc}/shares/{shareId}/activate — Activate sharePOST /v1/{sc}/shares/{shareId}/deactivate — Deactivate shareGET /v1/{sc}/files/{fileId}/shares — List shares for a filePOST /v1/{sc}/files/{fileId}/shares — Create share for a fileGET /v1/{sc}/files/{fileId}/permissions — List permissionsPOST /v1/{sc}/files/{fileId}/permissions — Grant permissionDELETE /v1/{sc}/files/{fileId}/permissions — Revoke permissionGET /v1/{sc}/files/{fileId}/permissions/check — Check permissionGET|POST /v1/{sc}/files — List/create filesGET|PUT|DELETE /v1/{sc}/files/{fileId} — CRUD file by IDGET /v1/{sc}/files/{fileId}/download — Download by IDPOST /v1/{sc}/files/{fileId}/copy|move|restore — File operations by IDPOST /v1/{sc}/files/bulk/delete|copy|move — Bulk operations by IDGET|POST /v1/{sc}/directories — List/create directoriesGET|PUT|DELETE /v1/{sc}/directories/{directoryId} — CRUD directory by IDPractical integration guide for services that consume FileVault.
https://filevault.denning.online/api/v1/{server_code}Authorization: Bearer fv_<token>X-API-Token: fv_<token>X-User-Email: <identity>staffCode in this header.Do not send server_code inside path values.
Valid path formats:
{module}/{recordId}/{sub_path}/{filename}documents/{sub_path}/{filename} (no recordId)Examples:
matters/12345/evidence/contract.pdfstaff/101/avatar/photo.jpgdocuments/policies/leave-policy.pdfInvalid:
TS0004/matters/12345/contract.pdf/matters/12345/contract.pdfdocuments/12345/file.pdfThe metadata field on upload controls file visibility and ownership tracking. It is optional — if omitted, the file defaults to access_level: "server" (visible to all firm users) and uploaded_by: "system".
Metadata fields:
uploaded_by — staff code or email of uploader (default: "system")access_level — file visibility (default: "server"):
"server" — everyone in the same firm/tenant can see it"private" — only the uploader can see it"public" — anyone with the linkUpload fields:
file (required) — the file to uploadpath (required) — destination path, e.g. matters/12345/document.pdfmetadata (optional JSON string) — see Access Levels and Metadata aboveoverwrite (optional boolean, default true) — replace existing file at same pathMinimal example (most common — all firm users can see):
curl -X POST "https://filevault.denning.online/api/v1/TS0004/storage/upload" \
-H "Authorization: Bearer fv_your_token" \
-H "X-User-Email: ST0001" \
-F "file=@document.pdf" \
-F "path=matters/12345/document.pdf"
With metadata (restrict to uploader only):
curl -X POST "https://filevault.denning.online/api/v1/TS0004/storage/upload" \
-H "Authorization: Bearer fv_your_token" \
-H "X-User-Email: ST0001" \
-F "file=@document.pdf" \
-F "path=matters/12345/document.pdf" \
-F 'metadata={"uploaded_by":"ST0001","access_level":"private"}'
Note: If you skip metadata, the file defaults to access_level: "server" (visible to all firm users) and uploaded_by: "system".
curl "https://filevault.denning.online/api/v1/TS0004/storage/download?path=matters/12345/document.pdf" \
-H "Authorization: Bearer fv_your_token" \
-H "X-User-Email: ST0001" \
-o document.pdf
curl "https://filevault.denning.online/api/v1/TS0004/storage/lookup?path=matters/12345/document.pdf" \
-H "Authorization: Bearer fv_your_token" \
-H "X-User-Email: ST0001"
curl "https://filevault.denning.online/api/v1/TS0004/storage/list?directory=matters/12345&recursive=false&per_page=100" \
-H "Authorization: Bearer fv_your_token" \
-H "X-User-Email: ST0001"
Check if a file exists:
curl "https://filevault.denning.online/api/v1/TS0004/storage/exists?path=matters/12345/document.pdf" \
-H "Authorization: Bearer fv_your_token"
Check if a directory/folder exists (pass type=directory):
curl "https://filevault.denning.online/api/v1/TS0004/storage/exists?path=matters/12345&type=directory" \
-H "Authorization: Bearer fv_your_token"
Response:
{ "status": "success", "exists": true, "path": "matters/12345", "type": "directory" }
curl -X DELETE "https://filevault.denning.online/api/v1/TS0004/storage" \
-H "Authorization: Bearer fv_your_token" \
-H "Content-Type: application/json" \
-d '{"path":"matters/12345/document.pdf"}'
Use these for cross-file operations. Responses may be:
status: success (all passed)status: partial (some failed; inspect data.errors)curl -X POST "https://filevault.denning.online/api/v1/TS0004/storage/bulk-copy" \
-H "Authorization: Bearer fv_your_token" \
-H "Content-Type: application/json" \
-d '{
"paths": ["matters/12345/a.pdf", "matters/12345/b.pdf"],
"target_path": "matters/12345/archive"
}'
curl -X POST "https://filevault.denning.online/api/v1/TS0004/storage/bulk-move" \
-H "Authorization: Bearer fv_your_token" \
-H "Content-Type: application/json" \
-d '{
"paths": ["matters/12345/draft1.pdf", "matters/12345/draft2.pdf"],
"target_path": "matters/12345/final"
}'
curl -X POST "https://filevault.denning.online/api/v1/TS0004/storage/bulk-delete" \
-H "Authorization: Bearer fv_your_token" \
-H "Content-Type: application/json" \
-d '{
"paths": ["matters/12345/old1.pdf", "matters/12345/old2.pdf"]
}'
Each tenant (server_code) can store multiple provider configs and activate one.
Supported providers:
system, s3, azure, gcp, alibaba, ftp, sftp, localcurl "https://filevault.denning.online/api/v1/TS0004/storage/configs" \
-H "Authorization: Bearer fv_your_token"
curl -X POST "https://filevault.denning.online/api/v1/TS0004/storage/configs" \
-H "Authorization: Bearer fv_your_token" \
-H "Content-Type: application/json" \
-d '{
"provider": "s3",
"name": "Tenant S3",
"config": {
"bucket": "tenant-bucket",
"region": "ap-southeast-1",
"key": "AKIA...",
"secret": "..."
},
"activate": false
}'
curl -X POST "https://filevault.denning.online/api/v1/TS0004/storage/configs/s3/activate" \
-H "Authorization: Bearer fv_your_token"
Migration is async and requires queue workers.
curl -X POST "https://filevault.denning.online/api/v1/TS0004/storage/migration/start" \
-H "Authorization: Bearer fv_your_token" \
-H "Content-Type: application/json" \
-d '{
"source_provider": "system",
"destination_provider": "s3",
"activate_destination": true,
"overwrite_existing": false
}'
curl "https://filevault.denning.online/api/v1/TS0004/storage/migration/status" \
-H "Authorization: Bearer fv_your_token"
curl -X POST "https://filevault.denning.online/api/v1/TS0004/storage/migration/{migrationId}/retry" \
-H "Authorization: Bearer fv_your_token"
Recommended client strategy:
4xx validation/permission errors without fixing payload/identity.data.errors and retry only failed paths.GET /storage/migration/statusGET /storage/migration/jobsPOST /storage/migration/{migrationId}/retryIdempotency notes:
upload is replace-by-default (overwrite=true default).move and bulk-move are not safe to blind-retry unless you verify source/destination state.delete and bulk-delete should re-check with exists or list.dp-api and dcc-api should call path-based endpoints (/storage/*) only.X-User-Email using staffCode.server_code in service config (example: DCC01) and keep path values server-code-free.| Group | Method | Endpoint |
|---|---|---|
| Health | GET | /api/health |
| Global modules | GET/POST/PUT/DELETE | /api/v1/modules/* |
| Server | GET/PUT | /api/v1/{server_code} |
| Server stats | GET | /api/v1/{server_code}/stats |
| Storage (path-based) | Various | /api/v1/{server_code}/storage/* |
| Storage providers | Various | /api/v1/{server_code}/storage/configs* |
| Storage migration | Various | /api/v1/{server_code}/storage/migration/* |
| Module config (server) | Various | /api/v1/{server_code}/modules/* |
| Legacy files/directories | Various | /api/v1/{server_code}/files/*, /api/v1/{server_code}/directories/* |
Versioned API reference aligned with current routes.
/api/health/api/v1/api/v1/{server_code}{server_code} format: uppercase alphanumeric, 4-20 chars (example: TS0004, DCC01).
Required for all protected endpoints.
Authorization: Bearer fv_<64_hex>X-API-Token: fv_<64_hex>Optional caller identity (used in access checks):
X-User-Email: <caller-id> (Denning convention: staffCode)Do not include server_code inside path params/body.
Accepted path patterns:
{module}/{recordId}/{sub_path}/{filename}documents/{sub_path}/{filename}Rejected examples:
TS0004/matters/123/file.pdf/matters/123/file.pdf../../../etc/passwdSuccess:
{
"status": "success",
"message": "Operation completed",
"data": {}
}
Partial (bulk operations):
{
"status": "partial",
"message": "Copied 2 of 3 files",
"data": {
"errors": []
}
}
Error:
{
"status": "error",
"message": "Validation failed",
"errors": {}
}
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/health |
Service health |
| POST | /api/v1/servers/register |
Register server (requires X-Master-Token) |
| GET | /api/v1/public/{token} |
Public share info |
| POST | /api/v1/public/{token}/verify |
Verify share password |
| GET | /api/v1/public/{token}/download |
Download shared file |
| Method | Endpoint |
|---|---|
| GET | /api/v1/modules |
| POST | /api/v1/modules |
| GET | /api/v1/modules/grouped |
| GET | /api/v1/modules/storage-paths |
| POST | /api/v1/modules/sync-all |
| POST | /api/v1/modules/sync-all-now |
| POST | /api/v1/modules/clear-cache |
| GET | /api/v1/modules/{key} |
| PUT | /api/v1/modules/{key} |
| DELETE | /api/v1/modules/{key} |
| POST | /api/v1/modules/{key}/sync |
| POST | /api/v1/modules/{key}/sync-now |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/{server_code} |
Server details |
| PUT | /api/v1/{server_code} |
Update server profile/quota/rate limit |
| GET | /api/v1/{server_code}/stats |
Storage and file stats |
| PUT | /api/v1/{server_code}/storage-config |
Update active provider and config map |
| Method | Endpoint |
|---|---|
| POST | /api/v1/{server_code}/storage/upload |
| GET | /api/v1/{server_code}/storage/download |
| GET | /api/v1/{server_code}/storage/lookup |
| GET | /api/v1/{server_code}/storage/list |
| GET | /api/v1/{server_code}/storage/exists |
| DELETE | /api/v1/{server_code}/storage |
| POST | /api/v1/{server_code}/storage/copy |
| POST | /api/v1/{server_code}/storage/move |
| POST | /api/v1/{server_code}/storage/rename |
| POST | /api/v1/{server_code}/storage/mkdir |
| DELETE | /api/v1/{server_code}/storage/rmdir |
| POST | /api/v1/{server_code}/storage/compress |
| POST | /api/v1/{server_code}/storage/extract |
| POST | /api/v1/{server_code}/storage/share |
| POST | /api/v1/{server_code}/storage/bulk-copy |
| POST | /api/v1/{server_code}/storage/bulk-move |
| POST | /api/v1/{server_code}/storage/bulk-delete |
Multipart form fields:
file (required) — the file to uploadpath (required) — destination path, e.g. matters/6000-6280/contract.pdfmetadata (optional JSON string) — controls file visibility and tracking:
uploaded_by — staff code or email of uploader (default: "system")access_level — file visibility (default: "server"):
"server" — everyone in the same firm/tenant can see it"private" — only the uploader can see it"public" — anyone with the linkoverwrite (optional boolean, default true) — replace existing file at same pathMinimal example (most common — all firm users can see):
curl -X POST "https://filevault.denning.online/api/v1/TS0004/storage/upload" \
-H "Authorization: Bearer fv_token" \
-F "file=@contract.pdf" \
-F "path=matters/12345/evidence/contract.pdf"
With metadata (restrict to uploader only):
curl -X POST "https://filevault.denning.online/api/v1/TS0004/storage/upload" \
-H "Authorization: Bearer fv_token" \
-F "file=@contract.pdf" \
-F "path=matters/12345/evidence/contract.pdf" \
-F 'metadata={"uploaded_by":"ST0001","access_level":"private"}'
Note: If you skip metadata, the file defaults to access_level: "server" (visible to all firm users) and uploaded_by: "system".
download query: pathlookup query: pathexists query: path, type (optional: file or directory, default: file)list query:
directory (optional)recursive (optional boolean)per_page (optional, max 100)copy body:
{ "source_path": "...", "dest_path": "..." }
move body:
{ "source_path": "...", "dest_path": "..." }
rename body:
{ "path": "...", "new_name": "new-file.pdf" }
mkdir body:
{ "path": "...", "access_level": "private", "created_by": "ST0001" }
rmdir body:
{ "path": "..." }
delete body:
{ "path": "..." }
compress body:
{
"paths": ["matters/123/a.pdf", "matters/123/b.pdf"],
"archive_name": "archive.zip",
"dest_path": "matters/123/archives"
}
extract body:
{
"path": "matters/123/archives/archive.zip",
"dest_path": "matters/123/extracted"
}
share body:
{
"path": "matters/123/contract.pdf",
"expires_at": "2026-03-01T00:00:00Z",
"password": "1234",
"download_limit": 10
}
bulk-copy and bulk-move:
{
"paths": ["matters/123/a.pdf", "matters/123/b.pdf"],
"target_path": "matters/123/archive"
}
bulk-delete:
{
"paths": ["matters/123/a.pdf", "matters/123/b.pdf"]
}
Supported providers:
system, s3, azure, gcp, alibaba, ftp, sftp, local| Method | Endpoint |
|---|---|
| GET | /api/v1/{server_code}/storage/configs |
| POST | /api/v1/{server_code}/storage/configs |
| PUT | /api/v1/{server_code}/storage/configs/{provider} |
| DELETE | /api/v1/{server_code}/storage/configs/{provider} |
| POST | /api/v1/{server_code}/storage/configs/{provider}/test |
| POST | /api/v1/{server_code}/storage/configs/{provider}/activate |
Create/update payload:
{
"provider": "s3",
"config": {},
"name": "Tenant S3",
"description": "Primary tenant storage",
"visibility": "private",
"driver": "aws_s3",
"activate": false
}
| Method | Endpoint |
|---|---|
| GET | /api/v1/{server_code}/storage/migration/status |
| GET | /api/v1/{server_code}/storage/migration/jobs |
| POST | /api/v1/{server_code}/storage/migration/start |
| POST | /api/v1/{server_code}/storage/migration/{migrationId}/cancel |
| POST | /api/v1/{server_code}/storage/migration/{migrationId}/retry |
Start payload:
{
"source_provider": "system",
"destination_provider": "s3",
"delete_source_files": false,
"overwrite_existing": false,
"preserve_directory_structure": true,
"file_pattern": "*.pdf",
"activate_destination": true
}
Notes:
| Method | Endpoint |
|---|---|
| GET | /api/v1/{server_code}/modules |
| POST | /api/v1/{server_code}/modules |
| GET | /api/v1/{server_code}/modules/export |
| GET | /api/v1/{server_code}/modules/health |
| GET | /api/v1/{server_code}/modules/backups |
| GET | /api/v1/{server_code}/modules/{key} |
| DELETE | /api/v1/{server_code}/modules/{key} |
| POST | /api/v1/{server_code}/modules/import |
| POST | /api/v1/{server_code}/modules/backup |
| POST | /api/v1/{server_code}/modules/restore |
| POST | /api/v1/{server_code}/modules/reset |
Still available for older consumers:
| Group | Endpoint Pattern |
|---|---|
| Legacy files | /api/v1/{server_code}/files/* |
| Legacy directories | /api/v1/{server_code}/directories/* |
| File permissions | /api/v1/{server_code}/files/{fileId}/permissions* |
| File-specific shares | /api/v1/{server_code}/files/{fileId}/shares* |
| Share management | /api/v1/{server_code}/shares* |
For new integrations, use /storage/*.
private and server files.partial; always process data.errors.404 typically means missing file/path/config/job.409 typically means conflict (already exists, migration in progress, active config deletion).422 indicates validation errors or unsupported provider input.Setup and integration checklist for engineers working with FileVault.
composer install
cp .env.example .env
php artisan key:generate
php artisan migrate
php artisan serve
Recommended for async features (migration/jobs):
php artisan queue:work
Every consuming service must provide:
FILEVAULT_URLFILEVAULT_TOKENFILEVAULT_SERVER_CODERequest headers:
Authorization: Bearer <token>X-User-Email: <caller-identity> (use staffCode in Denning services)Use path-based API for all new work:
/api/v1/{server_code}/storage/*Legacy endpoints are still present for compatibility:
/api/v1/{server_code}/files/*/api/v1/{server_code}/directories/*Paths must be server-code-free and follow:
{module}/{recordId}/{sub_path}/{filename}documents/{sub_path}/{filename}Do not send:
path/../)The metadata field on upload is optional and controls file visibility and ownership. If omitted, files default to access_level: "server" (visible to all firm users) and uploaded_by: "system".
Metadata fields:
uploaded_by — staff code or email of uploader (default: "system")access_level — file visibility (default: "server"):
"server" — everyone in the same firm/tenant can see it"private" — only the uploader can see it"public" — anyone with the linkExample (restrict to uploader only):
{
"uploaded_by": "ST0001",
"access_level": "private"
}
Most uploads do not need metadata — just send file and path, and the file will be visible to all users in the firm.
If caller identity is missing (X-User-Email), access checks for private/server files may deny reads.
Per tenant (server_code) you can:
storage/configs)storage/configs/{provider}/test)storage/configs/{provider}/activate)Supported providers:
system, s3, azure, gcp, alibaba, ftp, sftp, localMigration endpoints:
GET /storage/migration/statusGET /storage/migration/jobsPOST /storage/migration/startPOST /storage/migration/{migrationId}/cancelPOST /storage/migration/{migrationId}/retryImportant:
Use these rules in dp-api/dcc-api/other services:
5xx errors with exponential backoff.4xx blindly; fix payload or permissions first.status=partial and retry only failed paths.move, delete), verify state with exists or list.healthy.server_code.X-User-Email is passed from caller context.server_code.partial responses.docs/API_REFERENCE.mddocs/INTEGRATION_GUIDE.mddocs/bruno-collection//integration-guide