opbox

Files & Upload API

Upload files, manage stored files, and search across all resources. File uploads support linking to submissions and matters, include server-side security validation, and persist file binaries in a configurable storage backend. The default backend stores encrypted bytes in PostgreSQL; self-hosted deployments can use an S3-compatible object store such as MinIO on the same box so files stay local while the database remains the authoritative index.

Endpoints

MethodEndpointDescription
GET/api/filesList uploaded files
POST/api/filesUpload a standalone file (files workspace)
GET/api/files/browseBrowse files with extraction status, quality scores, and entity filtering
GET/api/files/browse/structureGet smart folder structure grouped by linked entities
PUT/api/files/:fileId/starToggle starred status on a file
PUT/api/files/:fileId/moveMove a file to a folder
POST/api/files/foldersCreate a folder
PUT/api/files/foldersRename a folder
DELETE/api/files/foldersDelete a folder
POST/api/uploadUpload a file and optionally link it to a submission or matter
GET/api/searchGlobal search across forms, submissions, tables, matters, boards, documents, records, workflows, and pages

Storage Backends

File metadata (filename, MIME type, size, sensitivity, links) always lives in the Opbox database. The actual bytes can be stored in one of three backends, selected by the FILE_STORAGE_BACKEND environment variable:

BackendFILE_STORAGE_BACKENDWhere bytes liveBest for
Databasedb (default)StoredFile.content in PostgreSQL, AES-256-GCM encrypted when ENCRYPTION_KEY is setSmall deployments, simplicity, no extra service
S3-compatibles3S3-compatible object store (MinIO, Garage, Ceph, R2, etc.)Larger files, separation of blob cost from database, same-box self-hosting
DiskdiskLocal filesystem under STORAGE_DIRLegacy fallback and local dev

S3 / MinIO on the same box

For Azure, on-prem, or security-conscious deployments, run an S3-compatible store on the same machine as Opbox. All file bytes stay on infrastructure you control; the database only keeps the index.

Set these environment variables:

VariableRequiredDescription
FILE_STORAGE_BACKENDYess3
S3_ENDPOINTYesEndpoint URL, e.g. http://127.0.0.1:9000 for local MinIO or a private cable/tailnet IP
S3_REGIONYesS3 region, e.g. us-east-1
S3_BUCKETYesBucket name, e.g. opbox-files
S3_ACCESS_KEY_IDYesAccess key
S3_SECRET_ACCESS_KEYYesSecret key
S3_FORCE_PATH_STYLENotrue (default) for MinIO; false for virtual-hosted-style clouds
S3_USE_TLSNotrue (default) to require TLS
S3_PRESIGNED_URL_EXPIRY_SECONDSNoPresigned URL lifetime in seconds (default 300)
S3_SSE_CUSTOMER_KEYNoOptional per-object server-side encryption key
ENCRYPTION_KEYRecommendedEncrypts bytes before they leave the app, even if the object store is compromised

Matter-scoped S3 prefixes

When a file is uploaded for a matter, the S3 key is scoped under the matter number so the object store mirrors the workspace filing structure:

matters/{matterNumber}/{folder}/{timestamp}_{random}_{filename}

For example:

matters/MAT-0001/1. Engagement/Letters/mqknd7k8_letter.pdf

Use the matterId and folder fields when uploading through POST /api/upload to opt into this layout. Files uploaded without a matter still use the default flat key format.

List Files

Returns all uploaded files for your workspace.

GET /api/files?search=passport&type=documents

Query Parameters

ParameterTypeDescription
searchstringFilter by filename substring
typestringType filter: images, documents, spreadsheets, archives

Response

{
  "files": [
    {
      "id": "cm_attachment_abc123",
      "filename": "passport-scan.pdf",
      "fileUrl": "/api/documents/5f9d6c13e2_mi2f6b_4sB...pdf",
      "fileSize": 245760,
      "mimeType": "application/pdf",
      "createdAt": "2026-02-13T09:30:00.000Z",
      "submission": {
        "id": "SUB-8XQ2M7K9P1ZT",
        "formTitle": "Client Onboarding",
        "user": {
          "id": "cm_user_123",
          "name": "Jane Smith"
        }
      }
    }
  ],
  "stats": {
    "totalFiles": 56,
    "totalSize": 9830400,
    "fileTypes": {
      "pdf": 20,
      "png": 11
    }
  }
}

Upload via Files Workspace

Uploads a standalone file and links it to the system File Uploads submission.

POST /api/files
Content-Type: multipart/form-data

------boundary
Content-Disposition: form-data; name="file"; filename="company-logo.png"
Content-Type: image/png

<binary data>
------boundary--

Response

{
  "success": true,
  "file": {
    "id": "cm_attachment_def456",
    "filename": "company-logo.png",
    "fileUrl": "/api/documents/5f9d6c13e2_mi2f70_EJd...png",
    "fileSize": 82944,
    "mimeType": "image/png"
  }
}

Browse Files

Returns files for your workspace with extraction status, quality scores, and entity link information. Supports filtering by entity, folder, and special views.

GET /api/files/browse?search=passport&entityId=cm_row_rec1&entityType=ROW

Query Parameters

ParameterTypeDescription
searchstringFilter by filename substring
typestringType filter: images, documents, spreadsheets, archives
entityIdstringFilter to files linked to a specific entity (e.g. a record row ID)
entityTypestringEntity type filter, used with entityId: ROW, MATTER, SUBMISSION
linkTableIdstringFilter to files linked to rows in a specific table
recentbooleanSet to true to show recently uploaded files
starredbooleanSet to true to show only starred files
unlinkedbooleanSet to true to show only files not linked to any entity

Response

Each file includes optional extraction and quality fields. The extraction object shows the document extraction pipeline status (PENDING, PROCESSING, COMPLETED, FAILED, or TIMED_OUT) and completion timestamp. The quality object includes a structureScore (0-1) and a passed boolean. Both fields are null when not yet available.

{
  "files": [
    {
      "id": "cm_file_abc123",
      "filename": "passport-scan.pdf",
      "fileUrl": "/api/documents/5f9d6c13e2_mi2f6b_4sB...pdf",
      "fileSize": 245760,
      "mimeType": "application/pdf",
      "starred": false,
      "folderId": null,
      "createdAt": "2026-02-13T09:30:00.000Z",
      "extraction": {
        "status": "COMPLETED",
        "completedAt": "2026-02-13T09:31:12.000Z"
      },
      "quality": {
        "structureScore": 0.92,
        "passed": true
      },
      "links": [
        {
          "entityId": "cm_row_rec1",
          "entityType": "ROW",
          "tableId": "cm_table_456"
        }
      ]
    }
  ],
  "total": 56
}

Browse Folder Structure

Returns a smart folder structure for the file browser sidebar. Files are grouped into sections by linked entity type (e.g. Individuals, Matters), with counts per entity. Also returns totals for the Recent, Starred, and Unlinked virtual folders.

GET /api/files/browse/structure

Response

{
  "sections": [
    {
      "key": "clients",
      "label": "Individuals",
      "icon": "Users",
      "totalFiles": 12,
      "entities": [
        {
          "id": "cm_row_rec1",
          "name": "Acme Corp",
          "count": 4,
          "tableId": "cm_table_456"
        }
      ]
    }
  ],
  "recent": 8,
  "starred": 3,
  "unlinked": 5
}

Toggle Star

Toggles the starred status of a file. Starred files appear in the Starred virtual folder in the file browser.

PUT /api/files/:fileId/star

Response

{
  "success": true,
  "starred": true
}

Create Folder

Creates a new folder for organising files.

POST /api/files/folders
Content-Type: application/json
{
  "name": "Client Documents"
}

Body Parameters

FieldTypeRequiredDescription
namestringYesThe folder name

Response

{
  "id": "cm_folder_abc123",
  "name": "Client Documents",
  "organizationId": "cm_org_123",
  "createdAt": "2026-03-01T10:00:00.000Z"
}

Rename Folder

Renames an existing folder.

PUT /api/files/folders
Content-Type: application/json
{
  "id": "cm_folder_abc123",
  "name": "Renamed Folder"
}

Body Parameters

FieldTypeRequiredDescription
idstringYesThe folder ID
namestringYesThe new folder name

Delete Folder

Deletes a folder. Files inside the folder are moved to the root level (not deleted).

DELETE /api/files/folders
Content-Type: application/json
{
  "id": "cm_folder_abc123"
}

Body Parameters

FieldTypeRequiredDescription
idstringYesThe folder ID to delete

Move File to Folder

Moves a file into a folder, or back to the root level by passing null as the folder ID.

PUT /api/files/:fileId/move
Content-Type: application/json
{
  "folderId": "cm_folder_abc123"
}

Body Parameters

FieldTypeRequiredDescription
folderIdstring | nullYesTarget folder ID, or null to move to root

Response

{
  "success": true,
  "file": {
    "id": "cm_file_abc123",
    "folderId": "cm_folder_abc123"
  }
}

Upload a File

Uploads a file using multipart/form-data encoding. Files can optionally be linked to a submission by passing submissionId, or scoped to a matter by passing matterId and an optional folder path.

POST /api/upload
Content-Type: multipart/form-data

------boundary
Content-Disposition: form-data; name="file"; filename="passport-scan.pdf"
Content-Type: application/pdf

<binary data>
------boundary
Content-Disposition: form-data; name="submissionId"

SUB-8XQ2M7K9P1ZT
------boundary
Content-Disposition: form-data; name="matterId"

cm_matter_789
------boundary
Content-Disposition: form-data; name="folder"

1. Engagement/Letters
------boundary--

Body Parameters

FieldTypeRequiredDescription
fileFileYesThe file to upload
submissionIdstringNoLinks the uploaded file to a specific submission
matterIdstringNoWhen set, the file is linked to the matter and the S3 key is scoped under matters/{matterNumber}/{folder}/
folderstringNoMatter folder path, used when matterId is set. Forward-slash separated, e.g. 1. Engagement/Letters

Response

{
  "success": true,
  "file": {
    "key": "5f9d6c13e2_mi2f6b_4sB...pdf",
    "filename": "passport-scan.pdf",
    "size": 245760,
    "mimeType": "application/pdf",
    "url": "/api/documents/5f9d6c13e2_mi2f6b_4sB...pdf",
    "attachmentId": "cm_attachment_abc123"
  }
}

Upload Security

  • Max file size: 10MB by default (configurable by the workspace administrator).
  • MIME type validation: Dangerous executable/script MIME types are blocked (for example PE binaries, shell scripts, HTML, and JavaScript MIME types).
  • Content inspection: Server-side validation ensures that the file’s actual content matches its declared MIME type. Files masquerading as images (e.g. SVG with image/png header) are rejected.
  • Content-Disposition: Markup-like MIME types such as image/svg+xml are forced to download via Content-Disposition: attachment.
  • Security token: File uploads require authentication.
  • Encryption at rest: When ENCRYPTION_KEY is set, files are encrypted with AES-256-GCM before storage. For the S3 backend, an optional S3_SSE_CUSTOMER_KEY adds server-side encryption on the object store.
  • Same-box S3: The S3-compatible backend is designed to run on the same host as Opbox (e.g. MinIO on localhost or a private tailnet/cable IP), so file bytes never traverse the public internet. Bind the object store to a private interface, require TLS with S3_USE_TLS=true, and rotate credentials regularly.

Blocked MIME Types

MIME TypeReason
application/x-msdownloadNative executable binary
application/x-shShell script execution risk
text/htmlHTML can execute scripts when rendered inline
application/javascriptScript execution risk

Searches across all resource types in your workspace. Results are grouped by type: forms, submissions, tables, matters, boards, documents, records, workflows, and navigation pages.

GET /api/search?q=onboarding

Query Parameters

ParameterTypeDescription
qstringSearch query string (required). Searches titles, names, descriptions, tags, and data content.
entitiesstringComma-separated list of entity types to search. Defaults to all types. Values: forms, submissions, tables, workflows, templates, boards, matters, documents, records, pages
limitnumberMaximum results to return (default: 20)
sortBystringSort order: relevance (default) or date
sortOrderstringSort direction: desc (default) or asc

Response

{
  "results": {
    "forms": [
      {
        "id": "cm_form_123",
        "title": "Client Onboarding",
        "description": "Collect client details for onboarding",
        "status": "PUBLISHED"
      }
    ],
    "submissions": [
      {
        "id": "SUB-8XQ2M7K9P1ZT",
        "formTitle": "Client Onboarding",
        "status": "PENDING",
        "createdAt": "2026-02-12T09:15:00.000Z"
      }
    ],
    "tables": [
      {
        "id": "cm_table_456",
        "name": "Individuals",
        "category": "SYSTEM"
      }
    ],
    "matters": [
      {
        "id": "cm_matter_789",
        "number": "MAT-0012",
        "title": "Acme Corp Onboarding",
        "status": "IN_PROGRESS",
        "priority": "HIGH",
        "boardName": "Client Onboarding"
      }
    ],
    "boards": [
      {
        "id": "cm_template_abc",
        "title": "Client Onboarding",
        "status": "ACTIVE"
      }
    ],
    "documents": [
      {
        "id": "cm_row_doc1",
        "title": "Onboarding Checklist",
        "tableName": "Documents"
      }
    ],
    "records": [
      {
        "id": "cm_row_rec1",
        "title": "Acme Corp",
        "tableName": "Companies"
      }
    ],
    "pages": [
      {
        "id": "page-matters",
        "title": "Matters",
        "url": "/matters"
      }
    ],
    "workflows": []
  },
  "query": "onboarding",
  "totalResults": 8,
  "durationMs": 12
}

Search Result Groups

GroupSearches
formsForm title and description
submissionsSubmission ID, form title, submitter name/email, and data content
tablesTable name and description
mattersMatter number, title, tags, and board name
boardsBoard (matter template) name and description
documentsDocument title from the system Documents table
recordsCRM and user table rows - searches all field data (Individuals, Companies, etc.)
workflowsWorkflow name and description
pagesNavigation pages and settings (e.g. “dark mode” matches Appearance)

Entity File Intelligence

Deep file aggregation endpoints for CSP entities and individuals. These endpoints traverse company rows, linked matters, and stakeholder relationships to provide a complete file picture for a given entity.

Entity Files (Company)

Deep file aggregation for a CSP entity (company). Returns files from direct company row links, all linked matters, and all stakeholders with roles in the entity.

GET /api/csp/entities/:entityId/files

Path Parameters

ParameterTypeDescription
entityIdstringThe row ID of the company entity in the Companies system table

Response

{
  "entityId": "cm_row_ent1",
  "entityName": "Acme Holdings Ltd",
  "direct": [
    {
      "id": "cm_file_abc123",
      "filename": "certificate-of-incorporation.pdf",
      "fileSize": 245760,
      "mimeType": "application/pdf",
      "createdAt": "2026-01-15T10:00:00.000Z"
    }
  ],
  "fromMatters": [
    {
      "matterId": "cm_matter_789",
      "matterTitle": "Acme Holdings - Annual Return 2026",
      "files": [
        {
          "id": "cm_file_def456",
          "filename": "annual-return-draft.pdf",
          "fileSize": 184320,
          "mimeType": "application/pdf",
          "createdAt": "2026-02-20T14:30:00.000Z"
        }
      ]
    }
  ],
  "fromStakeholders": [
    {
      "individualId": "cm_row_ind1",
      "name": "John Smith",
      "files": [
        {
          "id": "cm_file_ghi789",
          "filename": "passport-scan.pdf",
          "fileSize": 102400,
          "mimeType": "application/pdf",
          "createdAt": "2026-01-10T09:00:00.000Z"
        }
      ]
    }
  ],
  "totalFiles": 3
}

Individual Files

Deep file aggregation for a CSP individual. Returns files from direct stakeholder row links, linked matters, and bridged KYC documents.

GET /api/csp/individuals/:individualId/files

Path Parameters

ParameterTypeDescription
individualIdstringThe row ID of the individual in the Stakeholders system table

Response

{
  "individualId": "cm_row_ind1",
  "individualName": "John Smith",
  "direct": [
    {
      "id": "cm_file_ghi789",
      "filename": "passport-scan.pdf",
      "fileSize": 102400,
      "mimeType": "application/pdf",
      "createdAt": "2026-01-10T09:00:00.000Z"
    }
  ],
  "fromMatters": [
    {
      "matterId": "cm_matter_789",
      "matterTitle": "Acme Holdings - Annual Return 2026",
      "files": [
        {
          "id": "cm_file_jkl012",
          "filename": "director-resolution.pdf",
          "fileSize": 76800,
          "mimeType": "application/pdf",
          "createdAt": "2026-02-22T11:00:00.000Z"
        }
      ]
    }
  ],
  "totalFiles": 2
}

Stakeholder File Timeline

Chronological view of every document associated with a stakeholder across all entities and matters.

GET /api/stakeholders/:id/files

Path Parameters

ParameterTypeDescription
idstringThe stakeholder row ID

Query Parameters

ParameterTypeDescription
limitnumberMaximum entries to return (default: 100, max: 500)

Response

{
  "stakeholderId": "cm_row_ind1",
  "entries": [
    {
      "file": {
        "id": "cm_file_ghi789",
        "filename": "passport-scan.pdf",
        "fileSize": 102400,
        "mimeType": "application/pdf"
      },
      "source": "ROW",
      "sourceLabel": "Stakeholders - John Smith",
      "date": "2026-01-10T09:00:00.000Z"
    },
    {
      "file": {
        "id": "cm_file_jkl012",
        "filename": "director-resolution.pdf",
        "fileSize": 76800,
        "mimeType": "application/pdf"
      },
      "source": "MATTER",
      "sourceLabel": "Acme Holdings - Annual Return 2026",
      "date": "2026-02-22T11:00:00.000Z"
    }
  ],
  "totalEntries": 2
}