Skip to content

When you're migrating from another platform, seeding staging with a known list of customers, or onboarding a batch of new tenants at once, use the bulk-import endpoint. It creates up to 1000 tenants in a single request and reports per-row status (created vs skipped due to a pre-existing ID).

From the dashboard

  1. Open Tenants and click Bulk import in the top-right.
  2. Choose the format — CSV or JSON.
  3. Either click the upload area (or drag-and-drop) a .csv / .json file (up to 5 MB), or paste the content directly into the editor below.
  4. If you're using CSV, click Download CSV template for the expected header row.
  5. Review the parse preview — the modal shows how many rows are ready to import and flags any parse errors.
  6. Click Import. The modal closes and a toast reports the result — Imported N tenants (with M skipped (already existed) if any collided on ID).

CSV format

Headers required: id. Optional: name, metadata. Wrap metadata in double quotes because it contains commas — the JSON is what's stored:

id,name,metadata
acme-corp,Acme Corp,"{""tier"":""gold"",""region"":""wnam""}"
globex-inc,Globex Inc,"{""tier"":""silver""}"
initech,Initech,

JSON format

A top-level array of tenant objects:

[
  { "id": "acme-corp", "name": "Acme Corp", "metadata": { "tier": "gold" } },
  { "id": "globex-inc", "name": "Globex Inc" },
  { "id": "initech" }
]

From the API

curl --location 'https://api.truto.one/tenant/bulk' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <your-api-token>' \
--data '{
    "tenants": [
        { "id": "acme-corp", "name": "Acme Corp", "metadata": {"tier":"gold"} },
        { "id": "globex-inc", "name": "Globex Inc" },
        { "id": "initech" }
    ]
}'

Response (201 Created):

{
  "created": [
    { "id": "acme-corp", "environment_id": "…", "name": "Acme Corp", "metadata": {"tier":"gold"}, "created_at": "…", "updated_at": "…" },
    { "id": "globex-inc", "environment_id": "…", "name": "Globex Inc", "metadata": {}, "created_at": "…", "updated_at": "…" },
    { "id": "initech", "environment_id": "…", "name": "initech", "metadata": {}, "created_at": "…", "updated_at": "…" }
  ],
  "skipped": []
}

Rows in skipped collided on (id, environment_id) — a tenant with that ID already existed. The endpoint uses INSERT ... ON CONFLICT DO NOTHING, so the request never fails partway through: whichever rows can be inserted are, and the rest are reported back to you.

Caps: at most 1000 tenants per request. Split larger inputs client-side and call POST /tenant/bulk multiple times.

From the CLI

# Inline
truto tenants create-bulk -b '[{"id":"acme"},{"id":"globex"}]'
 
# From an NDJSON file (one JSON object per line)
cat tenants.ndjson | truto tenants create-bulk --stdin
 
# From a JSON array file
truto tenants create-bulk -b "$(cat tenants.json)"

The CLI enforces the 1000-row cap client-side so you catch it before hitting the network. See truto tenants.

Idempotency and re-runs

Because the endpoint skips existing IDs rather than failing, re-running the same import is safe: rerunning after adding a few new entries returns only those new entries in created and the rest in skipped. This makes it usable as a "sync my customer list" job on a cron.

If you need "upsert" semantics — update name / metadata on rows that already exist — do that separately with PATCH /tenant/:id after the bulk create returns its skipped list.