Organisations
Developer·5 minutes to read
What it does
Two different credentials meet on this page: an organisation key reads and renames the one organisation it belongs to, and the operator's platform key creates organisations, lists the ones it created and asks for one to be deleted. Everyone gets a personal organisation on sign-up; a team organisation has roles.
Authentication
GET /organisation needs an organisation key of scope read or wider. PATCH /organisation needs admin. The four /organisations routes are the platform class and reach nothing else; an organisation key on them, or a platform key on anything else, is refused exactly as no credential is. See Authentication and keys.
Request
GET /organisation
<!-- generated:begin GET /organisation --> <!-- generated:end GET /organisation -->PATCH /organisation
<!-- generated:begin PATCH /organisation --> <!-- generated:end PATCH /organisation -->POST /organisations
<!-- generated:begin POST /organisations --> <!-- generated:end POST /organisations -->GET /organisations
<!-- generated:begin GET /organisations --> <!-- generated:end GET /organisations -->GET /organisations/{id}
<!-- generated:begin GET /organisations/{id} --> <!-- generated:end GET /organisations/{id} -->DELETE /organisations/{id}
<!-- generated:begin DELETE /organisations/{id} --> <!-- generated:end DELETE /organisations/{id} -->A name is 1 to 80 characters. POST /organisations takes the new owner's e-mail and, optionally, a first key to mint for them; when the e-mail already has a user, that person becomes the owner and no second identity is created. DELETE /organisations/{id} does not remove anything: it records a deletion request with the grace period the product gives every organisation, it is written as the organisation's own live owner, and calling it twice returns the same open request rather than opening a second one. A platform key only ever sees the organisations it created; another key's organisation reads as not_found.
PUT /organisation-memberships
<!-- generated:begin PUT /organisation-memberships --> <!-- generated:end PUT /organisation-memberships -->The cross-organisation membership call (S-98210, hosted only), with a platform key: for up to 500 people, which of the linked team organisations this key created each one belongs to, and with which role (admin or member, default member). People are named by e-mail; an address that already has a user joins with no new identity. Each row is the whole set for that person — teams it names are joined or have their role moved, this key's teams it leaves out are left — and the answer has one result per row, in order, with a change for each team: added, role_changed, removed, unchanged or kept_owner. An owner is never moved: a team the row leaves out reads kept_owner. A team this key did not create is that row's not_found, and a team with an unpaid invoice takes no new seat (payment_required on that row), and a team whose plan is set to cancel takes no new member (forbidden on that row, naming those teams; S-98211); a failed row changes nothing and the other rows still land. More than 500 rows, or one address twice, is refused as a whole with invalid_request. Each join is audited in the team's own log as org.member_added_via_platform, each role change and removal as org.member_role_changed and org.member_removed with details.via = "platform"; a removed person gets the usual removal e-mail. The home edition answers 405 not_supported.
curl -sS "$BOTSEON_API_URL/api/v1/organisation" \
-H "authorization: Bearer $BOTSEON_API_KEY" \
-H "accept: application/json"
const res = await fetch(`${process.env.BOTSEON_API_URL}/api/v1/organisation`, {
headers: {
authorization: `Bearer ${process.env.BOTSEON_API_KEY}`,
accept: 'application/json',
},
});
if (!res.ok) throw new Error(`organisation: ${res.status}`);
const org = (await res.json()) as {
id: string;
name: string;
kind: 'personal' | 'team';
createdAt: string;
deletionRequestedAt: string | null;
};
console.log(org.name, org.kind, org.deletionRequestedAt ?? 'no deletion requested');
Response
{
"id": "2b7c9d10-3e4f-4a5b-8c9d-0e1f2a3b4c5d",
"name": "Nordic Supplies",
"kind": "team",
"createdAt": "2026-03-04T09:12:00.000Z",
"deletionRequestedAt": null
}
kind is personal or team. A personal organisation is the one every person gets on sign-up; a team organisation is the one with roles. deletionRequestedAt is set once a deletion has been asked for and is null otherwise.
Errors
| Status | Code | When | What to do |
|---|---|---|---|
| 401 | unauthenticated | The credential is missing, refused, of the wrong class, or below the scope the route needs. | Check the key's class and scope. |
| 400 | invalid_request | The name is outside 1 to 80 characters, the owner e-mail is not an address, or the POST carries no Idempotency-Key. | Read details. |
| 403 | forbidden | PATCH /organisation on a personal organisation. | Use a team organisation. |
| 404 | not_found | The organisation is not one this platform key created. | List the key's own organisations first. |
| 409 | conflict | The organisation has no live owner or admin to write a deletion request as. | Restore a member to the organisation, or use the app. |
| 429 | rate_limited | A bucket is full. | Wait for Retry-After. |
| 503 | unavailable | The service could not answer. | Retry with backoff. |
Rate limits
GET and PATCH /organisation are counted against two one-minute buckets: 300 requests per key and 1 000 per organisation. The platform-key routes are counted against one: 60 requests per platform key. A full bucket answers rate_limited with Retry-After: 60; wait that many seconds and retry. Failed authentication is counted separately, against the caller's address, so a working key's allowance is never spent by somebody else's bad credential. See Envelopes, errors, idempotency and paging.
Last verified against build c0f77aa.