QMail Object Transfers

DURABLE ASYNC

Object Transfer v1 provides resumable uploads, downloads, replacements, and deletions using local file paths and durable operation records.

/api/qmail/net/objects/* and /api/qmail/net/object-transfers/*

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.

Local Filesystem Contract

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.

Session Token

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.

64-bit JSON Values

Object sizes, byte counts, generations, retention values, and protocol timestamps that may exceed JavaScript's safe integer range are returned as decimal strings.

No Transfer List Endpoint

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/capabilitiesRead live Object Transfer limits and storage classes from one QMail RAIDA server.
POST/api/qmail/net/objects/uploadStart a durable object create or replacement upload.
POST/api/qmail/net/objects/downloadStart a durable range download into a verified local file.
POST/api/qmail/net/objects/deleteStart an owner-authorized generation tombstone operation.
GET/api/qmail/net/object-transfers/statusRead durable operation state and exact byte progress.
POST/api/qmail/net/object-transfers/resumeResume an operation in the paused state.
POST/api/qmail/net/object-transfers/cancelRequest 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

ParameterTypeRequiredDescription
wallet_pathstringNoWallet used to authenticate the RAIDA request. Uses normal wallet resolution when omitted.
raida_idinteger 0-24NoQMail 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

ParameterTypeRequiredDescription
source_pathstring (local path)YesExisting file readable by rest_core.
wallet_pathstringNoWallet used for identity, encryption, and payment.
raida_idinteger 0-24NoTarget server. Default 0.
file_typeinteger 0-255NoQMail file type. Default 2.
object_id32 hex charactersReplacement onlyStable object ID. For create, omit to generate one.
locker_keystringReplacement onlyOriginal locker key for the object.
retention_secondsuint64 decimalNoZero requests the server default; nonzero must be accepted exactly.
storage_classinteger 0-65535NoRequested storage class. Default 0.
preferred_chunk_bytesuint32NoClient preference; the server's accepted chunk size is authoritative.
operationintegerNo0 create (default), 1 replacement.
expected_generationuint64 decimalNoCreate requires zero. Replacement uses exact compare-and-swap.
target_generationuint64 decimalNoMonotonic 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

ParameterTypeRequiredDescription
object_id32 hex charactersYesObject to download.
destination_pathstring (local path)YesFinal local file path.
wallet_pathstringNoWallet used to authenticate and decrypt.
raida_idinteger 0-24NoSource server. Default 0.
file_typeinteger 0-255NoQMail file type. Default 2.
generationuint64 decimalNoGeneration to pin. Zero lets object-info resolve the current committed generation.
preferred_range_bytesuint32NoPreferred 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

ParameterTypeRequiredDescription
object_id32 hex charactersYesObject to tombstone.
expected_generationuint64 decimalYes in practiceExact current generation used for compare-and-swap.
target_generationuint64 decimalYesMust be greater than expected_generation.
wallet_pathstringNoOwner wallet.
raida_idinteger 0-24NoTarget server. Default 0.
file_typeinteger 0-255NoQMail 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:

StatusNameMeaning
218ERROR_TCP_REQUIREDTransfer command was attempted over an unsupported transport.
220ERROR_OBJECT_TOO_LARGEObject exceeds the server limit.
223ERROR_TRANSFER_EXPIREDTransfer reservation expired.
225ERROR_TRANSFER_INCOMPLETERequired ranges are still missing; this status is resumable.
226ERROR_HASH_MISMATCHRange or whole-object integrity verification failed.
227ERROR_QUOTA_EXCEEDEDIdentity or server capacity quota was exceeded.
230ERROR_STORAGE_FULLSelected storage class lacks free capacity.
233ERROR_GENERATION_CONFLICTExpected generation no longer matches.
234ERROR_TRANSFER_CONFLICTTransfer 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.