Members
Base path: /api/v1/members. Read (GET list, get one, media bytes) needs members: read. Write (PATCH, DELETE) needs members: read_write.
List members
GET/api/v1/members
Behavior
Returns all active members for the authenticated account, sorted by name. Optional query sideSystemId and subSystemId list members in that system scope instead of the account default. The server may run a one-time schema guard before querying; clients should treat this as a normal list endpoint.
Response
JSON array of member objects. MinIO-backed avatars and backgrounds are exposed as same-origin URLs under /api/v1/members/…/media (same Bearer token), not the dashboard-only /api/storage/member-media path.
Member object fields
| Field | Type | Notes |
|---|---|---|
_id | string | Member id (UUID) |
userId | string | Internal account id |
directoryId | string | Current folder / directory id |
name | string | Legal / system name |
displayName | string? | Optional display name |
pronouns | string? | Optional |
avatar | string? | Raw stored avatar ref or URL (when present) |
background | string? | Raw stored background ref or URL (when present) |
about | string? | Optional markdown or text |
aboutTags | string[] | Labels for the About card |
archivedAt | number? | Unix ms when archived, if set |
createdAt / updatedAt | number? | Unix ms. createdAt is the formed date (when the member/alter came into existence); editable via dashboard or PATCH. updatedAt is last profile change. |
deletedAt / purgeAt | number? | Soft-delete / purge scheduling |
originalDirectoryId | string? | When moved from another folder |
profileStyle | object | null | Member profile chrome (banner, fonts, tints, etc.) |
avatarUrl | string | null | Resolved URL for clients: embeddable legacy https URLs as absolute URLs; MinIO refs as /api/v1/members/{id}/media?kind=avatar&v=… |
backgroundUrl | string | null | Same pattern for background |
Response
200
Sorted JSON array of member objects.
[{
"_id": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
"userId": "a0a0a0a0-a0a0-a0a0-a0a0-a0a0a0a0a0a0",
"directoryId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"name": "string",
"displayName": "string",
"pronouns": "they/them",
"avatar": "bucket::key-or-url",
"about": "markdown or null",
"aboutTags": ["tag-one"],
"background": null,
"archivedAt": null,
"createdAt": 1714233600000,
"updatedAt": 1714237200000,
"deletedAt": null,
"purgeAt": null,
"originalDirectoryId": null,
"profileStyle": null,
"avatarUrl": "/api/v1/members/b1c2d3e4-f5a6-7890-abcd-ef1234567890/media?kind=avatar&v=1714237200000",
"backgroundUrl": null
}]Examples
cURL
curl -sS \
-H "Authorization: Bearer YOUR_TOKEN" \
"https://api.floralitys.com/api/v1/members"JavaScript (fetch)
const res = await fetch("https://api.floralitys.com/api/v1/members", {
headers: { Authorization: "Bearer YOUR_TOKEN" },
});
if (!res.ok) throw new Error(await res.text());
const members = await res.json();Status codes
- 200: JSON array
- 401 / 403: JSON error (see Errors)
Create member
POST/api/v1/members: scope members: read_write.
JSON body: required name; optional directoryId or homeGroupId, displayName, pronouns, avatar, about, background, createdAt (unix ms formed date; defaults to now; cannot be in the future), clientMutationId. Uses the account's active system scope when creating. Returns 201 with the member object.
Get member by id
GET/api/v1/members/{memberId}
Behavior
Returns one member object for the authenticated account. Same field shape as each element in List members (including avatarUrl / backgroundUrl with apiV1 media URLs). Includes archived (moved-out) members; trashed or unknown ids yield NOT_FOUND (JSON, 404).
Response
200
One member object (same fields as list rows).
{
"_id": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
"userId": "a0a0a0a0-a0a0-a0a0-a0a0-a0a0a0a0a0a0",
"directoryId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"name": "string",
"displayName": "string",
"pronouns": "they/them",
"avatar": "bucket::key-or-url",
"about": "markdown or null",
"aboutTags": ["tag-one"],
"background": null,
"archivedAt": null,
"createdAt": 1714233600000,
"updatedAt": 1714237200000,
"deletedAt": null,
"purgeAt": null,
"originalDirectoryId": null,
"profileStyle": null,
"avatarUrl": "/api/v1/members/b1c2d3e4-f5a6-7890-abcd-ef1234567890/media?kind=avatar&v=1714237200000",
"backgroundUrl": null
}404
{
"code": "NOT_FOUND",
"message": "Member not found."
}Examples
cURL
curl -sS \
-H "Authorization: Bearer YOUR_TOKEN" \
"https://api.floralitys.com/api/v1/members/MEMBER_ID"JavaScript (fetch)
const res = await fetch(
"https://api.floralitys.com/api/v1/members/MEMBER_ID",
{ headers: { Authorization: "Bearer YOUR_TOKEN" } },
);
if (!res.ok) throw new Error(await res.text());
const member = await res.json();Status codes
- 200: JSON object
- 404: JSON NOT_FOUND (wrong user, trashed, or unknown id)
- 400: JSON BAD_REQUEST if the path id is empty (edge case)
- 401 / 403: JSON error (see Errors)
Update member
PATCH/api/v1/members/{memberId}
Body (JSON)
Send only fields to change. Unknown keys are rejected (400). Non-media fields must pass validation. Avatar and background go through the same plan limits and WebP pipeline as first-party imports: https URLs are fetched safely, transcoded to WebP, stored under your account, and GIF animation requires a plan that allows GIF avatars/banners. bucket::key storage refs must already belong to your Clerk asset prefix and cannot be an import-staging object (complete the upload flow or use an https URL instead).
| Field | Type | Notes |
|---|---|---|
name | string? | Legal / system name |
displayName, pronouns | string | null? | null null clears; omit to leave unchanged |
about | string | null? | Non-empty strings rehost external markdown images when storage is configured; null / empty clears |
avatar | string | null? | https URL → server imports to WebP with subscription limits. Non-URL string → MinIO bucket::key you already own (final path only, not staging). null / empty clears |
background | string | null? | Same rules as avatar for banner images |
aboutTags | string[] | null? | Replaces About tags; null or [] clears |
profileStyle | object | null? | Member profile chrome JSON; null resets to defaults |
createdAt | number? | Formed date (unix ms). Backdate when the member/alter actually appeared; cannot be in the future. Omit to leave unchanged on PATCH. |
expectedVersion | number? | Optimistic concurrency on members.version |
clientMutationId | UUID string? | Optional idempotency / outbox correlation |
Response
200
Updated member JSON (same shape as GET).
{
"_id": "b1c2d3e4-f5a6-7890-abcd-ef1234567890",
"userId": "a0a0a0a0-a0a0-a0a0-a0a0-a0a0a0a0a0a0",
"directoryId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"name": "string",
"displayName": "string",
"pronouns": "they/them",
"avatar": "bucket::key-or-url",
"about": "markdown or null",
"aboutTags": ["tag-one"],
"background": null,
"archivedAt": null,
"createdAt": 1714233600000,
"updatedAt": 1714237200000,
"deletedAt": null,
"purgeAt": null,
"originalDirectoryId": null,
"profileStyle": null,
"avatarUrl": "/api/v1/members/b1c2d3e4-f5a6-7890-abcd-ef1234567890/media?kind=avatar&v=1714237200000",
"backgroundUrl": null
}404
{
"code": "NOT_FOUND",
"message": "Member not found."
}409
When expectedVersion does not match the stored row (message text may vary).
{
"code": "CONFLICT",
"message": "Version conflict: member was modified by another client."
}503
PRECONDITION_FAILED when storage is required (e.g. markdown image rehost) but not configured.
{
"code": "PRECONDITION_FAILED",
"message": "Object storage is not configured. …"
}400
Validation, empty body, unknown keys, member in Trash, etc.
{
"code": "BAD_REQUEST",
"message": "Invalid request body"
}Examples
cURL
curl -sS -X PATCH \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"displayName":"River","pronouns":"they/them"}' \
"https://api.floralitys.com/api/v1/members/MEMBER_ID"JavaScript (fetch)
const res = await fetch(
"https://api.floralitys.com/api/v1/members/MEMBER_ID",
{
method: "PATCH",
headers: {
Authorization: "Bearer YOUR_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ displayName: "River", pronouns: "they/them" }),
},
);
if (!res.ok) throw new Error(await res.text());
const member = await res.json();Delete member
DELETE/api/v1/members/{memberId}
Query: soft vs hard
| permanent | Behavior |
|---|---|
| omitted, false, 0, or other | Soft delete: move to Trash (deletedAt set, retention purgeAt). Fails if the member is already trashed or is currently on the front ordering / active front session. |
true, 1, or yes (case-insensitive) | Hard delete: permanently remove the member and dependent rows. If the member is not yet trashed, the server soft-deletes then purges in sequence. If already trashed, purges only. |
Response
204
Success: no JSON body.
// 204 No Content: empty response body404 / 400
JSON error when the handler returns a body (e.g. unknown id).
{
"code": "NOT_FOUND",
"message": "Member not found."
}Examples
cURL (soft)
curl -sS -X DELETE \
-H "Authorization: Bearer YOUR_TOKEN" \
"https://api.floralitys.com/api/v1/members/MEMBER_ID"cURL (hard)
curl -sS -X DELETE \
-H "Authorization: Bearer YOUR_TOKEN" \
"https://api.floralitys.com/api/v1/members/MEMBER_ID?permanent=true"JavaScript (fetch, hard)
const url = new URL("https://api.floralitys.com/api/v1/members/MEMBER_ID");
url.searchParams.set("permanent", "true"); // omit or false for soft delete (trash)
const res = await fetch(url.toString(), {
method: "DELETE",
headers: { Authorization: "Bearer YOUR_TOKEN" },
});
if (res.status !== 204) throw new Error(await res.text());Member media (avatar or background)
GET/api/v1/members/{memberId}/media
Query parameters
| Parameter | Required | Description |
|---|---|---|
kind | yes | avatar or background |
v | no | Cache-bust integer (list responses include a suitable v from member timestamps) |
Response
200
Raw image bytes (not JSON). Typical headers: Content-Type from object metadata, Cache-Control: private, max-age=300, optional ETag.
// binary body: not JSON302
Redirect to a legacy public https URL when the stored value is an embeddable HTTP(S) URL. Follow with GET separately; no JSON body on the redirect response.
// Location: https://…401 / 403
Same JSON error object as other v1 routes.
{
"code": "FORBIDDEN",
"message": "Missing API key scope: members requires read"
}400
{
"code": "BAD_REQUEST",
"message": "Missing or invalid kind (use avatar or background)."
}404 / 503
Often an empty body (no JSON) for missing asset, wrong member, or storage not configured.
// empty bodyStatus codes
- MinIO ref: 200 with image bytes, Cache-Control: private, max-age=300, Content-Type from object metadata, optional ETag
- Legacy HTTP(S) URL: 302 to the embeddable URL (upgraded to HTTPS when applicable)
- 401 / 403: JSON error body
- 400: JSON if kind is missing or invalid, or member id is empty
- 404: empty body (wrong user, deleted member, missing field, invalid ref, or non-embeddable URL)
- 503: empty body if object storage is not configured
Examples
cURL
curl -sS -o avatar.bin \
-H "Authorization: Bearer YOUR_TOKEN" \
"https://api.floralitys.com/api/v1/members/MEMBER_ID/media?kind=avatar&v=0"JavaScript (fetch)
const url = new URL(
"https://api.floralitys.com/api/v1/members/MEMBER_ID/media",
);
url.searchParams.set("kind", "avatar");
url.searchParams.set("v", "0");
const res = await fetch(url.toString(), {
headers: { Authorization: "Bearer YOUR_TOKEN" },
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const blob = await res.blob();