Layers

POST /v1/organizations

Create a child organization — one isolated tenant per customer.

View as Markdown
POST/v1/organizations
Phase 1stableidempotent
Auth
Bearer
Scope
org:admin

Create a child organization under your (parent) org — one per end-customer. The child is an isolation boundary: its projects, credits, audit log, and keys are invisible to its siblings. The call is synchronous and idempotent via the Idempotency-Key header.

The new org starts in status: "active" with an empty, isolated credit wallet. Operate inside it with your parent key plus the X-Layers-Organization header. See the walkthrough.

The hierarchy is one level deep. You can only create children under a top-level (parent) org — a child org cannot itself create children. Attempting it returns 422 VALIDATION.

Headers
  • Idempotency-Key
    string (UUID)optional
    Optional but strongly recommended. Same key + same body replays the cached response (`409 IDEMPOTENCY_CONFLICT` on body mismatch). Without it, a retry after a connection error creates a duplicate org. See [Idempotency](/docs/api/operational/idempotency).
Body
  • name
    stringrequired
    Display name for the customer org. 1–128 chars.
  • metadata
    object<string,string>optional
    Opaque key/value pairs you control (your customer id, plan tier, etc.). String values only; keys ≤ 40 chars, values ≤ 500 chars, ≤ 50 keys (16 KB hard cap as a backstop). Round-tripped unchanged; Layers never reads or indexes it.
  • billingEmail
    stringoptional
    Optional contact email for this customer org. Informational.

Example request

curl https://api.layers.com/v1/organizations \
  -H "Authorization: Bearer $PARENT_KEY" \
  -H "Idempotency-Key: 4c1a2e92-7b18-4c4b-9b2a-d7a3f8b1c210" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Coffee",
    "metadata": { "externalId": "cust_12345", "plan": "growth" },
    "billingEmail": "ops@acme.example"
  }'
const org = await fetch("https://api.layers.com/v1/organizations", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.PARENT_KEY}`,
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    name: "Acme Coffee",
    metadata: { externalId: "cust_12345", plan: "growth" },
    billingEmail: "ops@acme.example",
  }),
}).then((r) => r.json());
import os, uuid, requests

org = requests.post(
    "https://api.layers.com/v1/organizations",
    headers={
        "Authorization": f"Bearer {os.environ['PARENT_KEY']}",
        "Idempotency-Key": str(uuid.uuid4()),
        "Content-Type": "application/json",
    },
    json={
        "name": "Acme Coffee",
        "metadata": {"externalId": "cust_12345", "plan": "growth"},
        "billingEmail": "ops@acme.example",
    },
).json()

Response

201Created
{
  "id": "org_d4e5f6a7-8b9c-4d0e-9f2a-3b4c5d6e7f80",
  "parentOrganizationId": "org_2481fa5c-a404-44ed-a561-565392499abc",
  "name": "Acme Coffee",
  "status": "active",
  "metadata": { "externalId": "cust_12345", "plan": "growth" },
  "billingEmail": "ops@acme.example",
  "archivedAt": null,
  "createdAt": "2026-06-01T14:30:00.000000+00:00",
  "updatedAt": "2026-06-01T14:30:00.000000+00:00"
}

Field notes

  • id is the child org's org_-prefixed id. Pass it as the :orgId path param on the other org endpoints, or as the X-Layers-Organization header to act inside it.
  • parentOrganizationId is your org — always set on a child.
  • status is always active on create. Move it with suspend / resume / archive.
  • archivedAt is always null on create; it carries a timestamp only once the org is archived.
  • metadata is null when you don't send it.

Errors

StatusCodeWhen
422VALIDATIONname empty or > 128 chars; metadata violates the string/≤40-key/≤500-value/≤50-keys bounds (or the 16 KB backstop); or the calling org is itself a child (hierarchy is one level deep).
401UNAUTHENTICATEDMissing or invalid key.
403FORBIDDEN_SCOPEKey lacks org:admin. The control plane is deny-by-default — the scope must be held explicitly.
409IDEMPOTENCY_CONFLICTSame Idempotency-Key replayed with a different body.
429RATE_LIMITEDWrite budget exhausted.

See also

On this page