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 accepts up to 1000 tenants in a single request and reports per-row status.

From the dashboard

  1. Open Tenants and click Bulk import in the top-right. Tenants list with Bulk import
  2. Choose the format — CSV or JSON.
  3. Choose the mode:
    • Insert (default) — create new tenants; existing ids are skipped.
    • Upsert — create new tenants and update name / metadata when the id already exists.
  4. 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.
  5. If you're using CSV, click Download CSV template for the expected header row.
  6. Fix any validation errors (shown as a toast). Import stays disabled until the content is valid.
  7. Click Import / Upsert. A toast reports how many tenants were created, updated, or skipped. Bulk import tenants modal

Allowed fields

Each row may only include:

Field Required Rules
id Yes URL-path-safe: letters, digits, and . _ : @ + -. 1–255 characters. Unique within the file.
name No Non-empty string, max 255 characters. Defaults to id on insert when omitted.
metadata No JSON object (not an array or primitive). Blank keys are rejected.

Unknown columns (CSV) or unknown keys (JSON) are rejected. CSV data rows may not have more values than the header defines.

CSV format

Headers required: id. Optional: name, metadata. Wrap metadata in double quotes — 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 '{
    "mode": "insert",
    "tenants": [
        { "id": "acme-corp", "name": "Acme Corp", "metadata": {"tier":"gold"} },
        { "id": "globex-inc", "name": "Globex Inc" },
        { "id": "initech" }
    ]
}'

mode is optional and defaults to insert. Set "mode": "upsert" to update existing rows.

Insert response

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

Rows in skipped collided on (id, environment_id). Insert uses INSERT ... ON CONFLICT DO NOTHING, so the request never fails partway through.

Upsert response

{
  "created": [{ "id": "initech", "name": "initech", "metadata": {}, "…": "…" }],
  "updated": [{ "id": "acme-corp", "name": "Acme Corp", "metadata": { "tier": "gold" }, "…": "…" }],
  "skipped": []
}

On upsert, omitted name / metadata keep the existing row's values. Newly created rows still default name to id and metadata to {} when omitted.

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

From the CLI

# Insert (default) — skips existing ids
truto tenants create-bulk -b '[{"id":"acme"},{"id":"globex"}]'
 
# Upsert — updates name/metadata on existing ids
truto tenants create-bulk --mode upsert -b '[{"id":"acme","name":"Acme Corp"}]'
 
# 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

Insert: re-running the same import is safe — existing ids come back in skipped, and only new ids appear in created. Useful as a "sync my customer list" cron that only adds missing tenants.

Upsert: re-running applies the latest name / metadata from the file to matching ids and creates any missing ones.