Files
A file belongs to a version. Files are either uploaded blobs (stored under the add-on's Flysystem mount) or external links (probed via the DirectUrlSource adapter). Routes mount at /api/mc-dm-files/.
List files
GET /api/mc-dm-files/
Scope: mc_dm_download:read.
Parent filter: version_id is required for non-super-user keys.
| Param | Type | Notes |
|---|---|---|
version_id | uint | Required for user keys. Optional for super-user keys. |
Soft-deleted files (is_deleted=1) are always excluded.
Order whitelist: display_order, file_size, download_count. Default display_order desc. There is no since support. Files do not carry a primary date column.
Example
curl -H "XF-Api-Key: $KEY" \
"https://example.com/api/mc-dm-files/?version_id=88&order=display_order&direction=asc"
Response
{
"files": [
{
"file_id": 200,
"version_id": 88,
"filename": "acme-importer-1.4.0.zip",
"display_label": "Windows build",
"file_size": 4194304,
"file_hash": "sha256:c0ffee...",
"mime_type": "application/zip",
"is_primary": true,
"is_externally_linked": false,
"external_url": null,
"validation_status": "passed",
"display_order": 1,
"download_count": 31,
"display_name": "acme-importer-1.4.0.zip",
"is_external": false,
"is_link_only": false
}
],
"pagination": { "page": 1, "per_page": 20, "total": 2, "last_page": 1 }
}
Get a single file
GET /api/mc-dm-files/<file_id>/
Scope: mc_dm_download:read.
Permission: the visitor must pass canView on the parent download.
Response: { "file": { ... } }.
Upload or attach a file
POST /api/mc-dm-files/
Scope: mc_dm_download:write.
Permission: canManageFiles on the parent download.
This endpoint accepts either a multipart blob upload or an external_url, never both. Sending both fails HTTP 422 with code mc_dm_api_error.upload_exclusive. Sending neither fails with mc_dm_api_error.upload_required.
| Field | Type | Notes |
|---|---|---|
version_id | uint | Required. |
file | multipart file | Either this or external_url. |
external_url | string | Either this or file. The URL is probed via DirectUrlSource. |
link_only | bool | Only meaningful with external_url. 1 = store as link only (no remote fetch). 0 (default) = probe and ingest. |
display_label | string | Optional human label (e.g. Windows build). |
is_primary | bool | If 1, demotes existing primary siblings on save. |
display_order | uint | Optional sort key. |
Blob upload example
curl -X POST -H "XF-Api-Key: $KEY" \
-F "version_id=88" \
-F "file=@dist/acme-importer-1.5.0.zip" \
-F "display_label=Windows build" \
-F "is_primary=1" \
https://example.com/api/mc-dm-files/
Files are streamed to the add-on's Flysystem mount and processed by the registered Processor adapters: hash, size, mime, magic-byte, decompression-bomb. If a processor rejects the file, the upload returns HTTP 422 with code mc_dm_api_error.processor_rejected. The file row is left at validation_status='failed' for moderator review.
PHP-level upload errors (e.g. upload_max_filesize exceeded) return HTTP 422 with mc_dm_api_error.upload_exceeds_php_limit.
External URL example
curl -X POST -H "XF-Api-Key: $KEY" \
-d "version_id=88" \
-d "external_url=https://github.com/acme/acme/releases/download/v1.5.0/dist.zip" \
-d "display_label=GitHub release" \
https://example.com/api/mc-dm-files/
The response shape for both modes is { "file": { ... } }.
Update a file
PUT /api/mc-dm-files/<file_id>/
Scope: mc_dm_download:write.
Permission: canManageFiles on the parent download.
Only metadata is editable. To replace the blob or change external_url, soft-delete the file and POST a new one.
| Field | Type |
|---|---|
display_label | string |
is_primary | bool |
display_order | uint |
Setting is_primary=true automatically demotes the previous primary on the same version.
curl -X PUT -H "XF-Api-Key: $KEY" \
-d "display_label=Windows installer" \
-d "is_primary=1" \
https://example.com/api/mc-dm-files/200/
Response: { "file": { ... } }.
Delete a file
DELETE /api/mc-dm-files/<file_id>/
Scope: mc_dm_download:write always. mc_dm_download:delete_hard additionally when hard=1.
Permission: canManageFiles. Hard delete additionally requires canHardDelete on the parent download.
| Param | Effect |
|---|---|
hard | 1 = hard delete the row and remove the blob from storage (skipped for externally-linked files). Default 0 = soft delete (sets is_deleted=1, blob retained). |
When the deleted file was the primary, the next file by display_order is auto-promoted on hard delete only.
curl -X DELETE -H "XF-Api-Key: $KEY" \
https://example.com/api/mc-dm-files/200/
Response: { "success": true }.
Verbose mode (?with=full)
Adds:
| Field | Type | Notes |
|---|---|---|
external_source_type | string | Adapter id that handled the file (e.g. direct-url). |
is_available | bool | Storage exists or external URL still reachable. |
validation_attempts | int | |
validation_error | string | null | |
last_attempt_date | int | Unix timestamp. |