Group API
Planned — not yet live. All group endpoints are planned. See the product doc for the intended behavior.
All endpoints require a Firebase ID token in the Authorization: Bearer <token>
header unless noted. A missing or invalid token returns 401 UNAUTHORIZED.
Create Group
POST /groups
Auth: Bearer token; caller must be type: subscriber, type: beta, or
type: trial
Request
{
"name": "Bangalore Riders", // required; 3–100 chars
"description": "Weekend rides across Karnataka", // required; non-empty
"type": "public", // required; "public" | "private"
"baseLocation": {
"name": "Bangalore", // required; non-empty
"lat": 12.9716, // required
"lng": 77.5946, // required
},
"poster": "https://example.com/poster.jpg", // optional
}
Response 201
{ "id": "grp_abc123" }
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | MISSING_FIELD |
name, description, type, or baseLocation not provided |
| 400 | INVALID_FIELD |
name < 3 or > 100 chars; type not a valid enum value |
| 401 | UNAUTHORIZED |
No valid Firebase ID token |
| 403 | GROUP_LIMIT_REACHED |
Caller has reached the maximum group count for their subscription |
| 403 | FORBIDDEN |
Caller is type: free |
Discover Groups
GET /groups?lat=<number>&lng=<number>&search=<string>
Auth: Bearer token
Returns public, non-archived groups. Defaults to proximity order when
lat/lng are provided. Falls back to text search when search is provided.
Already-joined groups are excluded.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
lat |
number |
no | −90 to 90; enables proximity ordering |
lng |
number |
no | −180 to 180; required when lat is provided |
search |
string |
no | City or neighbourhood name to search |
Response 200
{
"groups": [
{
"id": "grp_abc123",
"name": "Bangalore Riders",
"description": "Weekend rides across Karnataka",
"type": "public",
"baseLocation": { "name": "Bangalore", "lat": 12.9716, "lng": 77.5946 },
"memberCount": 24,
"poster": "https://example.com/poster.jpg",
},
],
}
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | INVALID_FIELD |
lat provided without lng or vice versa; values out of range |
| 401 | UNAUTHORIZED |
No valid Firebase ID token |
Get Group
GET /groups/{groupId}
Auth: Bearer token
Returns full group details. Private groups require the caller to be a member.
Response 200
{
"id": "grp_abc123",
"name": "Bangalore Riders",
"description": "Weekend rides across Karnataka",
"type": "public",
"baseLocation": { "name": "Bangalore", "lat": 12.9716, "lng": 77.5946 },
"poster": "https://example.com/poster.jpg",
"ownerId": "uid_owner",
"adminsId": ["uid_admin1"],
"memberCount": 24,
"settings": {
"requireApproval": true,
"inviteEnabled": true,
"allowAdminChangeName": false,
"allowAdminChangeDescription": true,
"allowMembersToCreateRides": false,
},
"archivedAt": null,
"createdAt": "2025-01-01T00:00:00.000Z",
"updatedAt": "2025-06-01T08:00:00.000Z",
}
Errors
| Status | Code | Condition |
|---|---|---|
| 401 | UNAUTHORIZED |
No valid Firebase ID token |
| 403 | NOT_GROUP_MEMBER |
Group is private and caller is not a member |
| 404 | NOT_FOUND |
Group does not exist |
Preview Group (Invite Link)
GET /groups/{groupId}/preview?code={inviteCode}
Auth: Bearer token
Returns limited group details for a non-member viewing via invite link. Does not require group membership.
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
code |
string |
yes | Invite code from the invite link |
Response 200
{
"id": "grp_abc123",
"name": "Bangalore Riders",
"description": "Weekend rides across Karnataka",
"type": "private",
"baseLocation": { "name": "Bangalore", "lat": 12.9716, "lng": 77.5946 },
"memberCount": 24,
"poster": "https://example.com/poster.jpg",
"requireApproval": true,
}
Errors
| Status | Code | Condition |
|---|---|---|
| 401 | UNAUTHORIZED |
No valid Firebase ID token |
| 403 | INVALID_INVITE_CODE |
code does not match the group's current inviteCode |
| 403 | INVITE_DISABLED |
Invite links are disabled for this group |
| 404 | NOT_FOUND |
Group does not exist |
Update Group
PATCH /groups/{groupId}
Auth: Bearer token; caller must be owner or admin (see per-field permissions in product doc)
All fields optional; only provided fields are updated.
Request
{
"name": "Bangalore Weekend Riders", // owner; admin if allowAdminChangeName
"description": "Rides every Sunday", // owner; admin if allowAdminChangeDescription
"poster": "https://example.com/new-poster.jpg", // owner only; null to remove
"settings": {
"requireApproval": false, // owner only
"inviteEnabled": true, // owner only
"allowAdminChangeName": true, // owner only
"allowAdminChangeDescription": true, // owner only
"allowMembersToCreateRides": true, // owner only
},
}
Response 200
{ "id": "grp_abc123" }
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | INVALID_FIELD |
name < 3 or > 100 chars |
| 401 | UNAUTHORIZED |
No valid Firebase ID token |
| 403 | FORBIDDEN |
Caller is not owner or admin; or admin attempting an owner-only field change |
| 404 | NOT_FOUND |
Group does not exist |
Regenerate Invite Code
POST /groups/{groupId}/invite-code
Auth: Bearer token; caller must be owner or admin
Generates a new invite code, invalidating all previously shared invite links.
Request — empty body
Response 200
{
"inviteLink": "https://95octane.app/g/grp_abc123?code=y9rT4wKp",
}
Errors
| Status | Code | Condition |
|---|---|---|
| 401 | UNAUTHORIZED |
No valid Firebase ID token |
| 403 | FORBIDDEN |
Caller is not owner or admin |
| 403 | INVITE_DISABLED |
Invite links are disabled; enable them in settings first |
| 404 | NOT_FOUND |
Group does not exist |
Join Group
POST /groups/{groupId}/join
Auth: Bearer token
Joins a public group directly, or submits a join request via invite link. The
inviteCode is required for private groups.
Request
{
"inviteCode": "x7kP2mQn", // required for private groups; optional for public
}
Response 200
{
"status": "joined", // or "pending" when requireApproval is true
}
Errors
| Status | Code | Condition |
|---|---|---|
| 401 | UNAUTHORIZED |
No valid Firebase ID token |
| 403 | ALREADY_MEMBER |
Caller is already a member of the group |
| 403 | REQUEST_PENDING |
Caller already has a pending join request |
| 403 | INVITE_REQUIRED |
Group is private and no inviteCode provided |
| 403 | INVALID_INVITE_CODE |
Provided inviteCode does not match the current code |
| 403 | INVITE_DISABLED |
Invite links are disabled for this group |
| 403 | GROUP_ARCHIVED |
Group is archived; no new members can join |
| 404 | NOT_FOUND |
Group does not exist |
Approve Join Request
POST /groups/{groupId}/requests/{requestId}/approve
Auth: Bearer token; caller must be owner or admin
Request — empty body
Response 200
{ "success": true }
Errors
| Status | Code | Condition |
|---|---|---|
| 401 | UNAUTHORIZED |
No valid Firebase ID token |
| 403 | FORBIDDEN |
Caller is not owner or admin |
| 404 | NOT_FOUND |
Group or request does not exist (request may have already been resolved) |
Reject Join Request
POST /groups/{groupId}/requests/{requestId}/reject
Auth: Bearer token; caller must be owner or admin
Request — empty body
Response 200
{ "success": true }
Errors
| Status | Code | Condition |
|---|---|---|
| 401 | UNAUTHORIZED |
No valid Firebase ID token |
| 403 | FORBIDDEN |
Caller is not owner or admin |
| 404 | NOT_FOUND |
Group or request does not exist (request may have already been resolved) |
Update Member Role
PATCH /groups/{groupId}/members/{userId}
Auth: Bearer token; caller must be owner
Promotes a member to admin or demotes an admin to member.
Request
{
"role": "admin", // required; "admin" | "member"
}
Response 200
{ "success": true }
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | INVALID_FIELD |
role is not "admin" or "member" |
| 401 | UNAUTHORIZED |
No valid Firebase ID token |
| 403 | FORBIDDEN |
Caller is not the owner |
| 404 | NOT_FOUND |
Group or member does not exist |
Remove Member
DELETE /groups/{groupId}/members/{userId}
Auth: Bearer token; caller must be owner or admin (admins cannot remove other admins or the owner)
Request — empty body
Response 200
{ "success": true }
Errors
| Status | Code | Condition |
|---|---|---|
| 401 | UNAUTHORIZED |
No valid Firebase ID token |
| 403 | FORBIDDEN |
Caller is not owner or admin; or admin attempting to remove another admin or the owner |
| 404 | NOT_FOUND |
Group or member does not exist |
Leave Group
DELETE /groups/{groupId}/members/{callerId}
Auth: Bearer token; :callerId must match the authenticated user's UID
The caller removes themselves from the group. Owner cannot leave — they must transfer ownership first.
Request — empty body
Response 200
{ "success": true }
Errors
| Status | Code | Condition |
|---|---|---|
| 401 | UNAUTHORIZED |
No valid Firebase ID token |
| 403 | FORBIDDEN |
Caller is the group owner |
| 404 | NOT_FOUND |
Group or membership does not exist |
Transfer Ownership
POST /groups/{groupId}/transfer-ownership
Auth: Bearer token; caller must be the owner
Request
{
"newOwnerId": "uid_admin1", // required; must be a current admin
}
Response 200
{ "success": true }
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | MISSING_FIELD |
newOwnerId not provided |
| 401 | UNAUTHORIZED |
No valid Firebase ID token |
| 403 | FORBIDDEN |
Caller is not the owner |
| 403 | TARGET_NOT_ADMIN |
newOwnerId is not a current admin of the group |
| 404 | NOT_FOUND |
Group does not exist |
Archive Group
POST /groups/{groupId}/archive
Auth: Bearer token; caller must be owner or admin
Request — empty body
Response 200
{ "success": true }
Errors
| Status | Code | Condition |
|---|---|---|
| 401 | UNAUTHORIZED |
No valid Firebase ID token |
| 403 | FORBIDDEN |
Caller is not owner or admin |
| 403 | GROUP_ALREADY_ARCHIVED |
Group is already archived |
| 404 | NOT_FOUND |
Group does not exist |
Unarchive Group
POST /groups/{groupId}/unarchive
Auth: Bearer token; caller must be owner
Request — empty body
Response 200
{ "success": true }
Errors
| Status | Code | Condition |
|---|---|---|
| 401 | UNAUTHORIZED |
No valid Firebase ID token |
| 403 | FORBIDDEN |
Caller is not the owner |
| 403 | GROUP_NOT_ARCHIVED |
Group is not currently archived |
| 404 | NOT_FOUND |
Group does not exist |
Delete Group
DELETE /groups/{groupId}
Auth: Bearer token; caller must be the owner
Sets deletedAt and returns 202 Accepted. Member cleanup and push
notifications are handled by DeleteGroupWorkflow (see
worker/workflows/group.md).
Request
{
"confirmation": "DELETE", // required; must be the exact string "DELETE"
}
Response 202
{ "success": true }
Errors
| Status | Code | Condition |
|---|---|---|
| 400 | MISSING_FIELD |
confirmation not provided |
| 400 | INVALID_CONFIRMATION |
confirmation is not exactly "DELETE" |
| 401 | UNAUTHORIZED |
No valid Firebase ID token |
| 403 | FORBIDDEN |
Caller is not the owner |
| 404 | NOT_FOUND |
Group does not exist |