Open a pending attachment + mint a presigned upload
const url = 'https://api.aerandil.com/v1/attachments';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"content_category":"example","content_type":"example","filename":"example","host_id":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","host_kind":"person","max_bytes":1}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.aerandil.com/v1/attachments \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "content_category": "example", "content_type": "example", "filename": "example", "host_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "host_kind": "person", "max_bytes": 1 }'Open a pending stored_object reference against a host object (person / reservation / property) and mint a short-lived presigned S3 PUT the client uploads the bytes to directly. The row starts pending; the bytes are not the source of truth until POST /v1/attachments/{id}/finalize HEAD-verifies them.
Scope: objects:write.
Requires AERANDIL_OBJECT_STORE_ENABLED — returns 501 when object storage is not configured for the cell.
Idempotent via Idempotency-Key. A replay returns the original envelope with idempotent_replay: true added, rather than opening a second reference.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Header Parameters
Section titled “Header Parameters”Opaque idempotency token (Stripe-shape). Repeated POSTs with the same key replay the original response within the cache TTL rather than creating a duplicate object.
Request Bodyrequired
Section titled “Request Bodyrequired”object
Optional caller-assigned classification (e.g. id_document, invoice).
Declared MIME type of the upload; HEAD-verified against the actual bytes at finalize.
Optional original filename, preserved for the download disposition.
Id of the host object.
The object type the attachment hangs off.
Optional caller ceiling on the upload size, clamped to the bucket max.
Responses
Section titled “Responses”Pending reference opened; the body carries the presigned upload. An idempotent replay adds idempotent_replay: true.
object
Present and true only on an idempotent replay.
object
Headers the client MUST echo verbatim on the presigned PUT.
object
Example
{ "status": "pending"}Headers
Section titled “Headers”Always no-store — live data is never cacheable.
Missing the objects:write scope.
Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.
object
object
Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).
Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.
Human-readable explanation of the failure.
Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).
Examplegenerated
{ "error": { "code": "example", "instance_id": "example", "message": "example", "parameters": "example" }}Invalid body — malformed host_kind, host_id, or content_type.
Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.
object
object
Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).
Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.
Human-readable explanation of the failure.
Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).
Examplegenerated
{ "error": { "code": "example", "instance_id": "example", "message": "example", "parameters": "example" }}Object storage not enabled (AERANDIL_OBJECT_STORE_ENABLED unset).
Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.
object
object
Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).
Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.
Human-readable explanation of the failure.
Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).
Examplegenerated
{ "error": { "code": "example", "instance_id": "example", "message": "example", "parameters": "example" }}