Provisioning organizations & accounts at scale

When you onboard a large customer — thousands of OUs and member accounts in one go — call the bulk and paginated APIs below rather than looping the single-entity verbs. They are idempotent, apply backpressure, and come with published latency targets you can budget retries against.

All paths are under the public gateway at https://api.flexgalaxy.ai, signed with FGAI-HMAC-SHA256 (see Integrate an external app with AK/SK). Management-account context is supplied with the X-Account-Id header. The wire is snake_case.

Create OUs in bulk

POST /organizations/v1/{org}/ous/batchsynchronous, up to 500 OUs per call.

  • Items are topologically sorted, so you can create a whole subtree in one request and reference a not-yet-created parent by parent_ref.

  • Each item commits in its own transaction: one bad item (name collision, quota trip, bad parent) is rejected on its own and never discards the rest. You get a per-item accepted/rejected result set.

  • Requires an Idempotency-Key header. Re-POSTing the same key replays the exact same result instead of creating duplicates.

Import member accounts in bulk

POST /accounts/v1/provision/batchasynchronous, up to 1000 accounts per call.

  • Up-front rejects (quota headroom, duplicate email, unknown OU) come back immediately in the response.

  • Everything accepted is enqueued on a durable relay and returns a batch_id.

  • Poll GET /accounts/v1/provision/batch/{batchId} for {total, pending, completed, failed, items[]}.

  • Also Idempotency-Key-guarded.

Account provisioning is asynchronous by design — each account is stood up in the background (identity realm, root user, org-access role). A 202/enqueue is fast; the account becomes live a few seconds later. Drive large onboards as a sustained background import, not a single burst: submit, poll the batch, and honor Retry-After (below). Reconcile any in-flight single-provision work with GET /accounts/v1/provision/jobs.

Enterprise onboarding — placeholder accounts & lazy materialization

For an enterprise import where most accounts will not be used immediately (a franchise rolling out thousands of locations, say), use the onboarding path instead of provisioning every account up front. It creates cheap placeholder accounts now and defers the expensive per-account setup — identity realm, authz projection — to first use.

POST /organizations/v1/{org}/onboarding/accountssynchronous 202, bulk placeholder create.

{ "ou_node_id": "<ou>", "accounts": [ { "name": "warehouse-01" }, { "name": "warehouse-02" } ] }

Each returned account is { "status": "PLACEHOLDER", "realm_status": "NONE" } — a row, nothing more. A 20K-account import is just that many rows and stays fast.

Accounts materialize lazily on first use:

Call

Transition

POST …/onboarding/accounts/{account}:grant-operator

first operator grant → PLACEHOLDERACTIVE

POST …/onboarding/accounts/{account}:provision-service-user

first service user → materializes the account realm

Teardown mirrors it:

Call

Effect

POST …/onboarding/accounts/{account}:close

transition to terminal CLOSED (idempotent)

POST …/onboarding/accounts/{account}:purge

204 hard-delete of a never-materialized placeholder; a materialized account returns 409 — close it instead

Read the roster with the lifecycle view, optionally filtered by status:

GET /organizations/v1/{org}/account-lifecycle?status=PLACEHOLDER|MATERIALIZING|ACTIVE|CLOSED

The status values, the full lifecycle row shape and the filter semantics are in Account lifecycle status.

All of the above is available on the CLI as fgai org onboard-accounts, onboard-grant-operator, onboard-provision-service-user, onboard-close, onboard-purge, and account-lifecycle (see CLI reference).

Read without pulling the whole forest

No endpoint returns the entire org tree. Page and scope your reads:

  • GET /organizations/v1/{org}/ous?page=&size= → a page of OuSummary (size capped at 200). Each item carries child_count / has_children, so you can render a lazy tree without pre-fetching descendants. Add ?parentId=<ou> to fetch just one OU’s direct children.

  • GET /organizations/v1/{org}/accounts?page=&size= and GET /organizations/v1/{org}/ous/{ou}/accounts?page=&size= → paginated (the OU filter runs in the database).

  • GET /organizations/v1/{org}/tree?rootOuId=<ou>&depth=<n> → a subtree, depth-bounded. Walk the tree branch-by-branch rather than loading every node at once.

Backpressure & rate limits

  • Backpressure. When the provisioning backlog is over its high-water mark, POST /accounts/v1/provision and /provision/batch return 429 with a Retry-After header and a structured body (error.code = "provisioning-backpressure", with details.backlog / highWaterMark / retryAfterSeconds). Batches are rejected whole — never partially enqueued — so a retry is clean.

  • Rate limit. A single POST /accounts/v1/provision is capped per management account. Over the cap → 429, error.code = "rate-limited", Retry-After. Use /provision/batch for bulk.

Always honor Retry-After and retry with the same Idempotency-Key.

// 429 backpressure response
{
  "status": 429,
  "error": {
    "code": "provisioning-backpressure",
    "message": "Provisioning queue is over capacity (backlog … >= high-water mark …); retry after 30s.",
    "details": { "backlog": 5000, "highWaterMark": 5000, "retryAfterSeconds": 30 }
  }
}

Latency targets (SLOs)

Budget your client timeouts and retry backoff against these:

Operation

Target

OU create (single)

p95 ~200 ms

OU /batch (N ≤ 500)

p95 ~2 s

Paginated tree / list read (size ≤ 200)

p95 ~300 ms

Member provision — accept (202)

p95 ~400 ms

Member provision — complete (account live)

~7 s (async)

For very large onboards, plan for a sustained background import through the batch + relay path and pace submission to Retry-After — that is the supported path for high-volume onboarding.