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

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