Organization
Covers 14 API operations in the orgs category.
| Method | Path | Description |
|---|---|---|
| GET | /v1/orgs | — |
| POST | /v1/orgs | Create an org |
| PATCH | /v1/orgs/{orgSlug} | Rename an org's display name (runlot org rename) |
| DELETE | /v1/orgs/{orgSlug} | Delete an org (runlot org delete) |
| GET | /v1/orgs/{orgSlug}/notifications | Recent notifications |
| GET | /v1/orgs/{orgSlug}/members | List org members (runlot org members) |
| POST | /v1/orgs/{orgSlug}/members | Add an existing user as a member (for ops and automation) |
| GET | /v1/orgs/{orgSlug}/invites | Pending invites (runlot member invites) |
| POST | /v1/orgs/{orgSlug}/invites | Invite by email (runlot member invite) |
| DELETE | /v1/orgs/{orgSlug}/invites/{inviteId} | Revoke an invite (runlot member uninvite) |
| GET | /v1/invites/{token} | Preview an invite (no authentication) |
| POST | /v1/invites/{token}/accept | Accept an invite |
| PUT | /v1/orgs/{orgSlug}/members/{userId} | Change a role (runlot org set-role) |
| DELETE | /v1/orgs/{orgSlug}/members/{userId} | Remove a member or leave on your own (runlot member rm, runlot member leave) |
GET /v1/orgs
operationId listOrgs
| Status code | Description | Response body |
|---|---|---|
| 200 | The organizations you belong to | object |
| 401 | — | — |
POST /v1/orgs
The creator becomes admin. A personal account is simply a one-person organization,
so there is no separate path for it.
operationId createOrg
Request body: application/json · object
| Status code | Description | Response body |
|---|---|---|
| 201 | The created organization | Org |
| 401 | — | — |
| 409 | — | — |
PATCH /v1/orgs/{orgSlug}
Requires admin or higher. Audited as org.rename.
Only the display name can change here. The slug is part of your addresses —
<project>.<slug>.runlot.app, the certificate SAN, the clone URL, and the "org"
field in the runlot.json committed to your repository — and cannot be renamed.
operationId updateOrg
Request body: application/json · object
| Status code | Description | Response body |
|---|---|---|
| 200 | The updated org | OrgMembership |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
DELETE /v1/orgs/{orgSlug}
Requires admin or higher. This cannot be undone.
The confirm field must carry the exact org slug (leading and trailing whitespace is
trimmed).
Deletion has two steps, and you only wait for the first. The response marks the org
deleted: it disappears from the org list, every org-scoped route answers 404, pending
invites stop working, and the slug is free to reuse immediately. The data is erased
after the retention period, a month by default. purgeAfter in the response is that
moment — read it from the response rather than computing it yourself, since the
retention period can change.
Deletion succeeds only when the org is empty. If projects, domains, repositories,
or DNS zones remain, the response is 409 org_not_empty, and the envelope's
details.holdings lists counts and a few names per kind. Projects you already deleted
do not count — they disappear on their own retention clock, before the org's.
Two other rejections: a personal org returns personal_org (the org your first login
created, which cannot be deleted), and a live paid subscription returns
active_subscription — cancel the subscription first.
The org.delete audit event outlives the org, and keeps the slug in target.
operationId deleteOrg
Request body: application/json · object
| Status code | Description | Response body |
|---|---|---|
| 202 | Marked deleted — gone from every surface now, data erased after purgeAfter | OrgDeleted |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
| 409 | Not empty (org_not_empty) · personal org (personal_org) · active subscription (active_subscription) | Error |
GET /v1/orgs/{orgSlug}/notifications
Limit, backup, and restore events for the org, newest first.
If sentAt is set, an email was sent as well. Rows without it are either
still queued or have no address to send to — an account signed in with a
private GitHub email has no email on file, and this list is its only
channel.
Requires viewer or higher.
operationId listNotifications
| Parameter | Location | Required | Type | Description |
|---|---|---|---|---|
limit | query | No | integer | — |
| Status code | Description | Response body |
|---|---|---|
| 200 | List notifications | object |
| 403 | — | — |
| 404 | — | — |
GET /v1/orgs/{orgSlug}/members
Requires member or higher.
operationId listMembers
| Status code | Description | Response body |
|---|---|---|
| 200 | Member list | object |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/members
This isn't an invite. The user must already have signed in at least once,
and is identified by provider + subject; if no such user exists, this
returns 404 user_not_found. If they are already a member, it returns 409
already_member — use PUT to change a role.
To invite someone by email, use POST …/invites instead. This endpoint is for
automation that already knows the provider's subject id.
Requires admin or higher. Recorded in the audit log as member.add.
operationId addMember
Request body: application/json · object
| Status code | Description | Response body |
|---|---|---|
| 201 | Member added | Member |
| 400 | — | — |
| 403 | — | — |
| 404 | The org does not exist, or the user does not exist (user_not_found) | Error |
| 409 | Already a member (already_member) | Error |
GET /v1/orgs/{orgSlug}/invites
Requires admin or higher. The invite list holds the email addresses of people who are not yet in the org, so it needs a higher role than the member list.
Accepted or revoked invites do not appear — the audit log is where that history lives. Expired invites do appear, so an admin can resend them.
operationId listInvites
| Status code | Description | Response body |
|---|---|---|
| 200 | List invites | object |
| 403 | — | — |
| 404 | — | — |
POST /v1/orgs/{orgSlug}/invites
Requires admin or higher. The email is queued rather than sent inline — an
empty sentAt means it has not gone out yet.
Calling this again with the same address resends the invite: the existing invite is updated with a new token and expiry (and the role may change), and this returns 200. A brand-new invite returns 201.
An address that is already a member returns 409 already_member. Too many
pending invites returns 409 too_many_invites.
Invites last seven days, are single-use, and can be revoked at any time.
Recorded in the audit log as member.invite.
operationId createInvite
Request body: application/json · object
| Status code | Description | Response body |
|---|---|---|
| 200 | Resent an existing invite | Invite |
| 201 | New invite | Invite |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
| 409 | Already a member (already_member) or too many pending invites (too_many_invites) | Error |
DELETE /v1/orgs/{orgSlug}/invites/{inviteId}
Requires admin or higher. An email that has not gone out yet will not be
sent — revoking also removes it from the send queue. An invite that is
already accepted or revoked returns 404. Recorded in the audit log as
member.uninvite.
operationId revokeInvite
| Status code | Description | Response body |
|---|---|---|
| 204 | Revoked | — |
| 403 | — | — |
| 404 | — | — |
GET /v1/invites/{token}
No authentication required. Whoever receives the invite link has not signed in yet and needs to see what the invite is for. The token is a 32-byte random value, so it cannot be guessed, and opening it reveals only the org name.
The invite state is a value, not an error (state), so a client can tell an
expired link apart from a wrong one.
An unknown token returns 404 — the same answer as a token revoked long ago.
operationId getInvite
| Status code | Description | Response body |
|---|---|---|
| 200 | Invite | InviteView |
| 404 | — | — |
POST /v1/invites/{token}/accept
Requires login. The signed-in account's email does not have to match the invited address — an account signed in with a private GitHub email has no email on file at all. Instead, invites live for seven days, are single-use, and can be revoked at any time, and the audit record keeps both the invited address and the accepting account.
If the person is already a member, their role does not change
(alreadyMember: true).
Recorded in the audit log as member.accept.
operationId acceptInvite
| Status code | Description | Response body |
|---|---|---|
| 200 | Accepted | object |
| 401 | — | — |
| 404 | — | — |
| 409 | Already accepted (invite_used), revoked (invite_revoked), or expired (invite_expired) | Error |
PUT /v1/orgs/{orgSlug}/members/{userId}
Requires admin or higher. You cannot demote the last admin (409 last_admin),
including yourself — an org must always have someone able to manage it. Recorded in the
audit log as member.role.
operationId setMemberRole
Request body: application/json · object
| Status code | Description | Response body |
|---|---|---|
| 200 | The member that changed | Member |
| 400 | — | — |
| 403 | — | — |
| 404 | — | — |
| 409 | This is the last admin (last_admin) | Error |
DELETE /v1/orgs/{orgSlug}/members/{userId}
Removing someone else requires admin or higher; removing yourself is open to
anyone — if userId is your own, this is leaving the org.
The last admin cannot be removed and cannot leave (409 last_admin). The audit log
records removal as member.remove and leaving as member.leave.
operationId removeMember
| Status code | Description | Response body |
|---|---|---|
| 204 | Removed | — |
| 403 | — | — |
| 404 | — | — |
| 409 | This is the last admin (last_admin) | Error |