ServicesGuild

Guild

Membership groups with teeth: clans, teams, crews, or any grouping where who-may-do-what matters. Guilds combine CRUD, three join policies (open, application, invite-only), role-based permissions (leader, officer, member) enforced server-side on every operation, and a per-guild treasury bank denominated in gold. Every example on this page was executed against a live server.

When to reach for it: game clans with ranks, team rosters where officers moderate membership, communities gated by application review, groups pooling a shared currency balance.

When not to: account-level organizations (billing, invites tied to authentication) belong to Auth organizations; short-lived parties that queue for matches together belong to Matchmaking group tickets.

Concepts

  • Roles are the permission system - every member is a leader, officer, or member, and each operation checks the role server-side. The creator automatically becomes the leader.
  • Join policy decides what join does: open admits immediately, application files a pending application, invite_only rejects direct joins outright. Details on Join policies.
  • Leadership continuity is guarded - a leader cannot be kicked, and the last leader can neither leave nor be demoted. Promote a successor first.
  • The bank is a real ledger - integer gold amounts, atomic overdraft protection, and idempotency keys for safe retries, backed by the same ledger engine as the Treasury service.
  • The CLI acts as the token's subject - create, join, and leave fill the acting user from the JWT. Over raw HTTP the body carries it explicitly (created_by on create, user_id on join/leave).

Create and manage

snug guild create --name "Ember Vanguard" --description "Docs guild" \
  --visibility public --join-policy open
snug guild get --guild-id <id>
snug guild update --guild-id <id> --description "New blurb" --visibility private
snug guild delete --guild-id <id>

Over HTTP, creation is a POST /api/v1/guilds with an explicit created_by; captured live:

curl -X POST -H "Authorization: Bearer $SNUG_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"docs4-Iron Pact","created_by":"docs-wave","join_policy":"open"}' \
  http://localhost:4000/api/v1/guilds
{
  "status": 201,
  "msg": "Created",
  "data": {
    "guild_id": "gpBfMsmcATaFGFejtHxp",
    "name": "docs4-Iron Pact",
    "created_at": "2026-08-28T13:27:59.020794Z"
  }
}

get returns the full profile (policy, visibility, member_count, max_members, level, xp). Over HTTP, GET /api/v1/guilds/{id}?include=members hydrates the member list into the same response (the CLI get command has no include flag). Deleting a guild cascades: its members, applications, and invitations go with it, and subsequent reads return 404 guild_not_found.

Finding guilds

list speaks the shared search grammar: full-text -q over name and description, repeatable --filter field:operator:value, --sort-by (created_at, member_count, level, name), plus --public-only:

snug guild list -q vanguard --sort-by member_count --sort-order desc --page-size 5
snug guild list --public-only --filter join_policy:eq:open

Membership

snug guild join --guild-id <id>          # you, per the JWT subject
snug guild leave --guild-id <id>
snug guild members list --guild-id <id>
snug guild members update-role --guild-id <id> --user-id alice --role officer
snug guild members kick --guild-id <id> --user-id alice

Verified behaviors:

  • Joining an open guild admits immediately with role: "member". Joining twice is 409 member_already_exists.
  • Kicking a leader fails with 403 insufficient_permissions (Cannot kick a leader) - demote them first.
  • The last leader leaving is 400 cannot_leave_guild, and demoting the last leader is 400 cannot_demote_last_leader. Promote another member to leader first; after that the original leader leaves cleanly.

Roles and permissions

Verified against a live server with three identities:

CapabilityLeaderOfficerMember
View guild, members, bank balanceyesyesyes
Invite users, review applicationsyesyes-
Kick membersyesyes-
Deposit to the bankyesyes-
Withdraw from the bankyes--
Change roles, edit profile, delete guildyes--

A denied operation is always 403 insufficient_permissions, with the missing permission named in msg - e.g. an officer calling update-role gets Missing permission: ManageRoles.

Treasury bank

Each guild has one gold-denominated account. Amounts are integers >= 1 in the smallest currency unit:

snug guild bank balance --guild-id <id>
snug guild bank deposit --guild-id <id> --amount 500 --idempotency-key dep-1
snug guild bank withdraw --guild-id <id> --amount 200
{ "guild_id": "GCELutDuFrtYWagyxaTn", "currency": "gold", "balance": 300, "held": 0, "available": 300 }

Withdrawing more than the balance is rejected atomically with 409 insufficient_funds. Repeating a deposit or withdrawal with the same --idempotency-key returns the committed balance without applying it twice - verified by replaying a deposit and confirming the balance moved once.

Joining flows

Application review and invitations - who may act, and how decided records behave - have their own page: Join policies.

Limits and configuration

Guilds cap at 100 members and 10,000 guilds per deployment by default, with name and description bounded at 100 and 1,000 characters - all tunable via the GUILD_* variables in the Guild CONFIG reference.

Reference

  • Guild API - every endpoint, callable
  • Related: Auth for account-level organizations, Matchmaking for party-based match tickets, Treasury for the general-purpose ledger the bank is built on

On this page