Club
The Club resource manages the members of a clubstation and the permission
level each of them holds. Reading the list is the v2 equivalent of the v1
list_clubmembers endpoint; the write operations are the API counterpart of the
club permission page in the web UI.
- Base path:
/api/v2/club - Scopes:
club:read,club:write,club:delete
Note
Read the API v2 overview first for authentication, the response envelope and error codes.
Only on instances with clubstations enabled
The whole resource only exists when the administrator has switched on the
clubstation feature (special_callsign). Without it every path below
answers 404 not_found — exactly like an unknown resource — and the
club:* scopes are not offered when creating a token.
Endpoints
The path id addresses the member, using their user_id.
| Verb | Path | Scope | Purpose |
|---|---|---|---|
GET |
/api/v2/club |
club:read |
List the clubstation's members — or, for an administrator who named no club, the clubstations |
GET |
/api/v2/club/{user_id} |
club:read |
Fetch a single member |
POST |
/api/v2/club |
club:write |
Add a member to the clubstation |
PATCH |
/api/v2/club/{user_id} |
club:write |
Change a member's permission level |
DELETE |
/api/v2/club/{user_id} |
club:delete |
Remove a member from the clubstation |
There is no PUT
As on Station and QSO, the API has no full replace.
A membership has exactly one changeable field, so PATCH is all it needs.
Who may call this
The same two roles that can open the club permission page in the web UI:
| Token | Which clubstation | ?club_id= |
|---|---|---|
| Club token whose creator is an officer (level 9) | the token owner | optional, and only that same club |
| Personal token of a Wavelog administrator | whichever club_id names |
mandatory |
A club token belongs to the clubstation (owner) but was created by a member acting on its behalf (creator), so the club is implicit there. An administrator manages every clubstation and has no implicit one — they always name it.
Anything else is refused with 403 forbidden:
That covers a personal token of a regular user, a club token below officer level
and a token whose membership has since been revoked alike. The message names
whichever role was missing — a club token below officer level is turned away
with Token is not a club officer, because an administrator's rights are not
what it lacked. Match on the code, not the message. See
Clubstations for the permission levels.
The scopes are only offered where they are usable
A session that matches neither role is not shown the club:* checkboxes
when creating a token — a regular user outside a clubstation, or a member
below officer level inside one, has no club they could address.
Selecting the clubstation
?club_id= works the same way on every verb, including POST, PATCH and
DELETE — it selects the club, so it stays out of the request body.
# Administrator: name the club
curl "https://<WAVELOG_URL>/index.php/api/v2/club?club_id=5" \
-H "Authorization: Bearer wl2_admin_token"
# Officer: the club token already knows
curl "https://<WAVELOG_URL>/index.php/api/v2/club" \
-H "Authorization: Bearer wl2_club_token"
| Situation | Response |
|---|---|
Administrator without club_id, on GET /api/v2/club |
the clubstation list |
Administrator without club_id, anywhere else |
400 validation_error |
club_id present but not numeric |
400 validation_error |
club_id is not a clubstation, or does not exist |
404 not_found |
Club token, club_id names a different club |
403 forbidden |
Club token, club_id names its own club |
accepted |
Which clubstations are there
GET /api/v2/club without club_id, as an administrator: the directory of
clubstations to pick a club_id from. Every other call needs one, so refusing
this with a 400 would leave nowhere to look it up.
{
"data": [
{ "club_id": 5, "callsign": "N0CLUB", "member_count": 4 },
{ "club_id": 8, "callsign": "N0DX", "member_count": 0 }
],
"meta": { "count": 2 }
}
member_count counts members of any permission level. A club token never sees
this list — it has exactly one club, so GET /api/v2/club returns that club's
members.
Warning
By default the response contains every member's email address alongside
their name and locator, and club:write lets a token hand out officer
rights. Neither scope should be given to a third-party tool lightly. As the
instance owner you can withhold the personal fields — see
Withheld user data.
The member object
{
"user_id": 42,
"user_firstname": "Alex",
"user_lastname": "Example",
"user_locator": "JN47",
"callsign": "N0CALL",
"user_name": "alex",
"user_email": "alex@example.org",
"permission_level": 9,
"user_language": "english"
}
| Field | Notes |
|---|---|
user_id |
The member's user id — the path id of this resource |
user_firstname / user_lastname |
Member's real name |
user_locator |
Maidenhead locator |
callsign |
The member's own callsign, not the clubstation's |
user_name |
Login/username |
user_email |
Member's email address |
permission_level |
Clubstation permission level: 3, 6 or 9 |
user_language |
Member's UI language |
Fields may be missing
An instance owner can withhold the personal fields — everything except
user_id, callsign and permission_level — to comply with the privacy
rules they are subject to. A withheld field is absent from the object,
not empty. Check that a field is there before using it; see
Withheld user data.
List the members
GET /api/v2/club with a club selected — implicitly by a club token, or
explicitly with ?club_id= as an administrator. Without one, an administrator
gets the clubstation list instead.
{
"data": [
{
"user_id": 42,
"user_firstname": "Alex",
"user_lastname": "Example",
"user_locator": "JN47",
"callsign": "N0CALL",
"user_name": "alex",
"user_email": "alex@example.org",
"permission_level": 9,
"user_language": "english"
}
],
"meta": { "count": 1 }
}
GET /api/v2/club/{user_id} returns a single one of these objects, or
404 not_found when that user is not a member of the clubstation.
Add a member
POST /api/v2/club
Required fields: user_id, permission_level.
curl -X POST https://<WAVELOG_URL>/index.php/api/v2/club \
-H "Authorization: Bearer wl2_your_token_here" \
-H "Content-Type: application/json" \
-d '{ "user_id": 42, "permission_level": 6, "notify": true }'
- On success the API returns
201 Created, aLocationheader and the created member object indata. - A user who is already a member returns
409 conflict— usePATCHto change their level. permission_levelmust be3,6or9; anything else returns400 validation_errorwith the accepted values indetails.allowed.
Writable fields
| Field | Notes |
|---|---|
user_id |
The user to add (POST only) |
permission_level |
3 = Club Member, 6 = Club Member ADIF, 9 = Club Officer |
notify |
Optional, see Notifying the member |
Change a permission level
PATCH /api/v2/club/{user_id}
curl -X PATCH https://<WAVELOG_URL>/index.php/api/v2/club/42 \
-H "Authorization: Bearer wl2_your_token_here" \
-H "Content-Type: application/json" \
-d '{ "permission_level": 9 }'
The updated member object is returned in data. Unlike the web UI this never
creates a membership: a user who is not a member yet returns 404 not_found.
Adding one is POST.
Remove a member
DELETE /api/v2/club/{user_id}
curl -X DELETE https://<WAVELOG_URL>/index.php/api/v2/club/42 \
-H "Authorization: Bearer wl2_your_token_here"
- On success the API returns
204 No Content. - A user who is not a member returns
404 not_found.
Warning
Removing a member also deletes the API keys, API tokens and rig control sessions they created for this clubstation, exactly like the web UI. A token deleted this way is gone for good: re-adding the member restores their permission, but they have to create a new token. See Clubstations.
Their personal account, their personal tokens and their own QSOs are untouched — but the QSOs they logged under the club callsign stay with the club.
Notifying the member
POST and PATCH accept an optional notify flag. When it is true, Wavelog
sends the member the same email the web UI does — "added to the club" on
POST, "permission changed" on PATCH, in the member's own language.
The outcome is reported in the response meta:
A failed email does not fail the request: the permission was granted either
way, and meta.notified is false. The field is omitted entirely when notify
was not requested.
Note
This needs working email settings on the instance. Without them
meta.notified is always false.
Guard rails
Beyond the access check, the API refuses a few operations the web UI would allow:
| Situation | Officer | Administrator |
|---|---|---|
| Acting on your own membership | 403 forbidden |
allowed |
The clubstation's own user_id as the target |
400 validation_error |
400 validation_error |
| The target user is itself a clubstation | 400 validation_error |
400 validation_error |
| The target user does not exist | 404 not_found |
404 not_found |
| The club is managed by an identity provider | 409 conflict on every write |
409 conflict on every write |
Why an officer cannot change their own membership
Demoting or removing themselves over the API would lock the club out of its own permission management, with only a Wavelog administrator able to undo it. The web UI still allows it, where the consequence is visible — and an administrator reaches every club regardless, so the rule would only get in their way.
SSO-managed clubs
When memberships come from an identity provider
(auth_header_clubstation_direct), they are granted and revoked on login.
A write through the API would be silently undone, so it is refused with
409 conflict — the same reason the web UI disables the form.
Wavelog administrators
An administrator (user_type 99) manages every clubstation, exactly as in the
web UI. Two ways to get there:
- A personal token plus
?club_id=— the usual way. Create the token from your normal session; theclub:*scopes are offered because you are an administrator. Every request then names the club. - A club token created while switched into a clubstation. The club is implicit, and it works even without an entry on that club's permission page: the API waves administrators through the membership check, just like the web UI does.
Note
An administrator who does hold an explicit permission level on a club is
treated by that level, not as an administrator — a level below 9 is refused.
Setting yourself to Club Member is a deliberate choice, and the API respects
it. Use a personal token with ?club_id= if you want the administrator path
instead.