QMail Object Transfers
DURABLE ASYNCObject Transfer v1 provides resumable uploads, downloads, replacements, and deletions using local file paths and durable operation records.
Overview
Object operations start in the background and return HTTP 202 Accepted with an operation_id. Poll the status endpoint until the operation completes, pauses, fails, or is cancelled.
File bytes are not uploaded through HTTP. Uploads use a source_path visible to rest_core; downloads use a local destination_path. Treat these endpoints as trusted local-client APIs, not public web upload endpoints.
Because these endpoints accept arbitrary local filesystem paths, rest_core supports an optional per-session token (-api-token flag or QMAIL_API_TOKEN environment variable). When configured, every endpoint on this page requires Authorization: Bearer <token> and answers 401 otherwise. The QMail GUI generates a random token at launch and passes it to the backend automatically. With no token configured, no header is required.
Object sizes, byte counts, generations, retention values, and protocol timestamps that may exceed JavaScript's safe integer range are returned as decimal strings.
The API currently supports lookup by operation_id only. A client that needs restart recovery must persist its operation IDs before restarting.
Endpoints
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/qmail/net/objects/capabilities | Read live Object Transfer limits and storage classes from one QMail RAIDA server. |
| POST | /api/qmail/net/objects/upload | Start a durable object create or replacement upload. |
| POST | /api/qmail/net/objects/download | Start a durable range download into a verified local file. |
| POST | /api/qmail/net/objects/delete | Start an owner-authorized generation tombstone operation. |
| GET | /api/qmail/net/object-transfers/status | Read durable operation state and exact byte progress. |
| POST | /api/qmail/net/object-transfers/resume | Resume an operation in the paused state. |
| POST | /api/qmail/net/object-transfers/cancel | Request cancellation of an operation. |
Parameters may be supplied as query parameters or URL-encoded form fields. Each endpoint enforces its documented HTTP method.
GET /objects/capabilities
| Parameter | Type | Required | Description |
|---|---|---|---|
wallet_path | string | No | Wallet used to authenticate the RAIDA request. Uses normal wallet resolution when omitted. |
raida_id | integer 0-24 | No | QMail RAIDA server. Default 0. |
The response includes capability_schema, protocol_min, protocol_max, transport/server flags, preferred and maximum chunk sizes, maximum download range, active-transfer and parallelism limits, max_object_bytes, capability timestamps, payment mode, and storage_classes[].
{
"success": true,
"message": "Object Transfer capabilities",
"raida_id": 0,
"protocol_min": 1,
"protocol_max": 1,
"preferred_chunk_bytes": 1048576,
"max_chunk_bytes": 8388608,
"max_download_range_bytes": 8388608,
"max_object_bytes": "26843545600",
"storage_classes": [
{
"class_id": 1,
"capacity_bytes": "17179869184",
"available_bytes": "12884901888",
"max_retention_seconds": "31536000"
}
]
}
POST /objects/upload
| Parameter | Type | Required | Description |
|---|---|---|---|
source_path | string (local path) | Yes | Existing file readable by rest_core. |
wallet_path | string | No | Wallet used for identity, encryption, and payment. |
raida_id | integer 0-24 | No | Target server. Default 0. |
file_type | integer 0-255 | No | QMail file type. Default 2. |
object_id | 32 hex characters | Replacement only | Stable object ID. For create, omit to generate one. |
locker_key | string | Replacement only | Original locker key for the object. |
retention_seconds | uint64 decimal | No | Zero requests the server default; nonzero must be accepted exactly. |
storage_class | integer 0-65535 | No | Requested storage class. Default 0. |
preferred_chunk_bytes | uint32 | No | Client preference; the server's accepted chunk size is authoritative. |
operation | integer | No | 0 create (default), 1 replacement. |
expected_generation | uint64 decimal | No | Create requires zero. Replacement uses exact compare-and-swap. |
target_generation | uint64 decimal | No | Monotonic generation requested for the committed object. |
A replacement requires object_id and locker_key. The source file is hashed before transfer and must not change after the request is frozen.
POST /objects/download
| Parameter | Type | Required | Description |
|---|---|---|---|
object_id | 32 hex characters | Yes | Object to download. |
destination_path | string (local path) | Yes | Final local file path. |
wallet_path | string | No | Wallet used to authenticate and decrypt. |
raida_id | integer 0-24 | No | Source server. Default 0. |
file_type | integer 0-255 | No | QMail file type. Default 2. |
generation | uint64 decimal | No | Generation to pin. Zero lets object-info resolve the current committed generation. |
preferred_range_bytes | uint32 | No | Preferred independent download range size. |
The manager writes a unique .qmail.part file, downloads only unfinished ranges, verifies the complete SHA-256 hash, and atomically moves the verified file to destination_path.
POST /objects/delete
| Parameter | Type | Required | Description |
|---|---|---|---|
object_id | 32 hex characters | Yes | Object to tombstone. |
expected_generation | uint64 decimal | Yes in practice | Exact current generation used for compare-and-swap. |
target_generation | uint64 decimal | Yes | Must be greater than expected_generation. |
wallet_path | string | No | Owner wallet. |
raida_id | integer 0-24 | No | Target server. Default 0. |
file_type | integer 0-255 | No | QMail file type. Default 2. |
GET /object-transfers/status
Required parameter: operation_id, a 32-character hexadecimal operation identifier returned by upload, download, or delete.
Transfer Response Fields
operation_id, task_id, direction, state, raida_id, transfer_id, object_id, file_type, requested/accepted storage class, generation, target_generation, total_bytes, completed_bytes, progress, chunk_bytes, max_parallel, expires_at, object_hash, cancel_requested, last_result, last_status, optional error, relevant local paths, and created/updated timestamps.
{
"success": true,
"operation_id": "00112233445566778899aabbccddeeff",
"task_id": "task-123",
"direction": "upload",
"state": "uploading",
"raida_id": 0,
"object_id": "ffeeddccbbaa99887766554433221100",
"generation": "0",
"target_generation": "1",
"total_bytes": "25000000000",
"completed_bytes": "12500000000",
"progress": 50,
"chunk_bytes": 1048576,
"max_parallel": 4,
"last_result": 0,
"last_status": 0,
"source_path": "D:\\Mail\\large-attachment.bin"
}
Known states are created, hashing, beginning, uploading, committing, querying, downloading, verifying, completed, paused, cancelling, cancelled, and failed.
Resume and Cancel
POST /api/qmail/net/object-transfers/resume?operation_id=... resumes only an operation whose durable state is paused. Missing operations return HTTP 404; non-paused operations return HTTP 409.
POST /api/qmail/net/object-transfers/cancel?operation_id=... records a cancellation request. Upload cancellation sends Object Transfer command 80; partial download files are removed. The response confirms the request, not necessarily immediate termination.
Errors
last_status contains the Object Transfer protocol status when a RAIDA server rejects an operation. Important values include:
| Status | Name | Meaning |
|---|---|---|
| 218 | ERROR_TCP_REQUIRED | Transfer command was attempted over an unsupported transport. |
| 220 | ERROR_OBJECT_TOO_LARGE | Object exceeds the server limit. |
| 223 | ERROR_TRANSFER_EXPIRED | Transfer reservation expired. |
| 225 | ERROR_TRANSFER_INCOMPLETE | Required ranges are still missing; this status is resumable. |
| 226 | ERROR_HASH_MISMATCH | Range or whole-object integrity verification failed. |
| 227 | ERROR_QUOTA_EXCEEDED | Identity or server capacity quota was exceeded. |
| 230 | ERROR_STORAGE_FULL | Selected storage class lacks free capacity. |
| 233 | ERROR_GENERATION_CONFLICT | Expected generation no longer matches. |
| 234 | ERROR_TRANSFER_CONFLICT | Transfer ID was reused with different immutable fields. |
The complete protocol status assignment is defined by the Object Transfer v1 protocol specification. Local validation errors generally return HTTP 400; missing operation IDs return 404; invalid resume/cancel state returns 409; capability transport failures return 502; wrong HTTP methods return 405.
Persistence and Restart
Transfer operations and pending byte ranges are stored in qmail.db. On rest_core restart, in-flight ranges return to pending state and resumable operations are restarted. Upload recovery reuses the persisted Transfer ID and reconciles missing ranges; download recovery resumes only unfinished ranges against pinned object metadata.