# Organizations

Every user has a personal organization, created at first sign-in. It is an ordinary organization with ordinary membership rules, except that it cannot be deleted. Organizations are called publishers in some error messages.

## The organization object

| Field                      | Type                       | Meaning                         |
| -------------------------- | -------------------------- | ------------------------------- |
| `id`                       | UUID                       |                                 |
| `slug`                     | string, 2 to 48 characters | Lowercase words joined by `-`.  |
| `name`                     | string, 1 to 80 characters |                                 |
| `description`              | string or null             | Up to 500 characters.           |
| `website`                  | URL or null                |                                 |
| `logo_url`                 | URL or null                |                                 |
| `accent`                   | `#rrggbb` or null          |                                 |
| `verified`                 | boolean                    |                                 |
| `personal`                 | boolean                    | A user's personal organization. |
| `created_at`, `updated_at` | date-time                  |                                 |

## List organizations

`GET /v1/organizations`

Open. With `mine=true`, session only.

| Query  | Type   | Meaning                                                                         |
| ------ | ------ | ------------------------------------------------------------------------------- |
| `slug` | slug   | Only this organization.                                                         |
| `mine` | `true` | The caller's organizations, each with a `role` (`owner`, `admin`, or `member`). |

Response `200` `{ "organizations": [ ... ] }`.

## Create an organization

`POST /v1/organizations`

Session only. The creator becomes owner.

| Field         | Type                        | Meaning   |
| ------------- | --------------------------- | --------- |
| `slug`        | slug, 2 to 48 characters    | Required. |
| `name`        | string, 1 to 80 characters  | Required. |
| `description` | string, 1 to 500 characters |           |
| `website`     | URL                         |           |
| `accent`      | `#rrggbb`                   |           |

Response `201`: the organization.

## Get an organization

`GET /v1/organizations/:id`

Open. Response `200`: the organization.

## Update an organization

`PATCH /v1/organizations/:id`

Key `org:admin` with manager creator, or session: manager.

| Field         | Type                                 |
| ------------- | ------------------------------------ |
| `name`        | string, 1 to 80 characters           |
| `description` | string up to 500 characters, or null |
| `website`     | URL or null                          |
| `accent`      | `#rrggbb` or null                    |

Response `200`: the organization.

## Delete an organization

`DELETE /v1/organizations/:id`

Session: owner only, from the dashboard. A key can never delete its own organization. Deletes the organization's members, keys, Workbenches, versions, submissions, and images, and is audited. 409 for a personal organization. Response `200` `{ "deleted": true }`.

## Members

### List members

`GET /v1/memberships?organization_id=<uuid>`

Session: member. Response `200`:

```json
{
    "members": [
        {
            "id": "uuid",
            "name": "Ada Lovelace",
            "email": "ada@example.com",
            "image": null,
            "role": "owner",
            "current": true,
            "created_at": "..."
        }
    ]
}
```

`current` marks the caller.

### Remove a member

`DELETE /v1/memberships/:id`

Session: member. Response `200` `{ "deleted": true }`. Removing a member, or a member leaving, revokes every key that person created in the organization.

## Invitations

### List invitations

`GET /v1/invitations?organization_id=<uuid>`

Session: member. Response `200` `{ "invitations": [ { "id", "email", "status", "expires_at", "created_at" } ] }`. `status` is `pending`, `accepted`, `revoked`, or `expired`.

### Invite

`POST /v1/invitations`

Session: member.

| Field             | Type          |
| ----------------- | ------------- |
| `organization_id` | UUID          |
| `email`           | email address |

Response `201`:

| Field            | Meaning                         |
| ---------------- | ------------------------------- |
| `invitation`     | The invitation.                 |
| `delivery`       | `sent`, `preview`, or `failed`. |
| `acceptance_url` | The link to accept, or null.    |

Refused with 402 `plan_limit` when members and live pending invitations are at the plan's member cap.

### Revoke an invitation

`DELETE /v1/invitations/:id`

Session: member. Response `200` `{ "deleted": true }`.

### Accept an invitation

`POST /v1/acceptances` with `{ "token": "..." }` (32 to 256 characters)

Session only. The signed-in email must match the invited email. Response `201`: the organization. Refused when the organization's joined members are at the cap.

## Logos

`:id` is the organization's ID.

| Method | Path            | Auth                                                      | Result                                                                                       |
| ------ | --------------- | --------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| GET    | `/v1/logos/:id` | Open                                                      | The logo bytes. Supports `If-None-Match`.                                                    |
| PUT    | `/v1/logos/:id` | Key `org:admin` with manager creator, or session: manager | Upload a logo: the raw image as the body, with its `content-type`. Returns the organization. |
| DELETE | `/v1/logos/:id` | Key `org:admin` with manager creator, or session: manager | Remove the logo. Returns the organization.                                                   |

## Analytics

`POST /v1/analytics` with `{ "organization_id": "uuid" }`

Key `catalog:read`, or session: member. Save and run counts for the organization's Workbenches, as the CLI reports them:

```json
{
    "totals": { "saves": 12, "runs": 40, "workbenches": 3 },
    "series": [{ "day": "2026-10-28", "saves": 1, "runs": 4 }]
}
```

`series` holds up to 30 days.
