Skip to content

Verbatim NDJSON import of a full tenant (the relocation counterpart of export)

POST
/v1/tenant-import-requests
curl --request POST \
--url 'https://api.aerandil.com/v1/tenant-import-requests?tenant_id=2489E9AD-2EE2-8E00-8EC9-32D5F69181C0' \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/x-ndjson' \
--data '{ "created_at": "2026-04-15T12:00:00Z", "deleted_at": "2026-04-15T12:00:00Z", "deleted_by": "example", "id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "kind": "object", "object_type": "example", "properties": {}, "scope": "example", "tenant_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "updated_at": "2026-04-15T12:00:00Z", "version": 1 }'

Load a tenant’s footprint — the application/x-ndjson body produced by POST /v1/tenant-export-requests — verbatim into this cell. The write-side half of the tenant- relocation protocol (cloud proposal 0007 §8).

Operator-level scope: tenant_admin:write. Same surface as export and per-tenant erasure.

Verbatim, not replayed. Rows load exactly as the source held them — version, soft-delete state, link temporal windows + properties, and the portable per-tenant ordinal tenant_seq are all preserved, and no new events are emitted. The global seq and CDC commit_xid are reassigned by this cell.

Atomic + idempotent. The whole dump loads in one transaction (all-or-nothing); every insert is ON CONFLICT DO NOTHING on the row’s natural key, so a retried import skips already-present rows (counted, not errored) — the basis for crash resumption.

Single-tenant. Every row’s tenant_id must equal the tenant_id query parameter; a foreign row aborts with 422 tenant_mismatch, and an incomplete dump (an export {"kind":"error"} trailer) with 422 incomplete_export.

NDJSON is the small-tenant path — the dump+load freezes the tenant for the whole transfer. A large tenant relocates via row-filtered logical replication (proposal 0007 §3).

Writes through the admin pool (BYPASSRLS); tenant isolation is the single-tenant check, not the RLS GUC.

tenant_id
required
string format: uuid

The tenant every imported row must belong to.

An application/x-ndjson export stream — one TenantExportLine (object / link / event) per line.

Media typeapplication/x-ndjson
One of: discriminator: kind
object
created_at
required
string format: date-time
deleted_at
string format: date-time
nullable
deleted_by
nullable
id
required
string format: uuid
kind
required
string
Allowed value: object
Allowed values: object
object_type
required
string
properties
required
object
scope
required

Org-scope envelope (tenant/brand/portfolio/property/venue).

tenant_id
required
string format: uuid
updated_at
required
string format: date-time
version
required
integer

Import committed. Per-table tally of rows loaded vs. skipped (already present).

Media typeapplication/json

What a tenant import committed: a per-table tally of rows newly loaded vs. skipped (already present under the idempotent ON CONFLICT DO NOTHING).

object
events
required
object
loaded
required

Rows newly inserted by this import.

integer
skipped
required

Rows already present, skipped idempotently (not an error).

integer
links
required
object
loaded
required

Rows newly inserted by this import.

integer
skipped
required

Rows already present, skipped idempotently (not an error).

integer
objects
required
object
loaded
required

Rows newly inserted by this import.

integer
skipped
required

Rows already present, skipped idempotently (not an error).

integer
tenant_id
required
string format: uuid
Examplegenerated
{
"events": {
"loaded": 1,
"skipped": 1
},
"links": {
"loaded": 1,
"skipped": 1
},
"objects": {
"loaded": 1,
"skipped": 1
},
"tenant_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0"
}
Cache-Control
string
Allowed value: no-store

Always no-store — a live operation result.

Missing the tenant_admin:write scope. Tenant- level tokens MUST NOT carry this 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"
}
}

Body invalid (invalid_body), a row’s tenant_id differs from the target (tenant_mismatch), or the dump is incomplete (incomplete_export).

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