Organizations Federation API
HTTP and SDK reference for Organizations Service
This reference covers Organizations Actions for clients and Bureau. See Integrate Organizations into Bureau for product authorization.
Install
import { Federation } from "@downcity/federation";
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.