Users API
Reference for user endpoints: profile updates, follow/unfollow, account deletion, and messaging.
The Users API manages user profiles, social relationships, direct messaging, and account lifecycle. All endpoints require authentication.
Update profile
Update the authenticated user's profile. Changes to username and display name are cascaded to all products authored by the user. Username uniqueness is enforced atomically via a dedicated usernames collection in Firestore.
POST /users/update-profileAuth: Required (Bearer token) Rate limit: 5/min (strict)
Request body
{
"displayName": "Jane Doe",
"username": "janedoe",
"bio": "Building tools for the Claude Code ecosystem",
"country": "US",
"avatarUrl": "https://example.com/avatar.jpg"
}| Field | Type | Required | Constraints |
|---|---|---|---|
displayName | string | Yes | Max 50 characters |
username | string | Yes | Lowercase alphanumeric + _ + -, 3--30 characters. Must match /^[a-z0-9_-]{3,30}$/ |
bio | string | No | Max 500 characters |
country | string | No | Max 100 characters |
avatarUrl | string | No | Must be a valid HTTPS URL |
Response
{
"success": true
}Side effects
When username, display name, or avatar changes, the server cascades the update to all products authored by this user (denormalized fields: authorUsername, authorDisplayName, authorAvatar). Updates are batched in chunks of 500 documents.
Username changes are audited in the server log.
Errors
| Status | Condition |
|---|---|
| 400 | Invalid display name, username format, bio/country too long, or invalid avatar URL |
| 404 | User profile not found |
| 409 | Username is taken by another user |
Follow / unfollow
Toggle a follow relationship with another user. Uses a Firestore transaction for atomic counter updates on both users.
POST /users/followAuth: Required (Bearer token) Rate limit: 30/min (strict)
Request body
{
"targetUid": "uid_of_user_to_follow"
}| Field | Type | Required | Description |
|---|---|---|---|
targetUid | string | Yes | UID of the user to follow or unfollow |
Response
{
"following": true
}following: true means the follow was created. following: false means the follow was removed (toggled off).
Side effects
- Creates/deletes documents in both
users/{myUid}/followingandusers/{targetUid}/followerssubcollections. - Increments/decrements
stats.followingon the current user andstats.followerson the target user.
Errors
| Status | Condition |
|---|---|
| 400 | Missing targetUid, or attempting to follow yourself |
| 404 | Target user not found |
Delete account
Permanently delete the authenticated user's account. This is an irreversible operation that anonymizes the user profile, removes all products, cleans up subcollections, and deletes the Firebase Auth account.
DELETE /users/delete-accountAuth: Required (Bearer token) Rate limit: 1/min (strict)
Request body
None.
Response
{
"success": true
}Deletion sequence
The server performs these steps in order:
| Step | Action | Details |
|---|---|---|
| 0 | Release username | Deletes the username reservation from the usernames collection |
| 1 | Anonymize profile | Sets display name to "Deleted User", clears bio, avatar, email, Stripe data |
| 2 | Remove products | Sets all user's products to status: "removed", anonymizes author fields |
| 3 | Delete achievements | Removes all documents in users/{uid}/achievements |
| 4 | Delete following | Removes all documents in users/{uid}/following |
| 5 | Delete followers | Removes all documents in users/{uid}/followers |
| 6 | Delete Firebase Auth | Deletes the Firebase Authentication account (irreversible) |
An audit trail is written before deletion begins, recording the username and product count.
Errors
| Status | Condition |
|---|---|
| 404 | User profile not found |
List conversations
Retrieve all conversations the authenticated user is participating in, ordered by most recent message.
GET /messagesAuth: Required (Bearer token) Rate limit: 60/min (standard)
Response
{
"conversations": [
{
"id": "productId_buyerUid",
"productId": "abc123",
"productTitle": "Code Review Skill",
"productSlug": "code-review-skill",
"buyerUid": "uid_buyer",
"buyerUsername": "buyer1",
"buyerDisplayName": "Buyer One",
"buyerAvatar": "https://...",
"sellerUid": "uid_seller",
"sellerUsername": "seller1",
"sellerDisplayName": "Seller One",
"sellerAvatar": "https://...",
"participants": ["uid_buyer", "uid_seller"],
"lastMessage": "Thanks for the quick response!",
"lastMessageAt": "2026-03-24T16:30:00.000Z",
"lastMessageSenderUid": "uid_buyer",
"unreadBySeller": 1,
"unreadByBuyer": 0,
"createdAt": "2026-03-24T10:00:00.000Z"
}
]
}Returns up to 50 conversations.
Send a message
Send a message about a product. Buyers can initiate conversations; sellers can only reply to existing ones. A conversation is uniquely identified by {productId}_{buyerUid}.
POST /messagesAuth: Required (Bearer token) Rate limit: 20/min (strict)
Request body
{
"productId": "abc123",
"text": "Is this compatible with Claude Code 2.0?"
}| Field | Type | Required | Constraints |
|---|---|---|---|
productId | string | Yes | Valid product ID |
text | string | Yes | 1--1000 characters |
Response
{
"success": true,
"conversationId": "abc123_uid_buyer"
}Side effects
- Creates a conversation document if one does not exist (buyer-initiated only).
- Increments the unread counter for the recipient.
- Creates a notification for the recipient.
Errors
| Status | Condition |
|---|---|
| 400 | Missing productId, empty text, text too long, or seller has no conversation to reply to |
| 404 | Product not found |
Get conversation messages
Retrieve messages in a specific conversation. Marks the conversation as read for the authenticated user.
GET /messages/{conversationId}Auth: Required (Bearer token, must be a conversation participant) Rate limit: 60/min (standard)
Path parameters
| Parameter | Type | Description |
|---|---|---|
conversationId | string | Conversation ID (format: {productId}_{buyerUid}) |
Response
{
"messages": [
{
"id": "msg_001",
"senderUid": "uid_buyer",
"senderUsername": "buyer1",
"senderDisplayName": "Buyer One",
"senderAvatar": "https://...",
"text": "Is this compatible with Claude Code 2.0?",
"createdAt": "2026-03-24T10:05:00.000Z"
}
],
"conversation": {
"id": "abc123_uid_buyer",
"productTitle": "Code Review Skill",
"productSlug": "code-review-skill",
"buyerUsername": "buyer1",
"buyerDisplayName": "Buyer One",
"buyerAvatar": "https://...",
"sellerUsername": "seller1",
"sellerDisplayName": "Seller One",
"sellerAvatar": "https://...",
"buyerUid": "uid_buyer",
"sellerUid": "uid_seller"
}
}Returns up to 100 messages per conversation, ordered oldest first.
Errors
| Status | Condition |
|---|---|
| 403 | Authenticated user is not a participant in this conversation |
| 404 | Conversation not found |
Server-side user data (internal)
User profiles and product listings are rendered server-side for SEO via Next.js Server Components. These are not public API endpoints -- they are internal lib/server/ functions used during SSR:
| Function | Path | Description |
|---|---|---|
getUserProfile(uid) | lib/server/users.ts | Fetch user profile by UID |
getUserByUsername(username) | lib/server/users.ts | Fetch user profile by username |
These functions return full profile data which is then stripped to SafeUserProfile before being passed to client components.
Related pages
- API Overview -- Auth model, rate limits, error format
- Products API -- Product operations
- Payments API -- Stripe Connect setup for creators
- Creator Onboarding -- Getting started as a creator
- Security Model -- How user data is protected