Friends
The Friends API manages your friendships (requests, list, block) and lets an authenticated friend read or write another user's shared data: members, groups, front, notes, statuses, and custom fields: subject to Privacy buckets and per-bucket friend permissions.
Base path: /api/v1/friends. Primary scope: friends (read / read_write). See Authentication and Scopes.
Path parameter friendUserId (or requesterUserId on accept) the account that owns the data for friend-scoped routes, not necessarily the API key holder.
Concepts
Viewer vs owner
- Viewer: account identified by the API key.
- Owner: user whose account data is accessed (/api/v1/friends/{friendUserId}/…).
- Routes under /api/v1/friends (no nested id) operate on your friendships as viewer.:
- Nested routes require an accepted friendship (and no block) unless noted; failures often return 404 instead of 403 to avoid leaking existence.:
Friendship status
Filter list/page with status:
pending: outgoing or incoming request not yet acceptedaccepted: active friendsblockedrevoked
Privacy and permissions
Access is enforced in services via resolveFriendAccess and privacy bucket overlap. Each friend is assigned one or more buckets; each bucket defines coarse permission groups (members_view, member_profile, front, member_notes, etc.) at none, read, or read_write. Manage buckets with the Privacy buckets..
Friend write actions (notes, front changes, custom field values) are audited on the owner's account (friend_write.* audit actions).
Related APIs (other scopes)
- Social:
GET /api/v1/friends/{friendUserId}/social(social: read)
Endpoint index
Your friendships
| Method | Path | Scope |
|---|---|---|
| GET | /api/v1/friends | read |
| GET | /api/v1/friends/page | read |
| GET | /api/v1/friends/requests | read |
| GET | /api/v1/friends/suggestions | read |
| POST | /api/v1/friends/request-by-username | read_write |
| POST | /api/v1/friends/block-by-username | read_write |
| POST | /api/v1/friends/requests/{requesterUserId}/accept | read_write |
| GET | /api/v1/friends/search | read |
| GET | /api/v1/friends/search/catalog | read |
| GET | /api/v1/friends/search/members | read |
| GET | /api/v1/friends/{friendUserId} | read |
| POST | /api/v1/friends/{friendUserId}/request | read_write |
| POST | /api/v1/friends/{friendUserId}/revoke | read_write |
| POST | /api/v1/friends/{friendUserId}/block | read_write |
Friend-scoped data (owner = path id)
| Method | Path suffix | Scope |
|---|---|---|
| GET | /members, /members/{memberId} | read |
| GET | /groups, /groups/folder | read |
| GET | /front | read |
| PUT | /front (sole front) | read_write |
| GET | /front/history, /front/history/month | read |
| GET | /front/writable-member-ids | read |
| POST | /front/members | read_write |
| DELETE | /front/members/{memberId}, /front/tier-presence/{memberId} | read_write |
| PATCH | /front/order | read_write |
| GET/POST | /entries | read / read_write |
| PATCH/DELETE | /entries/{entryId} | read_write |
| GET | /member-statuses, /member-statuses/members/{memberId} | read |
| GET | /custom-fields?memberId= | read |
| PUT | /custom-fields/{fieldId}/value | read_write |
Prefix every suffix with /api/v1/friends/{friendUserId}.
List friends
GET/api/v1/friends
Query
| Param | Required | Notes |
|---|---|---|
status | no | pending | accepted | blocked | revoked |
Returns FriendListRow[] for the API key user.
{
"id": "friendship-row-uuid",
"friendUserId": "a0a0a0a0-a0a0-a0a0-a0a0-a0a0a0a0a0a0",
"status": "accepted",
"name": "River",
"username": "river_pl",
"imageUrl": "https://img.clerk.com/…",
"friendPronouns": "they/them",
"friendStatus": "Usually around evenings",
"friendBannerUrl": "/api/v1/users/…/friend-banner?…",
"isAdmin": false,
"planKey": "pro",
"donorSupporterUnlocked": true,
"closedBetaParticipantAt": null,
"privacyBucketIds": ["f47ac10b-58cc-4372-a567-0e02b2c3d479"],
"createdAt": 1714233600000,
"acceptedAt": 1714237200000
}curl -sS \
-H "Authorization: Bearer YOUR_TOKEN" \
"https://api.floralitys.com/api/v1/friends?status=accepted"Paginated friends
GET/api/v1/friends/page
Query
| Param | Required | Notes |
|---|---|---|
limit | yes | 1–200 |
cursor | no | Opaque offset from previous nextCursor |
query | no | Filter by friend display name (case-insensitive) |
status | no | Same as list endpoint |
{
"items": [ /* FriendListRow[] */ ],
"nextCursor": 25
}curl -sS \
-H "Authorization: Bearer YOUR_TOKEN" \
"https://api.floralitys.com/api/v1/friends/page?limit=25&query=river"Friend requests
GETGET /api/v1/friends/requests: incoming pending requests (FriendListRow[]; friendUserId is the requester).
POSTPOST /api/v1/friends/{friendUserId}/request: send request to user id. 204.
POSTPOST /api/v1/friends/request-by-username: body { username: clerk_username }. 201 { friendUserId, username }.
POSTPOST /api/v1/friends/requests/{requesterUserId}/accept: accept incoming. 204.
POSTPOST /api/v1/friends/{friendUserId}/revoke: end friendship. 204.
POSTPOST /api/v1/friends/{friendUserId}/block: optional body { reason: … } (max 280 chars). 204.
POSTPOST /api/v1/friends/block-by-username: body { username, reason? }. 200 { username }. Blocks even without prior friendship.
Suggestions
GET/api/v1/friends/suggestions: Accepted-friend graph recommendations (friends of friends) filtered by profile suggestion visibility and reach policy.
Optional query limit (1–50, default 20). Returns FriendSuggestionRow[].
{
"friendUserId": "a0a0a0a0-a0a0-a0a0-a0a0-a0a0a0a0a0a0",
"mutualFriendCount": 3,
"name": "River",
"username": "river_pl",
"imageUrl": "https://img.clerk.com/…",
"friendPronouns": "they/them",
"friendStatus": "Usually around evenings",
"friendBannerUrl": "/api/v1/users/…/friend-banner?…",
"isAdmin": false,
"planKey": "pro",
"donorSupporterUnlocked": true,
"closedBetaParticipantAt": null
}curl -sS \
-H "Authorization: Bearer YOUR_TOKEN" \
"https://api.floralitys.com/api/v1/friends/suggestions?limit=20"Friend profile
GET/api/v1/friends/{friendUserId}
Public friend card: Clerk username/image, friend-facing profile fields, plan badges, and friendProfileTabs booleans (members, frontHistory, notes) indicating which tabs the viewer may use. Requires accepted friendship.
{
"friendUserId": "a0a0a0a0-a0a0-a0a0-a0a0-a0a0a0a0a0a0",
"name": "River",
"username": "river_pl",
"imageUrl": "https://img.clerk.com/…",
"displayName": "River 🌊",
"friendPronouns": "they/them",
"friendStatus": "Usually around evenings",
"friendTimezone": "Europe/London",
"friendCountry": "GB",
"friendAboutMarkdown": "Hi!",
"friendBannerUrl": "/api/v1/users/…/friend-banner?…",
"isAdmin": false,
"planKey": "pro",
"donorSupporterUnlocked": true,
"closedBetaParticipantAt": null,
"friendProfileTabs": {
"members": true,
"membersDirectory": false,
"frontHistory": true,
"notes": true
}
}Search (friend-filtered)
Search the owner's catalog as a friend viewer. All endpoints require ownerUserId (UUID) and friendship access is enforced per result.
| Method | Path | Query |
|---|---|---|
| GET | /api/v1/friends/search | ownerUserId, q (min 1 char), optional limitPerType (1–50) |
| GET | /api/v1/friends/search/catalog | ownerUserId: full searchable index, filtered |
| GET | /api/v1/friends/search/members | ownerUserId, q, optional limit (1–50) |
Result rows use search result types (member, note, etc.); members and notes are dropped when the viewer lacks bucket permission.
Members visible to a friend
GET/api/v1/friends/{friendUserId}/members
Active members on the owner account that pass members.view, with profile fields redacted per bucket rules (same shapes as Members API dashboard media URLs tagged with ownerUserId)..
GET/api/v1/friends/{friendUserId}/members/{memberId}: Single member; requires members.profile.read. 404 if not visible.
curl -sS \
-H "Authorization: Bearer YOUR_TOKEN" \
"https://api.floralitys.com/api/v1/friends/OWNER_USER_UUID/members"Groups and folders
GET/api/v1/friends/{friendUserId}/groups
Directory tree visible to the friend (groups.read via buckets). Accepted friendship required.
GET/api/v1/friends/{friendUserId}/groups/folder
Query
| Param | Notes |
|---|---|
folderId | Folder UUID, or omit / null for root view |
Returns folder contents the friend may see (members, subfolders, metadata).
Front (who is fronting)
Read and optionally mutate the owner's live front state. Write routes need members.front.write on the friend's merged bucket matrix and visible members.
Current front
GET/api/v1/friends/{friendUserId}/front
Returns primary, alsoFronting, tieredMembers, and top-level note. Members hidden by front.read or members.view are stripped. On server errors, returns an empty front object instead of 500.
{
"primary": {
"member": { "_id": "member-uuid", "name": "Alex", "avatarUrl": "/api/v1/members/…" },
"activeSession": { "note": "At work", "startedAt": 1714237200000 }
},
"alsoFronting": [],
"tieredMembers": [],
"note": "At work"
}Set sole front
PUT/api/v1/friends/{friendUserId}/front
Body { memberId: uuid }: clears all fronts, then fronts that member. 204.
curl -sS -X PUT \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"memberId":"MEMBER_UUID"}' \
"https://api.floralitys.com/api/v1/friends/OWNER_USER_UUID/front"Add / remove / reorder
- POST
/front/members: body { memberId } - DELETE
/front/members/{memberId}: - DELETE
/front/tier-presence/{memberId}: remove tier-only presence - GET
/front/order: body { memberIds: [uuid, …] } - GET
/front/writable-member-ids: member ids the viewer may front-manage
Front history
GET/front/history
Query: optional limit (1–50, default 20), cursorStartedAt + cursorId, startTime, endTime (epoch ms). Returns { items, count, nextCursor } with members filtered like live front. count is the friend's total matching sessions (before per-member privacy filtering on items).
GET/front/history/month: Required startTime, endTime; optional maxSessions (1–50000). Calendar-style aggregation for friends with tab access.
Entries (notes)
Friend-visible notes on the owner account. Permissions: members.notes.read / members.notes.write per linked member.
GET/api/v1/friends/{friendUserId}/entries
Query
| Param | Notes |
|---|---|
limit | 1–100, default 50 |
memberId | Filter to one member; returns [] if not readable |
Each row includes viewerCanWrite. Scans up to 500 entries server-side to fill the limit.
{
"_id": "entry-uuid",
"title": "Check-in",
"content": "…",
"createdAt": 1714233600000,
"updatedAt": 1714237200000,
"members": [{ "id": "member-uuid", "name": "Alex" }],
"member": { "id": "member-uuid", "name": "Alex" },
"viewerCanWrite": true
}POST/entries: body { "content", "title?", "memberId?" | "memberIds?" }. 201 { "id" } or 403 with plan/limit code.
curl -sS -X POST \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"content":"Shared note","memberIds":["MEMBER_UUID"]}' \
"https://api.floralitys.com/api/v1/friends/OWNER_USER_UUID/entries"PATCH/entries/{entryId}: partial update; write permission required on all affected members. 204.
DELETE/entries/{entryId}: 204.
Member statuses (friend view)
Resolved status tags visible to the friend (bucket-gated). Uses friends: read, not the owner's memberStatuses scope.
- GET
/member-statuses: account-level resolved statuses for owner/friend pair - GET
/member-statuses/members/{memberId}: statuses on one member
See Member statuses: friend-visible for response shapes. If friendUserId equals your own user id, returns your self-scoped resolution instead..
Custom fields
GET/api/v1/friends/{friendUserId}/custom-fields?memberId={uuid}
Required memberId. Returns definitions with values where bucket overlap and field-level read permission pass. Each field includes viewerCanWrite.
emptyHint: emptyHint: null | no_definitions | none_visible.
{
"fields": [
{
"id": "field-def-uuid",
"name": "Favorite color",
"type": "text",
"value": "Blue",
"viewerCanWrite": true
}
],
"emptyHint": null
}PUT/custom-fields/{fieldId}/value: body { "memberId", "value": string | string[] }. 204.
Errors
JSON body { code, message }:
UNAUTHORIZED (401): missing/invalid tokenFORBIDDEN (403): scope or explicit permission denial (e.g. front write)NOT_FOUND (404): unknown id or friendship not visibleCONFLICT (409): duplicate request or invalid state transitionBAD_REQUEST (400): validation
See Errors.
{
"code": "NOT_FOUND",
"message": "Not found."
}// 204 No Content: empty response body