Verbatim NDJSON import of a full tenant (the relocation counterpart of export)
const url = 'https://api.aerandil.com/v1/tenant-import-requests?tenant_id=2489E9AD-2EE2-8E00-8EC9-32D5F69181C0';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/x-ndjson'}, body: '{ "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 }'};
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/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.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Query Parameters
Section titled “Query Parameters”The tenant every imported row must belong to.
Request Bodyrequired
Section titled “Request Bodyrequired”An application/x-ndjson export stream — one TenantExportLine (object / link / event) per line.
object
object
Org-scope envelope (tenant/brand/portfolio/property/venue).
object
When the link starts being in effect (migration 0011).
When the link stops being in effect; null is open-ended (still in effect).
Typed link-property bag (migration 0012); {} for adjacency-only links.
object
object
Underlying ObjectEvent discriminant — object_created, object_updated, object_deleted, object_linked, object_unlinked. (Distinct from the top-level kind which is always event for this variant.)
object
object
object
object
Service api_name (e.g. connector.opera_pms).
object
object
object
object
Service api_name (e.g. connector.opera_pms).
Model the agent ran with (e.g. claude-opus-4-7).
Issuer of the agent (e.g. anthropic, openai).
Version string under the vendor’s namespace.
The source DB’s global surrogate ordinal — a within-stream resume cursor only. A relocation import reassigns it; use tenant_seq for portable ordering.
Portable per-tenant ordinal (UNIQUE (tenant_id, tenant_seq), migration 0019) that travels with the tenant across a cross-cell relocation. Always present for exported (tenant-owned) rows. (The source-local commit_xid CDC horizon is deliberately not exported — a destination re-stamps its own.)
object
Stream-level failure surfaced in-band. Emitted as the last line when the dump errors after headers have been sent — the receiver tails the body and observes the error rather than getting a non-200 status.
Responses
Section titled “Responses”Import committed. Per-table tally of rows loaded vs. skipped (already present).
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
object
Rows newly inserted by this import.
Rows already present, skipped idempotently (not an error).
object
Rows newly inserted by this import.
Rows already present, skipped idempotently (not an error).
object
Rows newly inserted by this import.
Rows already present, skipped idempotently (not an error).
Examplegenerated
{ "events": { "loaded": 1, "skipped": 1 }, "links": { "loaded": 1, "skipped": 1 }, "objects": { "loaded": 1, "skipped": 1 }, "tenant_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0"}Headers
Section titled “Headers”Always no-store — a live operation result.
Missing the tenant_admin:write scope. Tenant- level tokens MUST NOT carry this 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" }}Body invalid (invalid_body), a row’s tenant_id differs from the target (tenant_mismatch), or the dump is incomplete (incomplete_export).
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" }}