Download OpenAPI specification:
Logistics operations API for the 4klyft platform.
This API provides comprehensive endpoints for managing:
All API endpoints (except /api/docs and /api/v1/iam/auth/*) require JWT Bearer token authentication.
Include the token in the Authorization header:
Authorization: Bearer <your-jwt-token>
API requests are rate-limited to ensure fair usage. Standard limits are:
Creates a resumable upload session. Returns the session id; clients then PATCH /storage/uploads/{id} with Upload-Offset headers to deliver chunks, and POST /storage/uploads/{id}/finalize to assemble.
The new ChunkedUpload resource
| filename required | string Default: "" |
| mimeType required | string Default: "" |
| sizeBytes | integer >= 0 Default: 0 |
| driveId required | string Default: "" |
| category | string Default: "uploads" |
{- "filename": "",
- "mimeType": "",
- "sizeBytes": 0,
- "driveId": "",
- "category": "uploads"
}{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "id": "",
- "driveId": "",
- "filename": "",
- "mimeType": "",
- "sizeBytes": 0,
- "offset": 0,
- "chunkCount": 0,
- "status": "",
- "category": "",
- "storageUri": "string",
- "createdAt": "2019-08-24T14:15:22Z",
- "expiresAt": "2019-08-24T14:15:22Z"
}Returns current offset, total size, status, and other session metadata. Used by clients to resume after a disconnect.
| id required | string Upload session ULID |
{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "id": "",
- "driveId": "",
- "filename": "",
- "mimeType": "",
- "sizeBytes": 0,
- "offset": 0,
- "chunkCount": 0,
- "status": "",
- "category": "",
- "storageUri": "string",
- "createdAt": "2019-08-24T14:15:22Z",
- "expiresAt": "2019-08-24T14:15:22Z"
}Mark the session abandoned and clean up any transient chunk blobs.
| id required | string Upload session ULID |
{- "type": "/errors/validation-error",
- "title": "Validation Error",
- "status": 400,
- "detail": "The provided input is invalid",
- "violations": [
- {
- "propertyPath": "code",
- "message": "This value should not be blank."
}
], - "instance": "string"
}Append raw octet-stream bytes at the offset declared in the Upload-Offset header. Must match the server-side offset exactly; mismatch returns HTTP 409 with the expected offset.
| id required | string Upload session ULID |
| Upload-Offset required | integer Byte offset where this chunk starts |
| Upload-Checksum | string Optional sha256: |
| Content-Length required | integer Chunk size in bytes |
Raw chunk bytes
{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "id": "",
- "driveId": "",
- "filename": "",
- "mimeType": "",
- "sizeBytes": 0,
- "offset": 0,
- "chunkCount": 0,
- "status": "",
- "category": "",
- "storageUri": "string",
- "createdAt": "2019-08-24T14:15:22Z",
- "expiresAt": "2019-08-24T14:15:22Z"
}Concatenate all received chunks into the final stored file and emit FileUploaded. Idempotent on retry as long as the session has not been abandoned.
| id required | string Upload session ULID |
The new ChunkedUpload resource
| id | string Default: "" |
| driveId | string Default: "" |
| filename | string Default: "" |
| mimeType | string Default: "" |
| sizeBytes | integer Default: 0 |
| offset | integer Default: 0 |
| chunkCount | integer Default: 0 |
| status | string Default: "" |
| category | string Default: "" |
| storageUri | string or null |
| createdAt | string or null <date-time> |
| expiresAt | string or null <date-time> |
{- "id": "",
- "driveId": "",
- "filename": "",
- "mimeType": "",
- "sizeBytes": 0,
- "offset": 0,
- "chunkCount": 0,
- "status": "",
- "category": "",
- "storageUri": "string",
- "createdAt": "2019-08-24T14:15:22Z",
- "expiresAt": "2019-08-24T14:15:22Z"
}{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "id": "",
- "driveId": "",
- "filename": "",
- "mimeType": "",
- "sizeBytes": 0,
- "offset": 0,
- "chunkCount": 0,
- "status": "",
- "category": "",
- "storageUri": "string",
- "createdAt": "2019-08-24T14:15:22Z",
- "expiresAt": "2019-08-24T14:15:22Z"
}OAuth 2.0 authorization-code flow endpoints for drive providers (API Platform).
POST /api/storage/oauth/init — start the flow (returns auth URL + state) POST /api/storage/oauth/refresh/{driveId} — mint a fresh access token for a drive
The third endpoint, GET /api/storage/oauth/callback, lives on a plain Symfony route (see OAuthCallbackProvider) because API Platform's Get operation requires an identifier in the URI path and OAuth callbacks only carry their code/state/provider as query strings.
Retrieve a paginated list of storage drives with optional filters.
| scope | string Example: scope=tenant Filter by drive scope |
| status | string Enum: "active" "suspended" "disconnected" Example: status=active Filter by drive status |
| page | integer >= 1 Default: 1 Example: page=1 Page number for pagination |
| itemsPerPage | integer [ 1 .. 100 ] Default: 20 Example: itemsPerPage=20 Number of items per page |
{- "totalItems": 0,
- "search": {
- "@type": "string",
- "template": "string",
- "variableRepresentation": "string",
- "mapping": [
- {
- "@type": "string",
- "variable": "string",
- "property": "string",
- "required": true
}
]
}, - "view": {
- "@id": "string",
- "@type": "string",
- "first": "string",
- "last": "string",
- "previous": "string",
- "next": "string"
}, - "member": [
- {
- "@context": "string",
- "@id": "string",
- "@type": "string",
- "id": "01JNXZ00000000000000000000",
- "compactId": "string",
- "scope": "tenant",
- "label": "Primary Storage",
- "type": "platform",
- "status": "active",
- "purpose": "document",
- "pathPrefix": "/uploads",
- "isDefault": false,
- "quotaBytes": 1073741824,
- "usedBytes": 0,
- "createdAt": "2024-01-15T10:00:00+00:00"
}
]
}Create a new storage drive.
Drive creation data
| label required | string [ 1 .. 255 ] characters |
| type required | string Enum: "amazon_s3" "google_cloud_storage" "azure_blob" "google_drive" "dropbox" "onedrive" "sftp" "ftp" "s3_compatible" |
| pathPrefix | string or null |
| isDefault | boolean Default: false |
{- "label": "Primary Storage",
- "type": "amazon_s3",
- "pathPrefix": "/uploads",
- "isDefault": false
}{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "id": "01JNXZ00000000000000000000",
- "compactId": "string",
- "scope": "tenant",
- "label": "Primary Storage",
- "type": "platform",
- "status": "active",
- "purpose": "document",
- "pathPrefix": "/uploads",
- "isDefault": false,
- "quotaBytes": 1073741824,
- "usedBytes": 0,
- "createdAt": "2024-01-15T10:00:00+00:00"
}Returns whether the drive has stored credentials and a redacted set of public hints. Never returns secret material.
| driveId required | string Example: 01JNXZ00000000000000000000 Drive ULID |
{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "driveId": "01JNXZ00000000000000000000",
- "driveType": "amazon_s3",
- "hasCredentials": false,
- "configuredAt": "2026-05-28T11:00:00+00:00",
- "hints": {
- "property1": true,
- "property2": true
}
}Store encrypted credentials for an S3, S3-compatible, SFTP, or Azure Blob drive. The "type" field discriminates which fields apply.
| driveId required | string Example: 01JNXZ00000000000000000000 Drive ULID |
Credentials payload; field set selected by "type".
| type required | string Enum: "amazon_s3" "s3_compatible" "sftp" "azure_blob" |
| accessKeyId | string or null |
| secretAccessKey | string or null |
| bucket | string or null |
| region | string or null |
| endpoint | string or null |
| usePathStyleEndpoint | boolean or null |
| host | string or null |
| port | integer or null |
| username | string or null |
| password | string or null |
| privateKey | string or null |
| accountName | string or null |
| accountKey | string or null |
| container | string or null |
{- "type": "amazon_s3",
- "accessKeyId": "string",
- "secretAccessKey": "string",
- "bucket": "string",
- "region": "string",
- "endpoint": "string",
- "usePathStyleEndpoint": true,
- "host": "string",
- "port": 0,
- "username": "string",
- "password": "string",
- "privateKey": "string",
- "accountName": "string",
- "accountKey": "string",
- "container": "string"
}{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "driveId": "01JNXZ00000000000000000000",
- "driveType": "amazon_s3",
- "hasCredentials": false,
- "configuredAt": "2026-05-28T11:00:00+00:00",
- "hints": {
- "property1": true,
- "property2": true
}
}Retrieve a single storage drive by its ID.
| id required | string Example: 01JNXZ00000000000000000000 Drive ULID |
{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "id": "01JNXZ00000000000000000000",
- "compactId": "string",
- "scope": "tenant",
- "label": "Primary Storage",
- "type": "platform",
- "status": "active",
- "purpose": "document",
- "pathPrefix": "/uploads",
- "isDefault": false,
- "quotaBytes": 1073741824,
- "usedBytes": 0,
- "createdAt": "2024-01-15T10:00:00+00:00"
}Activate a suspended drive.
| id required | string Example: 01JNXZ00000000000000000000 Drive ULID |
{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "id": "01JNXZ00000000000000000000",
- "compactId": "string",
- "scope": "tenant",
- "label": "Primary Storage",
- "type": "platform",
- "status": "active",
- "purpose": "document",
- "pathPrefix": "/uploads",
- "isDefault": false,
- "quotaBytes": 1073741824,
- "usedBytes": 0,
- "createdAt": "2024-01-15T10:00:00+00:00"
}Disconnect a drive, making it unavailable.
| id required | string Example: 01JNXZ00000000000000000000 Drive ULID |
The new Drive resource
| reason required | string |
{- "reason": "string"
}{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "id": "01JNXZ00000000000000000000",
- "compactId": "string",
- "scope": "tenant",
- "label": "Primary Storage",
- "type": "platform",
- "status": "active",
- "purpose": "document",
- "pathPrefix": "/uploads",
- "isDefault": false,
- "quotaBytes": 1073741824,
- "usedBytes": 0,
- "createdAt": "2024-01-15T10:00:00+00:00"
}Suspend an active drive.
| id required | string Example: 01JNXZ00000000000000000000 Drive ULID |
The new Drive resource
| reason required | string |
{- "reason": "string"
}{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "id": "01JNXZ00000000000000000000",
- "compactId": "string",
- "scope": "tenant",
- "label": "Primary Storage",
- "type": "platform",
- "status": "active",
- "purpose": "document",
- "pathPrefix": "/uploads",
- "isDefault": false,
- "quotaBytes": 1073741824,
- "usedBytes": 0,
- "createdAt": "2024-01-15T10:00:00+00:00"
}Update the label of a drive.
| id required | string Example: 01JNXZ00000000000000000000 Drive ULID |
The new Drive resource
| label required | string [ 1 .. 255 ] characters |
{- "label": "string"
}{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "id": "01JNXZ00000000000000000000",
- "compactId": "string",
- "scope": "tenant",
- "label": "Primary Storage",
- "type": "platform",
- "status": "active",
- "purpose": "document",
- "pathPrefix": "/uploads",
- "isDefault": false,
- "quotaBytes": 1073741824,
- "usedBytes": 0,
- "createdAt": "2024-01-15T10:00:00+00:00"
}Returns the authorization URL the browser must navigate to plus the signed state token to be mirrored back on the callback.
Provider selection + optional redirect override.
| provider required | string Enum: "google_drive" "dropbox" "onedrive" |
| redirectUri | string or null |
| returnUri | string or null |
{- "provider": "google_drive",
- "redirectUri": "string",
- "returnUri": "string"
}{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "id": "oauth",
- "authorizationUrl": "string",
- "state": "string",
- "expiresInSeconds": 0,
- "driveId": "string",
- "expiresAt": "string"
}Refresh the OAuth access token for a drive
| driveId required | string StorageOAuth identifier |
The new StorageOAuth resource
| id | string Default: "oauth" Synthetic identifier; not part of any persisted entity. |
| authorizationUrl | string or null Authorization URL the browser must navigate to (only present on init) |
| state | string or null HMAC-signed state token (only present on init) |
| expiresInSeconds | integer or null Lifetime of the state token in seconds (only present on init) |
| driveId | string or null ULID of the drive (only present on refresh) |
| expiresAt | string or null ISO 8601 timestamp of the new access token expiry (only present on refresh) |
{- "id": "oauth",
- "authorizationUrl": "string",
- "state": "string",
- "expiresInSeconds": 0,
- "driveId": "string",
- "expiresAt": "string"
}{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "id": "oauth",
- "authorizationUrl": "string",
- "state": "string",
- "expiresInSeconds": 0,
- "driveId": "string",
- "expiresAt": "string"
}Fetch file metadata by its storage name.
| name required | string File identifier |
{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "name": "string",
- "path": "string",
- "isDirectory": false,
- "mimeType": "string",
- "sizeInBytes": 0,
- "lastModified": "string",
- "thumbnailUrl": "string",
- "storageUri": "",
- "originalFilename": "string",
- "fileType": "string",
- "derivedVariants": [
- {
- "property1": 0,
- "property2": 0
}
]
}Resource for the Storage signed file-access workflow:
POST /api/storage/drives/{driveId}/files/access-url — mint a short-lived signed URL for a file on a specific drive.
GET /api/storage/files/access/{token}
— public stream endpoint. The token IS the auth (signature + expiry).
Used by the frontend in /
The Get operation is also what API Platform uses to construct the @id IRI
for the mint response — keeping both operations on the same resource avoids
cross-resource IRI lookup issues.
List files and folders within a drive at the given path.
| driveId required | string Example: 01JNXZ00000000000000000000 Drive ULID |
| path | string Default: "/" Example: path=/invoices/2024 Directory path to list |
| page | integer Default: 1 The collection page number |
{- "totalItems": 0,
- "search": {
- "@type": "string",
- "template": "string",
- "variableRepresentation": "string",
- "mapping": [
- {
- "@type": "string",
- "variable": "string",
- "property": "string",
- "required": true
}
]
}, - "view": {
- "@id": "string",
- "@type": "string",
- "first": "string",
- "last": "string",
- "previous": "string",
- "next": "string"
}, - "member": [
- {
- "@context": "string",
- "@id": "string",
- "@type": "string",
- "name": "string",
- "path": "string",
- "isDirectory": false,
- "mimeType": "string",
- "sizeInBytes": 0,
- "lastModified": "string",
- "thumbnailUrl": "string",
- "storageUri": "",
- "originalFilename": "string",
- "fileType": "string",
- "derivedVariants": [
- {
- "property1": 0,
- "property2": 0
}
]
}
]
}Delete a file from the specified drive.
| driveId required | string Example: 01JNXZ00000000000000000000 Drive ULID |
| path required | string Example: path=invoices/2024/invoice-001.pdf File path within the drive |
{- "type": "/errors/validation-error",
- "title": "Validation Error",
- "status": 400,
- "detail": "The provided input is invalid",
- "violations": [
- {
- "propertyPath": "code",
- "message": "This value should not be blank."
}
], - "instance": "string"
}Mint a short-lived signed URL that streams the file via the public access endpoint. The URL can be embedded in /
| driveId required | string Drive ULID |
The new SignedFileUrl resource
| path required | string <= 1024 characters Default: "" |
| action | string or null Enum: "preview" "download" "share" "browse" |
| expirySeconds | integer or null [ 1 .. 3600 ] |
{- "path": "",
- "action": "preview",
- "expirySeconds": 1
}{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "token": "string",
- "url": "string",
- "expiresAt": "string",
- "expiresInSeconds": 0,
- "filename": "string",
- "mimeType": "string"
}Upload a file to the specified drive.
| driveId required | string Example: 01JNXZ00000000000000000000 Drive ULID |
File upload data (multipart/form-data)
| file required | string <binary> The file to upload |
| path | string Target folder to place the file in verbatim (e.g. "invoices/2024") |
| category | string Default: "uploads" Fallback folder when no path is given |
{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "name": "string",
- "storageUri": "string",
- "mimeType": "string",
- "sizeInBytes": 0,
- "originalFilename": "string",
- "fileType": "string"
}Create a new folder in the specified drive.
| driveId required | string Example: 01JNXZ00000000000000000000 Drive ULID |
Folder creation data
| path required | string Folder path to create |
{- "path": "invoices/2024"
}{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "name": "string",
- "path": "string",
- "isDirectory": false,
- "mimeType": "string",
- "sizeInBytes": 0,
- "lastModified": "string",
- "thumbnailUrl": "string",
- "storageUri": "",
- "originalFilename": "string",
- "fileType": "string",
- "derivedVariants": [
- {
- "property1": 0,
- "property2": 0
}
]
}Delete a folder and all its contents from the specified drive.
| driveId required | string Example: 01JNXZ00000000000000000000 Drive ULID |
| path required | string Example: path=invoices/2024 Folder path to delete |
{- "type": "/errors/validation-error",
- "title": "Validation Error",
- "status": 400,
- "detail": "The provided input is invalid",
- "violations": [
- {
- "propertyPath": "code",
- "message": "This value should not be blank."
}
], - "instance": "string"
}Resource for the Storage signed file-access workflow:
POST /api/storage/drives/{driveId}/files/access-url — mint a short-lived signed URL for a file on a specific drive.
GET /api/storage/files/access/{token}
— public stream endpoint. The token IS the auth (signature + expiry).
Used by the frontend in /
The Get operation is also what API Platform uses to construct the @id IRI
for the mint response — keeping both operations on the same resource avoids
cross-resource IRI lookup issues.
Public endpoint: streams the file bytes; the signed token IS the auth (signature + expiry).
| token required | string SignedFileUrl identifier |
{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "token": "string",
- "url": "string",
- "expiresAt": "string",
- "expiresInSeconds": 0,
- "filename": "string",
- "mimeType": "string"
}Same as the token stream, with a trailing /{filename} so external consumers see a real file extension. The token is the auth; the filename is ignored.
| token required | string SignedFileUrl identifier |
| filename required | string SignedFileUrl identifier |
{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "token": "string",
- "url": "string",
- "expiresAt": "string",
- "expiresInSeconds": 0,
- "filename": "string",
- "mimeType": "string"
}Declares an upload intent for a purpose + config and returns the intent — its id, resolved slots (each with accepted types/size/count constraints), and expiry. Idempotent per (purpose, config, identity): a reload returns the same active intent rather than duplicating staged files. Consumer features reference only the returned intent id.
The new UploadIntent resource
| purpose | string Default: "" |
object | |
| strategy | string or null return_active (default) | continue_progress | fresh |
{- "purpose": "",
- "config": {
- "property1": "string",
- "property2": "string"
}, - "strategy": "string"
}{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "id": "",
- "purpose": "",
- "status": "",
- "slots": [
- {
- "property1": "string",
- "property2": "string"
}
], - "staged": {
- "property1": [
- {
- "property1": 0,
- "property2": 0
}
], - "property2": [
- {
- "property1": 0,
- "property2": 0
}
]
}, - "expiresAt": "string",
- "createdAt": "string"
}Returns the intent status, its slots, and the files staged into each slot so far.
| id required | string Upload intent ULID |
{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "id": "",
- "purpose": "",
- "status": "",
- "slots": [
- {
- "property1": "string",
- "property2": "string"
}
], - "staged": {
- "property1": [
- {
- "property1": 0,
- "property2": 0
}
], - "property2": [
- {
- "property1": 0,
- "property2": 0
}
]
}, - "expiresAt": "string",
- "createdAt": "string"
}Marks the intent complete once every required slot has met its minimum file count. The consumer feature then references the intent id to read the staged files. Pass {"saveToDrive": true} to PROMOTE each staged file into the tenant's document drive (durable, browsable, re-readable) instead of leaving it ephemeral — the response staged then carries the document URIs.
| id required | string Upload intent ULID |
Optional finalize options.
| saveToDrive | boolean Default: false Promote staged files into the tenant document drive. |
{- "saveToDrive": false
}{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "id": "",
- "purpose": "",
- "status": "",
- "slots": [
- {
- "property1": "string",
- "property2": "string"
}
], - "staged": {
- "property1": [
- {
- "property1": 0,
- "property2": 0
}
], - "property2": [
- {
- "property1": 0,
- "property2": 0
}
]
}, - "expiresAt": "string",
- "createdAt": "string"
}Uploads one file (multipart form field file) into the named slot. The server sniffs the mime type from the bytes and validates it plus the size against the slot constraints — a client-declared content type is never trusted. P2 replaces this proxied path with a presigned direct-to-driver URL.
| id required | string Upload intent ULID |
| slot required | string Slot name |
The file to stage
| file | string <binary> |
{- "@context": "string",
- "@id": "string",
- "@type": "string",
- "id": "",
- "purpose": "",
- "status": "",
- "slots": [
- {
- "property1": "string",
- "property2": "string"
}
], - "staged": {
- "property1": [
- {
- "property1": 0,
- "property2": 0
}
], - "property2": [
- {
- "property1": 0,
- "property2": 0
}
]
}, - "expiresAt": "string",
- "createdAt": "string"
}