Organizations Federation API
HTTP and SDK reference for Organizations Service
Organizations Federation API
This reference covers Organizations Actions for clients and Bureau. See Integrate Organizations into Bureau for product authorization.
Install
import { Federation } from "@downcity/bureau";
import { OrganizationsService } from "@downcity/services";
const federation = new Federation({ database });
federation.use(new OrganizationsService({
max_organizations_per_user: 3,
}));| Option | Required | Meaning |
|---|---|---|
max_organizations_per_user | yes | Maximum active Organizations a user may own across Federation |
The service supports better-sqlite3, PostgreSQL, and Cloudflare D1 through context.transaction().
Endpoint and authentication
{federation_origin}/v1/organizations/{action}Every Action requires a Federation user_token:
Authorization: Bearer ub_<JWT>Federation reads user_id and bureau_id from the verified Token. GET Actions use query parameters; POST Actions use JSON.
Types
type Organization = {
organization_id: string;
name: string;
scope_type: "federation" | "bureau";
scope_bureau_id: string;
state: "active" | "archived";
created_by: string;
created_at: string;
updated_at: string;
archived_at: string;
};
type Membership = {
membership_id: string;
organization_id: string;
user_id: string;
role: "owner" | "admin" | "member";
state: "active" | "removed";
created_at: string;
updated_at: string;
removed_at: string;
removed_by: string;
};
type JoinRequest = {
request_id: string;
organization_id: string;
user_id: string;
state: "pending" | "approved" | "rejected" | "canceled";
requested_at: string;
decided_at: string;
decided_by: string;
};For scope_type = federation, scope_bureau_id is empty. For scope_type = bureau, Federation derives scope_bureau_id from the creator's verified user_token.bureau_id.
Permissions
| Operation | Owner | Admin | Member |
|---|---|---|---|
| Read Organization and members | yes | yes | yes |
| Rename | yes | yes | no |
| Decide Join Requests | yes | yes | no |
| Remove Member | yes | yes | no |
| Remove Admin | yes | no | no |
| Assign or revoke Admin | yes | no | no |
| Transfer ownership | yes | no | no |
| Archive | yes | no | no |
| Leave | transfer first | yes | yes |
Actions
| Method | Action | Authorization | Purpose |
|---|---|---|---|
GET | my | User | List visible Organizations for the caller |
GET | get | Member | Read Organization and current Membership |
POST | create | User | Create an Organization as Owner |
POST | update | Owner/Admin | Rename |
POST | archive | Owner | Archive terminally |
GET | membership/get | Member | Read current Membership |
GET | members/list | Member | List active Memberships |
POST | members/role | Owner | Set Admin or Member |
POST | members/remove | Owner/Admin | Remove a manageable member |
POST | members/leave | Admin/Member | Leave |
POST | owner/transfer | Owner | Transfer the unique Owner |
POST | join-requests/create | User | Apply to join |
POST | join-requests/cancel | Applicant | Cancel a pending request |
GET | join-requests/list | Owner/Admin | List pending requests |
POST | join-requests/decide | Owner/Admin | Approve or reject |
Organization Actions
GET my
GET /v1/organizations/my
GET /v1/organizations/my?include_archived=trueThe default response contains active Organizations only. Each item includes role, membership_id, and membership_state. Federation Organizations are visible across Token Cities; Bureau Organizations require a matching Token Bureau.
GET get
GET /v1/organizations/get?organization_id=org_01J...Requires an active Membership and returns { organization, membership }. Members may read an archived Organization but cannot mutate it.
POST create
Federation-wide:
{ "name": "Genesis Research", "scope_type": "federation" }Current Bureau:
{ "name": "Genesis Research", "scope_type": "bureau" }Returns { organization, membership }, with the caller as the unique Owner. The trimmed name must contain 1–120 characters.
POST update
{ "organization_id": "org_01J...", "name": "New Name" }Owner/Admin only. Scope is immutable after creation.
POST archive
{ "organization_id": "org_01J..." }Owner only and irreversible. Archive stops governance writes and releases the Owner quota. Membership and Join Request history remains.
Membership Actions
GET membership/get
GET /v1/organizations/membership/get?organization_id=org_01J...Returns { organization, membership }. Bureau may forward the same user Token to this endpoint. Removed users receive 403 NOT_AN_ORGANIZATION_MEMBER.
GET members/list
GET /v1/organizations/members/list?organization_id=org_01J...Returns active Memberships in { "items": Membership[] }.
POST members/role
{
"organization_id": "org_01J...",
"membership_id": "mem_01J...",
"role": "admin"
}Owner only. role accepts admin or member. Use owner/transfer for ownership.
POST members/remove
{ "organization_id": "org_01J...", "membership_id": "mem_01J..." }Owners may remove Admins or Members. Admins may remove Members. The unique Owner cannot be removed.
POST members/leave
{ "organization_id": "org_01J..." }Admins and Members may leave. The Owner transfers ownership first.
POST owner/transfer
{ "organization_id": "org_01J...", "membership_id": "mem_new_owner" }The target must have an active Membership and an available Owner quota. The previous Owner becomes Admin.
Join Request Actions
POST join-requests/create
{ "organization_id": "org_01J..." }Users apply with a known ID. A duplicate returns the same pending request. Existing members receive { state: "joined", organization, membership }.
POST join-requests/cancel
{ "request_id": "join_01J..." }The applicant may cancel their pending request.
GET join-requests/list
GET /v1/organizations/join-requests/list?organization_id=org_01J...Owner/Admin only. Returns pending requests.
POST join-requests/decide
{ "request_id": "join_01J...", "decision": "approved" }decision accepts approved or rejected. Approval creates a new Member Membership in the same transaction.
Errors
| HTTP | Code | Meaning |
|---|---|---|
400 | ORGANIZATION_INPUT_INVALID | Invalid name, ID, role, or decision |
400 | ORGANIZATION_SCOPE_INVALID | Invalid scope_type |
401 | AUTH_REQUIRED | Missing valid user identity |
403 | ORGANIZATION_BUREAU_MISMATCH | Token Bureau cannot access the Bureau Organization |
403 | NOT_AN_ORGANIZATION_MEMBER | No active Membership |
403 | ORGANIZATION_ROLE_DENIED | Governance role denies the operation |
404 | ORGANIZATION_NOT_FOUND | Organization does not exist |
404 | ORGANIZATION_MEMBERSHIP_NOT_FOUND | Membership is absent or inactive |
404 | JOIN_REQUEST_NOT_FOUND | Join Request is absent or unavailable |
409 | ORGANIZATION_LIMIT_REACHED | Owner quota is full |
409 | OWNER_TRANSFER_REQUIRED | Transfer ownership first |
410 | ORGANIZATION_ARCHIVED | Organization is archived |
Branch on error.code, not the message text.