Federation & Service

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,
}));
OptionRequiredMeaning
max_organizations_per_useryesMaximum 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

OperationOwnerAdminMember
Read Organization and membersyesyesyes
Renameyesyesno
Decide Join Requestsyesyesno
Remove Memberyesyesno
Remove Adminyesnono
Assign or revoke Adminyesnono
Transfer ownershipyesnono
Archiveyesnono
Leavetransfer firstyesyes

Actions

MethodActionAuthorizationPurpose
GETmyUserList visible Organizations for the caller
GETgetMemberRead Organization and current Membership
POSTcreateUserCreate an Organization as Owner
POSTupdateOwner/AdminRename
POSTarchiveOwnerArchive terminally
GETmembership/getMemberRead current Membership
GETmembers/listMemberList active Memberships
POSTmembers/roleOwnerSet Admin or Member
POSTmembers/removeOwner/AdminRemove a manageable member
POSTmembers/leaveAdmin/MemberLeave
POSTowner/transferOwnerTransfer the unique Owner
POSTjoin-requests/createUserApply to join
POSTjoin-requests/cancelApplicantCancel a pending request
GETjoin-requests/listOwner/AdminList pending requests
POSTjoin-requests/decideOwner/AdminApprove or reject

Organization Actions

GET my

GET /v1/organizations/my
GET /v1/organizations/my?include_archived=true

The 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

HTTPCodeMeaning
400ORGANIZATION_INPUT_INVALIDInvalid name, ID, role, or decision
400ORGANIZATION_SCOPE_INVALIDInvalid scope_type
401AUTH_REQUIREDMissing valid user identity
403ORGANIZATION_BUREAU_MISMATCHToken Bureau cannot access the Bureau Organization
403NOT_AN_ORGANIZATION_MEMBERNo active Membership
403ORGANIZATION_ROLE_DENIEDGovernance role denies the operation
404ORGANIZATION_NOT_FOUNDOrganization does not exist
404ORGANIZATION_MEMBERSHIP_NOT_FOUNDMembership is absent or inactive
404JOIN_REQUEST_NOT_FOUNDJoin Request is absent or unavailable
409ORGANIZATION_LIMIT_REACHEDOwner quota is full
409OWNER_TRANSFER_REQUIREDTransfer ownership first
410ORGANIZATION_ARCHIVEDOrganization is archived

Branch on error.code, not the message text.