# Create organization

> Source: https://elaichi.ai/docs/api-reference/organizations/organization/createorganization/

`POST /organization`

Resource: **Organization** · API: **Organizations**

## Request body

- **`name`** _(string)_
- **`slug`** _(string)_
  Lowercase alphanumeric with hyphens. Generated from the name when omitted.
- **`region`** _(string)_
  Data location, fixed at creation: `eu`/`us` pin a hard Durable Object jurisdiction, `apac` is a placement hint. It cannot be changed afterwards.
  Allowed: `us`, `eu`, `apac`
- **`referral_code`** _(string)_
  Optional referral code, for tracking only. Whitespace is trimmed, a blank value is treated as absent, and it is stored upper-cased. Recorded once at creation; not returned by this API and not editable.
- **`signup_source`** _(object)_
  Optional attribution for where the signup came from, for tracking only. Every field is optional. Malformed input is dropped, never a 400: unknown keys, non-string or blank values, and a `referrer_host` that is not a hostname are ignored. Values are cut to 100 characters; `referrer_host` is reduced to a lowercase hostname (a full URL is accepted and stripped to its host). Recorded once at creation; not returned by this API and not editable.
  - **`utm_source`** _(string)_
  - **`utm_medium`** _(string)_
  - **`utm_campaign`** _(string)_
  - **`utm_term`** _(string)_
  - **`utm_content`** _(string)_
  - **`ref`** _(string)_
  - **`referrer_host`** _(string)_

## Response body

- **`id`** _(string)_
  Organization id (`org_…`) — the value for `X-Organization-Id`.
- **`name`** _(string)_
- **`slug`** _(string)_
  Immutable after creation.
- **`plan`** _(string)_
  The stored plan: `gold`, `black`, or `none` when locked. This is not the same as the effective entitlement — read `GET /organization/{id}/entitlements` before gating a feature on it.
- **`region`** _(string,null)_
  Data location fixed at creation: `us`/`eu` pin a hard jurisdiction, `apac` is a placement hint.
- **`trial_ends_at`** _(string,null)_
- **`trial_indefinite_at`** _(string,null)_
  When staff granted an indefinite trial, or null. Unlocked with no end date: no countdown, no expiry email, billing still reachable.
- **`settings`** _(object)_
  Free-form org settings. `PATCH /organization/{id}` REPLACES this object wholesale.
- **`logo`** _(string,null)_
  Public URL of the logo image, or null.
- **`can_delete`** _(boolean)_
  Whether the CALLER may delete this organization — the `org:delete` permission, which only the Org Owner role holds, and never for the platform root organization. Server-computed: branch on this rather than inspecting roles. Always `false` where the response has no member context to compute it from — the org switcher list `GET /organization` (one member-context lookup per row would be a per-row round trip), the staff console, and the invite-accept response. It never overstates: trust it when true, and read `GET /user/me` or `GET /organization/{id}` (both of which compute it) when you need it for a list row.
- **`can_manage`** _(boolean)_
  Whether the CALLER may change this organization's own settings — the `org:manage` permission that `PATCH /organization/{id}` requires, and the organization neither staff-suspended nor in its deletion grace period. Server-computed, and `false` wherever `can_delete` is for lack of a member context; it never overstates.
- **`allow_staff_impersonation`** _(boolean)_
  Whether Elaichi staff may impersonate this organization's members for support. `true` by default. When `false`, every request an impersonated session makes against this organization is refused with `403 staff_impersonation_blocked`, reads included, and the organization is left out of that session's `GET /user/me` and `GET /organization`. Changed with `PATCH /organization/{id}` (`org:manage`).
- **`mfa_required`** _(boolean)_
  Whether members must hold a second factor to act in this organization. `false` by default, for existing and new organizations alike. When `true`, a signed-in session that has not shown a second factor — no TOTP challenge at login, not a passkey sign-in, no factor enrolled since — is refused on every organization-scoped route, reads included, with `403 mfa_setup_required` (`error.details.has_second_factor` says whether the person already has one and only needs to sign in with it). Exempt: org API tokens, Elaichi staff impersonation sessions, and a session signed in through THIS organization's own SSO connection. MCP connections made before it was switched on, or from a session without a factor, are refused too until the person connects again. Turning it OFF only lifts that requirement: a member who set up an authenticator app of their own is still challenged for it at every non-passkey sign-in, because the factor belongs to the member and no organization setting can switch it off. Changed with `PATCH /organization/{id}` (`org:manage`, interactive session only).
- **`can_require_mfa`** _(boolean)_
  Whether the CALLER may turn `mfa_required` ON right now: `can_manage`, from an interactive session that would itself pass the check it is about to create. `false` — with the reason in `can_require_mfa_reason` — when switching it on would lock the caller out of this organization. The PATCH refuses that regardless with `409 mfa_setup_required`. Turning it OFF is not governed by this field; it needs a step-up.
- **`can_require_mfa_reason`** _(string,null)_
  A sentence saying why `can_require_mfa` is `false` for a caller who can otherwise manage the organization. `null` otherwise.
- **`deletion_scheduled_at`** _(string,null)_
  Set when a deletion has been scheduled (`DELETE /organization/{id}`). The organization is read-only until `purge_after`. Null otherwise.
- **`purge_after`** _(string,null)_
  When a scheduled deletion becomes permanent — 30 days after `deletion_scheduled_at`. Null unless scheduled.
- **`can_restore`** _(boolean)_
  Whether the CALLER may cancel a scheduled deletion: the `org:manage` permission, and only while `purge_after` is still ahead. Server-computed, like `can_delete`, and `false` for the same reasons — no member context (the org switcher list, the staff console) — plus once the window has closed. Read `purge_after` to tell "too late" from "not yours to undo".
- **`can_authorize_apps`** _(boolean)_
  Whether the CALLER may connect an app (MCP OAuth consent) to this organization right now: `false` while it is suspended, scheduled for deletion, or has no active plan. Server-computed by the same gate `POST /oauth/authorize-request/{id}/approve` calls; present only on member-scoped responses. `authorize_apps_blocked_reason` says which.
- **`authorize_apps_blocked_reason`** _(string,null)_
  Why `can_authorize_apps` is `false`, in the gate's own order: `blocked`, then `pending_deletion` (even when the subscription is also gone, since a soft delete cancels it), then `no_plan`. `null` when connecting is open.
  Allowed: `blocked`, `pending_deletion`, `no_plan`, `null`
- **`created_at`** _(string)_
- **`updated_at`** _(string)_

## Code examples

### curl

```bash
curl -X POST 'https://api.elaichi.ai/organization' \
  -H 'Authorization: Bearer $ELAICHI_API_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"name":"your_name","slug":"your_slug","region":"us","referral_code":"your_referral_code","signup_source":{}}'
```

### JavaScript

```javascript
const body = {
  "name": "your_name",
  "slug": "your_slug",
  "region": "us",
  "referral_code": "your_referral_code",
  "signup_source": {}
};

const response = await fetch('https://api.elaichi.ai/organization', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ' + process.env.ELAICHI_API_TOKEN,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(body),
});

const data = await response.json();
console.log(data);
```

### Python

```python
import os
import requests

url = "https://api.elaichi.ai/organization"
headers = {
    "Authorization": f"Bearer {os.environ['ELAICHI_API_TOKEN']}",
    "Content-Type": "application/json",
}
payload = {
    "name": "your_name",
    "slug": "your_slug",
    "region": "us",
    "referral_code": "your_referral_code",
    "signup_source": {}
}

response = requests.post(url, headers=headers, json=payload)
print(response.json())
```
