Organizations
Your org is the top-level account. Run customers as flat projects, or give each one an isolated sub-organization with the parent as the control plane.
Your Layers organization is the root of everything you do with the Partner API — it holds your API keys, it's the billing principal for credits and ads spend, and every project you create nests beneath it (directly, or under a child org). Your org is provisioned when you're onboarded as a partner; from that point forward every request resolves back to it.
There are two ways to model your customers, and you can mix them:
- Flat — every customer is a project directly under your org. Simple, and the right shape for a handful of customers. Nothing here has changed.
- Sub-organizations — each customer gets its own child organization with an isolated wallet, audit trail, and dedicated API keys. Your top-level org becomes the control plane that creates, funds, suspends, and offboards them. This is the Stripe Connect / Twilio Subaccounts shape, and the right fit once you're managing customers at scale.
Sub-organizations are additive and non-breaking. If the flat model works for you, keep using it — nothing about it changes. Adopt child orgs per customer when you want hard isolation and clean offboarding.
The flat model
One Layers org hosts many projects; each project represents a single end-customer:
your org ──┬── project A (customer "acme-prod")
├── project B (customer "wayne-labs")
└── project C (customer "stark-industries")A single org-scoped key can touch any project inside the org. You keep customers isolated by path-scoping — every project-scoped route lives under /v1/projects/:projectId/... and the server returns 404 NOT_FOUND for any project that doesn't belong to your org (we don't leak existence with a 403).
For belt-and-suspenders verification, read the project first via GET /v1/projects/:projectId and assert customerExternalId matches what your code expects before issuing follow-up calls.
The sub-organization model
A parent org (yours) holds child orgs (one per customer). Each child is an isolation boundary: its projects, credits, audit log, and API keys are invisible to its siblings at the data layer — not by convention, but because a child key (or a parent acting on a child) can only ever resolve rows inside that one child.
your org (parent · control plane)
│ credit card · master wallet · org:admin key
├── Customer A (org_cust_a · child) wallet · audit · projects
├── Customer B (org_cust_b · child) wallet · audit · projects
└── Customer C (org_cust_c · child) wallet · audit · projectsYou create and manage children with the organization endpoints, all gated by the org:admin scope. The hierarchy is exactly one level deep — a child cannot itself have children (the API rejects it). Offboarding a customer is a single DELETE /v1/organizations/:orgId that archives the child and revokes its keys atomically.
Lifecycle
Every child org has a status:
POST /organizations
│
▼
POST /:id/suspend ┌─────────┐
◄───────────────── │ active │ ──────────────┐
┌─────────┐ ─────► │ │ │ DELETE /:id
│suspended│ ◄────── └─────────┘ │
└─────────┘ POST /:id/resume ▼
│ ┌──────────┐
└──────────── DELETE /:id ───────►│ archived │ (terminal)
└──────────┘- active — normal operation.
- suspended — kill switch. The child's own keys get
503 KILL_SWITCH; no generation, no spend. Reversible with resume. The parent can still read and manage a suspended child. - archived — offboarded. Terminal: projects/keys are revoked and the org can't be restored. Returning customers get a fresh child org.
Metadata
Every child org carries an opaque metadata object that's yours to own — stash your internal customer id, plan tier, Salesforce account id, whatever you reconcile against. Layers never reads or indexes it. It's Stripe-shaped: string→string pairs, each key ≤ 40 chars, each value ≤ 500 chars, ≤ 50 keys (a 16 KB cap backstops the whole object); anything outside those bounds is rejected with 422 VALIDATION. It round-trips unchanged on every read and is editable via PATCH, which merges updates key-by-key (Stripe-style) — send only the keys you're changing, unset one by sending it with an empty string (""), or clear all with metadata: null. The same bounds and the same merge semantics apply to the metadata on a credit allocation.
Operating inside a child org
Today there is one way to act inside a child: your parent org:admin key plus the X-Layers-Organization header naming the child. Every existing endpoint works unchanged — the header transparently scopes the call to that child:
# Create a project inside Customer A's org, using your parent key
curl https://api.layers.com/v1/projects \
-H "Authorization: Bearer $PARENT_KEY" \
-H "X-Layers-Organization: org_cust_a" \
-H "Content-Type: application/json" \
-d '{ "name": "Acme Main", "timezone": "America/New_York" }'The header is honored only for keys that hold org:admin, and only for a child that is a direct child of the calling org — anything else returns 404. See Authentication → acting on behalf of a child and the walkthrough guide.
Dedicated child API keys
The header pattern is for when your backend drives a child with your parent key. When you'd rather hand a customer (or a per-customer service) its own credential, mint a dedicated child API key: a least-privilege key scoped to a single child org, so that integration never sees the parent or its siblings. Your parent org:admin key mints, lists, rotates, and revokes them.
A child key can only carry scopes the parent holds, and can never hold org:admin — the control plane stays with the parent. Rotation is zero-downtime: a rotate mints a fresh secret while the old one keeps working for a 24-hour grace window. See API keys → child keys for the full model.
What the org owns
In the sub-org model, some things live on the parent (the control plane) and some are per-child:
| Resource | Scope | Notes |
|---|---|---|
| Credit card / master wallet | Parent only | The parent is the billing principal. Children never put a card on file. |
org:admin capability | Parent only | Creating, suspending, and archiving children. |
| Projects | Per-child | One per end-customer's workload, under that child. |
| Credit wallet | Per-child | Each child has its own isolated balance, funded by the parent via allocate. Generation debits the child's wallet; siblings are unaffected. |
| Audit log | Per-child | Every write is stamped with (api_key_id, org_id, project_id). |
| API keys | Per-org | Scoped to one org, not to a project. Mint child-scoped keys for each customer with your parent org:admin key. |
Funding and governing child wallets
A child starts with an empty wallet. You allocate credits to it from your parent balance with POST /v1/organizations/:orgId/credits/allocate, read its balance with GET …/credits, and reclaim the unspent remainder automatically when you archive it. Beyond manual funding, a parent can arrange a per-child monthly spend cap and auto-refill (top the child up from the parent when it runs low) — see the Credits concept for the full model of allocation, caps, auto-refill, and balance vs available.
For existing single-org partners, nothing here changes: your org remains the billing principal and your projects hang directly off it. If you later adopt sub-orgs, you can move existing flat projects under child orgs in one call — see Migrating existing projects.
Migrating existing projects into children
If you started flat and want to adopt the sub-org model, you don't have to recreate anything. POST /v1/organizations/migrate takes a mapping of { projectId: childOrgName } and, in one atomic call, creates the named child orgs and moves the mapped projects under them. Each distinct childOrgName becomes one new child; the response reports projectsMoved, childrenCreated, and the children it created (with their new org_… ids and the projects now under each).
A few rules worth knowing before you run it:
- All-or-nothing. If any mapped project has an in-flight execution, the whole migration is rejected with
409 CONFLICTand nothing moves — retry once those workflows finish. A project that isn't yours returns404 NOT_FOUND(anti-enumeration — we don't confirm which). - History stays with the parent. Past credit events for a migrated project remain on the parent's ledger; only the project's future spend bills to its new child wallet.
- 30-day read grace. For 30 days after the move, the parent can still read a migrated project, so dashboards and reconciliation don't break the instant you migrate.
Introspecting your org
The first call any client makes is GET /v1/whoami. It resolves your key to the org it belongs to, echoes your organization name, and names your rate-limit tier. A successful 200 is itself the "key is live and the org is in good standing" signal — if access has been suspended every endpoint, including this one, returns 503 KILL_SWITCH with a request id you can quote to support.