POST /v1/admin/org/create
Provision a new org and mint its first API key (master-key auth; payment-worker only).
What this is
Creates a new organizations row and mints the first API key for that org. The plaintext key is returned exactly once in the response body; replays of the same Idempotency-Key return prefix-only with already_provisioned: true. Called by payment-worker on Stripe checkout.session.completed.
Authentication
X-API-Key: $MASTER_ADMIN_KEY_ORG_CREATE — the scope-specific Cloudflare Secret bound to this endpoint. NOT the per-org ck_live_* key. payment-worker holds this secret; admin-worker does not. The 8-character prefix concept does not apply (master keys are opaque).
Idempotency
Idempotency-Key header required (use Stripe event.id when called from a webhook handler). Missing or empty header → 400 with error: "VALIDATION_FAILED". Maximum length 256 chars. Replays return the cached envelope sanitized of plaintext keys; the original caller had ONE chance to capture the api_key field on first response. Composite primary key (endpoint, idempotency_key) prevents cross-endpoint replay (Codex P1-16).
⚠️ Plaintext key returned ONCE
The api_key field in the 201 response body is the only place the plaintext appears. It is NEVER persisted to D1, audit logs, or the idempotency cache. Replays return prefix + already_provisioned: true only — the original caller cannot recover the key after the first response. Lost keys must be re-minted via force-rotate.
Rate limiting
- Layer 1 (per-IP, 60 req / 60 s) is enforced at the Cloudflare edge.
- Layer 2 (per-key, 1000 req / 60 s) is bypassed for master-key admin endpoints — bulk operations (e.g., quota-poll cron sweeps) would self-DoS otherwise. No
X-RateLimit-Limitheader on the response.
Request
curl -X POST https://api.cohesionauth.com/v1/admin/org/create \
-H "X-API-Key: $MASTER_ADMIN_KEY_ORG_CREATE" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: example-org-create-key" \
-d '{
"org_id": "org_acme_2026_example",
"name": "Acme Corp",
"domain": "general",
"tier": "starter",
"allowed_origins": "https://acme.example.com"
}'
Response
{
"org_id": "org_acme_2026_example",
"api_key": "TEST_FIXTURE_NOT_A_REAL_KEY_ORG_CREATE",
"prefix": "TESTFIXT",
"created_at": "2026-05-02T20:00:00.000Z",
"warning": "This API key is shown ONCE. Store it securely. It cannot be retrieved later.",
"request_id": "req_01H...",
"timestamp": "2026-05-02T20:00:00.000Z"
}
Errors
| Status | Meaning |
|---|---|
400 | Invalid JSON body |
401 | Master-key missing or wrong scope (audit-logged as AUTH_FAIL_MASTER_KEY with detail=scope=ORG_CREATE) |
409 | org_id already exists |
422 | Validation failure (invalid domain enum, tier enum, missing allowed_origins, name out of range, malformed org_id) |
500 | Server misconfigured (pepper missing) or D1 transaction failure |
Next step
See the Postman collection “Admin (master-key)” folder for ready-to-run requests with the {{master_admin_key_org_create}} env variable wired up.