Skip to content

Open a pending attachment + mint a presigned upload

POST
/v1/attachments
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.

Idempotency-Key
string
>= 1 characters

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.

Media typeapplication/json
object
content_category

Optional caller-assigned classification (e.g. id_document, invoice).

string
content_type
required

Declared MIME type of the upload; HEAD-verified against the actual bytes at finalize.

string
filename

Optional original filename, preserved for the download disposition.

string
host_id
required

Id of the host object.

string format: uuid
host_kind
required

The object type the attachment hangs off.

string
Allowed values: person reservation property
max_bytes

Optional caller ceiling on the upload size, clamped to the bucket max.

integer format: int64

Pending reference opened; the body carries the presigned upload. An idempotent replay adds idempotent_replay: true.

Media typeapplication/json
object
id
required
string format: uuid
idempotent_replay

Present and true only on an idempotent replay.

boolean
max_bytes
required
integer format: int64
status
required
Allowed value: pending
upload
required
object
expires_at
required
string format: date-time
headers
required

Headers the client MUST echo verbatim on the presigned PUT.

Array<object>
object
name
required
string
value
required
string
method
required
string
url
required
string
Example
{
"status": "pending"
}
Cache-Control
string
Allowed value: no-store

Always no-store — live data is never cacheable.

Missing the objects:write scope.

Media typeapplication/json

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
error
required
object
code
required

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).

string
instance_id

Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.

string
message
required

Human-readable explanation of the failure.

string
parameters

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.

Media typeapplication/json

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
error
required
object
code
required

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).

string
instance_id

Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.

string
message
required

Human-readable explanation of the failure.

string
parameters

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).

Media typeapplication/json

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
error
required
object
code
required

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).

string
instance_id

Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.

string
message
required

Human-readable explanation of the failure.

string
parameters

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"
}
}