post/filesexperimentalSession required
Uploads a file for a category and stores it against the workshop identified by scopeId. The response is the stored file's own record; this endpoint never assigns the file to anything else, a later request (for example a grid button patch) references it by id. Every upload also counts against the workshop's plan storage quota (10 MB on Free, 250 MB on paid plans) and the request itself is capped at 2 MB regardless of category, on top of the category's own per-file cap. 413 covers all three limits with an empty body; nothing in the response distinguishes which one was hit. The file's actual type is detected from its bytes, not the client-supplied MIME type.
Responses
201Created
| Field | Type | Required | Constraints | Description |
|---|
| id | string | Yes | - | - |
| scope | FileScope | Yes | - | The kind of resource a file is authorized against: workshop or estimate. Both categories accepted by POST /files resolve to workshop; estimate-scoped files are created by other endpoints, not this one. |
| scopeId | string | Yes | - | - |
| category | FileCategory | Yes | - | What a file is used for. Decides the accepted content types and any per-file size cap: grid_tile_icon (an image attached to a grid button, up to 512 KB) or estimate_pdf (a generated estimate PDF, no per-file cap). |
| name | string | No | - | - |
| contentType | string | Yes | - | - |
| sizeBytes | integer | Yes | minimum: -9007199254740991maximum: 9007199254740991 | - |
| uploadedByUid | string | Yes | - | - |
| createdAt | string | Yes | format: "date-time" | - |
| estimateId | string | No | - | Set only for files created against an estimate scope. |
Example
{
"id": "file-1",
"scope": "workshop",
"scopeId": "workshop-1",
"category": "grid_tile_icon",
"name": "chisel-icon.png",
"contentType": "image/png",
"sizeBytes": 48213,
"uploadedByUid": "uid-1",
"createdAt": "2026-08-05T10:15:00.000Z"
}400category or scopeId is missing or invalid, or no file was attached
No body.
401Not authenticated
No body.
403The caller is not a member of the workshop identified by scopeId
No body.
413The file exceeds the 2 MB request cap, the category's own size limit, or the workshop's storage quota
No body.
415The file's real content type is not one of the category's accepted types
No body.
Example request
curl -X POST 'https://mercastra.cloud/api/files' \
-H 'Cookie: session=<session cookie>' \
-H 'X-CSRF-Token: <csrf-token cookie value>'