a9cd7750f4
CI / unit-test (push) Has been cancelled
CI / detect-changes (push) Has been cancelled
CI / build (push) Has been cancelled
Publish docs via GitHub Pages / Deploy docs (push) Has been cancelled
CI / test-harness (push) Has been cancelled
CI / generate-e2e-matrix (push) Has been cancelled
CI / e2e (push) Has been cancelled
CI / build-ui (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
UI v2 Integration CI / E2E (Integration) (push) Has been cancelled
UI v2 CI / Lint, Format & Test (push) Has been cancelled
UI v2 CI / E2E (Mocked) (push) Has been cancelled
257 lines
7.6 KiB
Markdown
257 lines
7.6 KiB
Markdown
---
|
|
description: "Conductor File API — create, upload, download, and inspect file payloads. Includes single-shot and multipart presigned URL flows."
|
|
---
|
|
|
|
# File API
|
|
|
|
The File API manages binary file payloads associated with workflow executions. All endpoints use the base path `/api/files` and are gated by `conductor.file-storage.enabled=true` — when the feature is disabled, every endpoint returns `404`. See [File Storage](../advanced/file-storage.md) for backend setup.
|
|
|
|
Path variables carry the bare `fileId` (a UUID assigned at creation). Request and response bodies carry the prefixed handle as `fileHandleId` (`conductor://file/<fileId>`); pass either form back in to subsequent calls — the server normalizes them.
|
|
|
|
Download access is *workflow-family scoped*: callers must supply a `workflowId` that belongs to the same workflow family (self, ancestors, or descendants) as the file's owning workflow. Cross-family access returns `403 Forbidden`.
|
|
|
|
## Create a File
|
|
|
|
```
|
|
POST /api/files
|
|
```
|
|
|
|
Reserves a `fileId`, persists the metadata record, and returns a presigned upload URL. The file is created in `UPLOADING` status; the client must confirm completion via [Confirm Upload](#confirm-upload) once the bytes are uploaded.
|
|
|
|
**Request body:**
|
|
|
|
| Field | Description | Required |
|
|
|---|---|---|
|
|
| `workflowId` | Workflow execution that owns this file. Used for download-access scoping. | Yes |
|
|
| `fileName` | Original file name. | No |
|
|
| `contentType` | MIME type. | No |
|
|
| `taskId` | Task that produced the file, if applicable. | No |
|
|
|
|
```shell
|
|
curl -X POST 'http://localhost:8080/api/files' \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{
|
|
"workflowId": "3a5b8c2d-1234-5678-9abc-def012345678",
|
|
"fileName": "input.mp4",
|
|
"contentType": "video/mp4"
|
|
}'
|
|
```
|
|
|
|
**Response** `201 Created`
|
|
|
|
```json
|
|
{
|
|
"fileHandleId": "conductor://file/a1b2c3d4-5678-90ab-cdef-111111111111",
|
|
"fileName": "input.mp4",
|
|
"contentType": "video/mp4",
|
|
"storageType": "S3",
|
|
"uploadStatus": "UPLOADING",
|
|
"uploadUrl": "https://bucket.s3.amazonaws.com/conductor/3a5b8c2d.../a1b2c3d4...?X-Amz-Signature=...",
|
|
"uploadUrlExpiresAt": 1700000060000,
|
|
"createdAt": 1700000000000
|
|
}
|
|
```
|
|
|
|
The client `PUT`s file bytes directly to `uploadUrl`, then calls [Confirm Upload](#confirm-upload).
|
|
|
|
---
|
|
|
|
## Get a Fresh Upload URL
|
|
|
|
```
|
|
GET /api/files/{fileId}/upload-url
|
|
```
|
|
|
|
Issues a new presigned upload URL — used to retry an upload after the original URL expired.
|
|
|
|
```shell
|
|
curl 'http://localhost:8080/api/files/a1b2c3d4-5678-90ab-cdef-111111111111/upload-url'
|
|
```
|
|
|
|
**Response** `200 OK`
|
|
|
|
```json
|
|
{
|
|
"fileHandleId": "conductor://file/a1b2c3d4-5678-90ab-cdef-111111111111",
|
|
"uploadUrl": "https://bucket.s3.amazonaws.com/conductor/3a5b8c2d.../a1b2c3d4...?X-Amz-Signature=...",
|
|
"expiresAt": 1700000060000
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Confirm Upload
|
|
|
|
```
|
|
POST /api/files/{fileId}/upload-complete
|
|
```
|
|
|
|
Marks the upload as complete. The server probes the storage backend to verify the object exists, then transitions the record to `UPLOADED` and records the backend-reported content hash and size.
|
|
|
|
```shell
|
|
curl -X POST 'http://localhost:8080/api/files/a1b2c3d4-5678-90ab-cdef-111111111111/upload-complete'
|
|
```
|
|
|
|
**Response** `200 OK`
|
|
|
|
```json
|
|
{
|
|
"fileHandleId": "conductor://file/a1b2c3d4-5678-90ab-cdef-111111111111",
|
|
"uploadStatus": "UPLOADED",
|
|
"contentHash": "d41d8cd98f00b204e9800998ecf8427e"
|
|
}
|
|
```
|
|
|
|
`409 Conflict` if the file is already in `UPLOADED` status; `500 Internal Server Error` if the backend reports the object is missing.
|
|
|
|
---
|
|
|
|
## Get a Download URL
|
|
|
|
```
|
|
GET /api/files/{workflowId}/{fileId}/download-url
|
|
```
|
|
|
|
Issues a presigned download URL. The caller's `workflowId` must be in the same workflow family as the file's owning workflow.
|
|
|
|
| Parameter | Description |
|
|
|---|---|
|
|
| `workflowId` | The caller's workflow ID, used for family-scope check. |
|
|
| `fileId` | The file to download. |
|
|
|
|
```shell
|
|
curl 'http://localhost:8080/api/files/3a5b8c2d-1234-5678-9abc-def012345678/a1b2c3d4-5678-90ab-cdef-111111111111/download-url'
|
|
```
|
|
|
|
**Response** `200 OK`
|
|
|
|
```json
|
|
{
|
|
"fileHandleId": "conductor://file/a1b2c3d4-5678-90ab-cdef-111111111111",
|
|
"downloadUrl": "https://bucket.s3.amazonaws.com/conductor/3a5b8c2d.../a1b2c3d4...?X-Amz-Signature=...",
|
|
"expiresAt": 1700000060000
|
|
}
|
|
```
|
|
|
|
`403 Forbidden` if the caller's workflow is not in the file's family. `400 Bad Request` if the file is not yet `UPLOADED`.
|
|
|
|
---
|
|
|
|
## Get File Metadata
|
|
|
|
```
|
|
GET /api/files/{fileId}
|
|
```
|
|
|
|
Returns the file metadata record. Does not expose the server-internal storage path.
|
|
|
|
```shell
|
|
curl 'http://localhost:8080/api/files/a1b2c3d4-5678-90ab-cdef-111111111111'
|
|
```
|
|
|
|
**Response** `200 OK`
|
|
|
|
```json
|
|
{
|
|
"fileHandleId": "conductor://file/a1b2c3d4-5678-90ab-cdef-111111111111",
|
|
"fileName": "input.mp4",
|
|
"contentType": "video/mp4",
|
|
"contentHash": "d41d8cd98f00b204e9800998ecf8427e",
|
|
"storageType": "S3",
|
|
"uploadStatus": "UPLOADED",
|
|
"workflowId": "3a5b8c2d-1234-5678-9abc-def012345678",
|
|
"taskId": "task-uuid-1",
|
|
"createdAt": 1700000000000,
|
|
"updatedAt": 1700000005000
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Multipart Upload
|
|
|
|
Multipart is opt-in (typically for files above ~100 MB) and supported on `s3`, `azure-blob`, and `gcs` backends. The `local` backend does not support multipart.
|
|
|
|
### Initiate Multipart
|
|
|
|
```
|
|
POST /api/files/{fileId}/multipart
|
|
```
|
|
|
|
Begins a multipart upload session. Returns a backend-specific `uploadId`. For GCS/Azure, `uploadUrl` is the resumable session URL clients upload parts to directly; for S3 it is `null` and clients fetch a per-part URL via [Get Part Upload URL](#get-part-upload-url).
|
|
|
|
```shell
|
|
curl -X POST 'http://localhost:8080/api/files/a1b2c3d4-5678-90ab-cdef-111111111111/multipart'
|
|
```
|
|
|
|
**Response** `200 OK`
|
|
|
|
```json
|
|
{
|
|
"fileHandleId": "conductor://file/a1b2c3d4-5678-90ab-cdef-111111111111",
|
|
"uploadId": "S3-multipart-upload-id-string",
|
|
"uploadUrl": null
|
|
}
|
|
```
|
|
|
|
### Get Part Upload URL
|
|
|
|
```
|
|
GET /api/files/{fileId}/multipart/{uploadId}/part/{partNumber}
|
|
```
|
|
|
|
S3 only. Returns a presigned URL for uploading a single part. Part numbers start at `1`.
|
|
|
|
```shell
|
|
curl 'http://localhost:8080/api/files/a1b2c3d4-5678-90ab-cdef-111111111111/multipart/S3-multipart-upload-id-string/part/1'
|
|
```
|
|
|
|
**Response** `200 OK`
|
|
|
|
```json
|
|
{
|
|
"fileHandleId": "conductor://file/a1b2c3d4-5678-90ab-cdef-111111111111",
|
|
"uploadUrl": "https://bucket.s3.amazonaws.com/conductor/.../?partNumber=1&uploadId=...&X-Amz-Signature=...",
|
|
"expiresAt": 1700000060000
|
|
}
|
|
```
|
|
|
|
### Complete Multipart
|
|
|
|
```
|
|
POST /api/files/{fileId}/multipart/{uploadId}/complete
|
|
```
|
|
|
|
Finalizes the multipart upload and transitions the file to `UPLOADED`. The request body carries the ordered list of part ETags (or backend equivalents) from the part uploads.
|
|
|
|
```shell
|
|
curl -X POST 'http://localhost:8080/api/files/a1b2c3d4-5678-90ab-cdef-111111111111/multipart/S3-multipart-upload-id-string/complete' \
|
|
-H 'Content-Type: application/json' \
|
|
-d '{
|
|
"partETags": ["\"etag-of-part-1\"", "\"etag-of-part-2\"", "\"etag-of-part-3\""]
|
|
}'
|
|
```
|
|
|
|
**Response** `200 OK`
|
|
|
|
```json
|
|
{
|
|
"fileHandleId": "conductor://file/a1b2c3d4-5678-90ab-cdef-111111111111",
|
|
"uploadStatus": "UPLOADED",
|
|
"contentHash": "d41d8cd98f00b204e9800998ecf8427e"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Errors
|
|
|
|
| Status | Cause |
|
|
|---|---|
|
|
| `400 Bad Request` | Missing `workflowId` on create; download requested before file is `UPLOADED`. |
|
|
| `403 Forbidden` | Caller's `workflowId` is not in the file's workflow family. |
|
|
| `404 Not Found` | Unknown `fileId`, or `conductor.file-storage.enabled=false`. |
|
|
| `409 Conflict` | Confirm-upload called on a file already in `UPLOADED` status. |
|
|
| `413 Payload Too Large` | `FileStorageException` raised by a backend (e.g., upstream size enforcement). |
|
|
| `500 Internal Server Error` | Backend reports object missing on confirm/complete; other transient/non-transient backend errors. |
|