REST API
POST /v1/drives/{sg}/nodes
Upload a file
/v1/drives/{sg}/nodes Encrypts and uploads file into security group sg, optionally inside folder parent_id and linked to exam_id; single PUT or multipart is chosen by the SDK from its size. The whole multipart body is buffered (python-multipart's SpooledTemporaryFile, 1 MiB in RAM before it spills to disk) before this handler even runs, so request size is what bounds memory, not this endpoint's own logic.
Path parameters #
-
sgstring requiredThe security group (the drive).
Responses #
-
201 Successful Response
-
node_idstring required -
workspace_idstring required -
security_group_idstring required -
kindenum("file" | "folder") -
statusenum("pending" | "ready" | "failed") required -
exam_idobject -
parent_idobject -
modeobject -
media_kindobject -
declared_sizeobject -
sizeobject -
mime_typeobject -
encrypted_nameobject requiredThe `{salt, nonce, ciphertext}` shape (all b64url) that crosses the wire. This is the only envelope shape in the protocol — a `HybridSeal` (§6) reuses these same three field names even though its `salt` carries a KEM encapsulation instead of an HKDF salt, so the wire JSON of both looks identical to anything that doesn't know the difference.
-
encrypted_keysobject required -
storage_pathobject -
optimized_variantsobject[] -
processing_statusobject -
processing_errorobject -
total_sizeobject -
created_bystring required -
created_atstring required -
completed_atobject -
is_deletedboolean
-
-
400 The vault refused the request as invalid.
-
errorobject requiredThe body of every non-2xx response: what went wrong, for a program and for a person.
-
-
401 No client certificate, or the SDK session was rejected or expired (`session_expired`).
-
errorobject requiredThe body of every non-2xx response: what went wrong, for a program and for a person.
-
-
402 The workspace has no credit for this.
-
errorobject requiredThe body of every non-2xx response: what went wrong, for a program and for a person.
-
-
403 CN not allowed, permission denied, or no key for the data's security group (`group_key_unavailable`).
-
errorobject requiredThe body of every non-2xx response: what went wrong, for a program and for a person.
-
-
404 Not found — also a node read under a group it does not belong to.
-
errorobject requiredThe body of every non-2xx response: what went wrong, for a program and for a person.
-
-
409 A pending or newer version, a replay, or an upload that never reached storage.
-
errorobject requiredThe body of every non-2xx response: what went wrong, for a program and for a person.
-
-
422 The body or query failed validation (`invalid_request`).
-
errorobject requiredThe body of every non-2xx response: what went wrong, for a program and for a person.
-
-
429 Rate limited, after the SDK's own backoff gave up.
-
errorobject requiredThe body of every non-2xx response: what went wrong, for a program and for a person.
-
-
500 An envelope did not open (`crypto_error`, no detail on purpose).
-
errorobject requiredThe body of every non-2xx response: what went wrong, for a program and for a person.
-
-
502 The vault failed, or answered outside the protocol (`protocol_error`).
-
errorobject requiredThe body of every non-2xx response: what went wrong, for a program and for a person.
-
curl --cert client.crt --key client.key \
-X POST "https://api.imgexam.com/v1/drives/<sg>/nodes" \
-H "Content-Type: application/json"import httpx
response = httpx.post(
"https://api.imgexam.com/v1/drives/<sg>/nodes",
cert=("client.crt", "client.key"),
)
response.raise_for_status()
print(response.json()){
"node_id": "string",
"workspace_id": "string",
"security_group_id": "string",
"kind": "file",
"status": "pending",
"exam_id": {},
"parent_id": {},
"mode": {},
"media_kind": {},
"declared_size": {},
"size": {},
"mime_type": {},
"encrypted_name": {
"salt": "string",
"nonce": "string",
"ciphertext": "string"
},
"encrypted_keys": {},
"storage_path": {},
"optimized_variants": [
{
"kind": "string",
"storage_bucket": "string",
"storage_path": "string",
"storage_size": 0,
"storage_mime_type": "string",
"created_at": "string"
}
],
"processing_status": {},
"processing_error": {},
"total_size": {},
"created_by": "string",
"created_at": "string",
"completed_at": {},
"is_deleted": false
}{
"error": {
"code": "string",
"message": "string",
"trace_id": {}
}
}{
"error": {
"code": "string",
"message": "string",
"trace_id": {}
}
}{
"error": {
"code": "string",
"message": "string",
"trace_id": {}
}
}{
"error": {
"code": "string",
"message": "string",
"trace_id": {}
}
}{
"error": {
"code": "string",
"message": "string",
"trace_id": {}
}
}{
"error": {
"code": "string",
"message": "string",
"trace_id": {}
}
}{
"error": {
"code": "string",
"message": "string",
"trace_id": {}
}
}{
"error": {
"code": "string",
"message": "string",
"trace_id": {}
}
}{
"error": {
"code": "string",
"message": "string",
"trace_id": {}
}
}{
"error": {
"code": "string",
"message": "string",
"trace_id": {}
}
}