Appearance
Auth, Tile management, and Tile hosting APIs for Poe Tiles.
Compatibility login with a linked Poe API key
POST
/auth/login
Request Body
application/json
{
}
Responses
Login result
application/json
{
}
Start a poe-tiles CLI device-code login
POST
/auth/device-code/start
Request Body
application/json
{
}
Responses
Device-code grant
application/json
{
}
Poll a poe-tiles CLI device-code login
Start phone signup or login
POST
/auth/phone/start
Sends an OTP to the supplied phone number without revealing whether the number is already registered.
Request Body
application/json
{
}
Responses
Challenge created and OTP sent
application/json
{
}
Confirm phone signup or login
POST
/auth/phone/confirm
Verifies the OTP, creates or selects the Tiles user by phone, creates a hashed session, and sets the existing session_token cookie.
Request Body
application/json
{
}
Responses
Phone confirmed and session created
application/json
{
}
Get current session
GET
/auth/session
Responses
Session result
application/json
{
}
Logout and clear session
Get current points balance
Get usage history
List available models
GET
/models
Authorizations
Responses
Models list result
application/json
{
}
List published Tiles
GET
/tiles
Paginated by opaque nextCursor. Ordered newest-first by the Tile registry's (updated_at, id) sort key.
Parameters
Query Parameters
limit
Type
integer
Minimum
1Maximum
200cursor
Type
string
Max Length
1000creatorHandle
Type
string
Min Length
1Max Length
255Responses
Tiles list result
application/json
{
}
Personalized 'for you' feed of Tiles with seeded pagination
GET
/tiles/for-you-feed
Top of the feed mixes the curated priority list with a seeded-random sample of recently updated Tiles (sample size = priority count, drawn from a pool of the 2*priority newest Tiles after excluding creators in the random-recent exclusion set — filtered first, then sliced, so a burst of excluded-creator publishes can't starve the slot). Pagination is stable for a given seed only as long as the underlying set of Tiles and the prioritized list are unchanged. New Tiles published or visibility flips between page fetches may shift positions; clients should treat duplicate or skipped Tiles across pages as expected.
Parameters
Query Parameters
seed
Type
string
Min Length
1Max Length
256cursor
Type
[
"integer",
"null"
]
Minimum
0limit
Type
integer
Minimum
1Maximum
200Responses
Seeded for-you feed page
application/json
{
}
Get Tile details by ID
GET
/tiles/by-id/{tileId}
Parameters
Path Parameters
tileId*
Type
Requiredstring
Min Length
1Responses
Tile details result
application/json
{
}
Batch-get Tile summaries by ID
POST
/tiles/batch-by-id
Batch sibling of GET /tiles/by-id/{tileId}. Ids with no matching Tile are omitted from the response rather than erroring.
Request Body
application/json
{
}
Responses
Tile summaries for the requested ids
application/json
{
}
List published versions for a Tile
GET
/tiles/by-id/{tileId}/versions
Paginated by opaque nextCursor. Ordered newest-first by the Tile registry's per-Tile version_number.
Parameters
Path Parameters
tileId*
Type
Requiredstring
Min Length
1Query Parameters
limit
Type
integer
Minimum
1Maximum
200cursor
Type
string
Max Length
1000Responses
Tile versions result
application/json
{
}
Get Tile details with bundle URL
GET
/tiles/{userHandle}/{tileHandle}
Parameters
Path Parameters
userHandle*
Type
Requiredstring
Min Length
1tileHandle*
Type
Requiredstring
Min Length
1Responses
Tile details result
application/json
{
}
Publish a Tile (cookie or Bearer auth)
Search the public Tile catalog
GET
/tiles/search
Authorizations
Parameters
Query Parameters
query*
Type
Requiredstring
Min Length
1Max Length
500limit
Type
integer
Minimum
1Maximum
50Default
20Responses
Tile search results
application/json
{
}
Download a Tile's source-code bundle (cookie or Bearer auth)
Rename one of your own Tiles.
Leave a top-level Tile instance
POST
/tiles/{typeId}/{instanceId}/leave
Authorizations
Parameters
Path Parameters
typeId*
Type
Requiredstring
Min Length
1Max Length
1000instanceId*
Type
Requiredstring
Min Length
1Max Length
1000Responses
Caller removed from the instance, or already not a member
application/json
{
}
Search the user directory
GET
/users/search
Authorizations
Parameters
Query Parameters
q*
Type
Requiredstring
Min Length
1Max Length
500limit
Type
integer
Minimum
1Maximum
50Default
20include
Type
string
Responses
User search results
application/json
{
}
Get public profile, published Tiles, and recent plays
GET
/profiles/{publicProfileId}
Parameters
Path Parameters
publicProfileId*
Type
Requiredstring
Min Length
1Query Parameters
include
Type
string
Responses
User profile result
application/json
{
}
Get legacy handle-based user profile
GET
/users/{userHandle}
Parameters
Path Parameters
userHandle*
Type
Requiredstring
Min Length
1Query Parameters
include
Type
string
Responses
User profile result
application/json
{
}
Batch-get user info by internal user id
POST
/users/batch-by-id
Ids with no matching user are omitted from the response rather than erroring.
Authorizations
Request Body
application/json
{
}
Responses
User info for the requested ids
application/json
{
}
Search indexed documents
GET
/search
Authorizations
Parameters
Query Parameters
q*
Type
Requiredstring
Min Length
1Max Length
500limit
Type
integer
Minimum
1Maximum
100Default
20Responses
Search results
application/json
{
}
Submit client-reported metrics
POST
/client-metrics
Accepts a bounded batch of client-reported metric events. The server records only metrics registered in its closed registry; invalid events are dropped and counted without rejecting the whole batch.
Authorizations
Request Body
application/json
{
}
Responses
Metrics accepted
Submit client-reported error events
POST
/client-errors
Accepts a bounded batch of client-reported error events, kernel-enriched with host-resolved tile identity. code is an open, free-form string (not a closed registry, unlike client-metrics); invalid events are dropped and counted without rejecting the whole batch.
Authorizations
Request Body
application/json
{
}
Responses
Error events accepted
Submit client-observed tile-metric events
POST
/tile-metrics
Accepts a bounded batch of client-observed tile-metric events (tile_opened / tile_ended / tile_replayed / chat_sheet_opened). The actor is derived from the session; the target (tileTypeId, tileInstanceId) and event details come from the client. Events are forwarded best-effort to the central ingest boundary, which authorizes membership and dedups at read time.
Authorizations
Request Body
application/json
{
}
Responses
Events accepted
Submit an authenticated bug report
POST
/bug-reports
Accepts a bounded user-written bug report and optional root-app local-data dump, persists it (metadata in the database, dump in blob storage), then notifies the configured notifier best-effort.
Authorizations
Request Body
application/json
{
}
Responses
Bug report persisted
application/json
{
}
Synced Store
Operations
GET/synced-store/instances/{typeId}/{instanceId}/infoGET/synced-store/instances/{typeId}/{instanceId}/schemaPOST/synced-store/instances/{typeId}/{instanceId}/upgradePOST/synced-store/instances/{typeId}/{instanceId}/actionPOST/synced-store/instances/{typeId}/{instanceId}/getPOST/synced-store/instances/{typeId}/{instanceId}/scanPOST/synced-store/instances/{typeId}/{instanceId}/mutatePUT/synced-store/instances/{typeId}/{instanceId}/titlePOST/synced-store/instances/{typeId}/{instanceId}/invite
Get store instance version info
GET
/synced-store/instances/{typeId}/{instanceId}/info
Authorizations
Parameters
Path Parameters
typeId*
Type
Requiredstring
Min Length
1instanceId*
Type
Requiredstring
Min Length
1Responses
Store info
application/json
{
}
Get store schema (tables, mutators, actions)
GET
/synced-store/instances/{typeId}/{instanceId}/schema
Authorizations
Parameters
Path Parameters
typeId*
Type
Requiredstring
Min Length
1instanceId*
Type
Requiredstring
Min Length
1Responses
Store schema
application/json
{
}
Upgrade store code version and trigger migrations
POST
/synced-store/instances/{typeId}/{instanceId}/upgrade
Authorizations
Parameters
Path Parameters
typeId*
Type
Requiredstring
Min Length
1instanceId*
Type
Requiredstring
Min Length
1Request Body
application/json
{
}
Responses
Upgrade result
application/json
{
}
Execute a server-side action
POST
/synced-store/instances/{typeId}/{instanceId}/action
Authorizations
Parameters
Path Parameters
typeId*
Type
Requiredstring
Min Length
1instanceId*
Type
Requiredstring
Min Length
1Request Body
application/json
{
}
Responses
Action result
application/json
{
}
Read one user-visible item from a store table
POST
/synced-store/instances/{typeId}/{instanceId}/get
Authorizations
Parameters
Path Parameters
typeId*
Type
Requiredstring
Min Length
1instanceId*
Type
Requiredstring
Min Length
1Request Body
application/json
{
}
Responses
Get result
application/json
{
}
Scan user-visible items from a store table
POST
/synced-store/instances/{typeId}/{instanceId}/scan
Authorizations
Parameters
Path Parameters
typeId*
Type
Requiredstring
Min Length
1instanceId*
Type
Requiredstring
Min Length
1Request Body
application/json
{
}
Responses
Scan result
application/json
{
}
Push mutations to a store instance
POST
/synced-store/instances/{typeId}/{instanceId}/mutate
Authorizations
Parameters
Path Parameters
typeId*
Type
Requiredstring
Min Length
1instanceId*
Type
Requiredstring
Min Length
1Request Body
application/json
{
}
Responses
Mutation result
application/json
{
}
Set the user-facing title of an app instance
PUT
/synced-store/instances/{typeId}/{instanceId}/title
Authorizations
Parameters
Path Parameters
typeId*
Type
Requiredstring
Min Length
1instanceId*
Type
Requiredstring
Min Length
1Request Body
application/json
{
}
Responses
Title set
application/json
{
}
Invite users to play / use an app instance
POST
/synced-store/instances/{typeId}/{instanceId}/invite
Authorizations
Parameters
Path Parameters
typeId*
Type
Requiredstring
Min Length
1instanceId*
Type
Requiredstring
Min Length
1Request Body
application/json
{
}
Responses
Invites dispatched
application/json
{
}
Test-only: seed a store instance with patches
POST
/internal/synced-store/instances/{typeId}/{instanceId}/seed
Authorizations
Parameters
Path Parameters
typeId*
Type
Requiredstring
Min Length
1Max Length
1000instanceId*
Type
Requiredstring
Min Length
1Max Length
1000Request Body
application/json
{
}
Responses
Seed result
application/json
{
}
Test-only: upgrade a store instance's code version and trigger migrations
POST
/internal/synced-store/instances/{typeId}/{instanceId}/upgrade
Authorizations
Parameters
Path Parameters
typeId*
Type
Requiredstring
Min Length
1Max Length
1000instanceId*
Type
Requiredstring
Min Length
1Max Length
1000Request Body
application/json
{
}
Responses
Upgrade result
application/json
{
}
Test-only: run a system mutation on a store instance
POST
/internal/synced-store/instances/{typeId}/{instanceId}/mutate-as-system
Authorizations
Parameters
Path Parameters
typeId*
Type
Requiredstring
Min Length
1Max Length
1000instanceId*
Type
Requiredstring
Min Length
1Max Length
1000Request Body
application/json
{
}
Responses
Mutate-as-system result
application/json
{
}
Reindex every Tile in the registry (backfill).
Seed local-dev users and verified phone identifiers.
In-region delegate: resolve an app's backend bundle metadata
POST
/central/resolve-bundle
Request Body
application/json
{
}
Responses
Bundle metadata, or a structured not-found result
application/json
{
}
In-region delegate: read a user's profile from D1
POST
/central/get-user-info
Request Body
application/json
{
}
Responses
User info, or null when no users row exists
application/json
{
}
In-region delegate: batch-read user profiles from D1
POST
/central/get-user-info-many
Request Body
application/json
{
}
Responses
User info rows aligned with the requested userIds
application/json
{
}
In-region delegate: write search documents to the index
POST
/central/index-search-documents
Request Body
application/json
{
}
Responses
Indexing completed
application/json
{
}
Ingest trust boundary: validate + emit a tile-metric event
POST
/central/tile-metrics
Request Body
application/json
{
}
Responses
Ingest outcome (best-effort; always 200 when well-formed)
application/json
{
}
Process pending GDPR deletion requests (deploy hook)
POST
/run-pending-deletions
Request Body
application/json
{
}
Responses
Per-entry deletion results
application/json
{
}
Run pending deploy-time data-migration jobs (deploy hook)
Create a shareable invite URL for a store instance
POST
/invites
Authorizations
Request Body
application/json
{
}
Responses
Invite created
application/json
{
}
Fetch display info for an invite code
GET
/invites/{code}
Authorizations
Parameters
Path Parameters
code*
Type
Requiredstring
Min Length
1Max Length
1000Responses
Invite info
application/json
{
}
Redeem an invite and join the target instance
Fetch display info for a room invite
GET
/room-invite/{roomId}/{code}
Authorizations
Parameters
Path Parameters
roomId*
Type
Requiredstring
Min Length
1Max Length
1000code*
Type
Requiredstring
Min Length
1Max Length
1000Responses
Room invite info
application/json
{
}
Redeem a room invite (add the caller to the room)
Add a target user as a member of (typeId, instanceId) without a share link
POST
/instance-members
Sibling of POST /invites — gated on the same caller membership check via isLiveInstanceMember. Target gate uses decideMessageSend(caller, target): 'deny' is rejected with a generic 403; 'allow' and 'warn' proceed. Self-add returns 422. The success payload echoes the decision so future UX can render a recipient-side warning for 'warn'.
Authorizations
Request Body
application/json
{
}
Responses
Member added (or already a member)
application/json
{
}
Add multiple target users as members of (typeId, instanceId) by uid
POST
/instance-members/batch
UID-only batch sibling of POST /instance-members. Caller-level failures return one error response; target-level failures are returned per target.
Authorizations
Request Body
application/json
{
}
Responses
Ordered per-target add results
application/json
{
}
Add an agent as an agent-class member of (typeId, instanceId)
POST
/instance-agent-members
Agent-class sibling of POST /instance-members. The caller must be the agent's creator (agent store config.creatorUserId) AND a live member of the room. The agent's display row is built from its config name; membership is written with memberClass: 'agent'. Agent-existence/creator failures collapse to a generic 403.
Authorizations
Request Body
application/json
{
}
Responses
Agent added (or already a member)
application/json
{
}
Remove an agent member from (typeId, instanceId)
POST
/instance-agent-members/remove
Creator-only for v1 (room-admin removal widens later with the allowedAdders policy). Idempotent: returns alreadyRemoved: true when the agent is not a live member. Agent-existence/creator failures collapse to a generic 403.
Authorizations
Request Body
application/json
{
}
Responses
Agent removed (or already not a member)
application/json
{
}
Create an agent owned by the caller (optionally add to a room)
POST
/instance-agents
Server-orchestrated agent creation (replaces the client-first option-C flow). Reserves the per-creator-unique name in the caller's manager registry, initializes the agent store with the caller as creator, and — when room is supplied — adds the agent as a memberClass:'agent' member (caller must be a live member; DMs refused). Duplicate name / full registry → 409.
Authorizations
Request Body
application/json
{
}
Responses
Agent created (and optionally added to the room)
application/json
{
}
Delete an agent: remove it from all rooms + release its name
POST
/instance-agents/delete
Creator-only, like the single-room remove. Marks the registry claim deleting (a terminal state the add path refuses, so the agent cannot be re-added mid-delete), sweeps every room in the agent reverse index with $removeUser, then tombstones the claim (release runs LAST, only when every room removal succeeded — a partial failure returns failedRooms and keeps the deleting claim as the retry ticket; retrying re-attempts exactly the failed rooms). Deleting an already-deleted agent is an idempotent no-op. Agent-existence/creator failures collapse to a generic 403.
Authorizations
Request Body
application/json
{
}
Responses
Sweep result (complete when failedRooms is empty)
application/json
{
}
Create a new chat room and add target users by uid
POST
/chat-rooms
Creates a new chat room with a server-minted instanceId, adds the creator, and adds up to 100 known uid targets. Does not accept phone numbers or contact-discovery inputs.
Authorizations
Request Body
application/json
{
}
Responses
New chat room id and ordered per-target add results
application/json
{
}
Validate tile-end results for an ended tile instance
POST
/rooms/tile-end
Validates the ended tile instance is in a room, the caller is a current room member, and per-player tile-end results mention exactly the current active roster.
Authorizations
Request Body
application/json
{
}
Responses
Tile-end payload accepted
application/json
{
}
List agent tokens for the current user
Create a scoped agent token
Delete an agent token
Look up a user by phone number (admin only)
POST
/admin/users/lookup-by-phone
Admin-only. Returns the full user record for the account that verified the given phone number, or 404 if none.
Authorizations
Request Body
application/json
{
}
Responses
The matched user
application/json
{
}
Search users by display name (admin only)
POST
/admin/users/search-by-display-name
Admin-only. Returns up to limit user records whose display name contains the given substring (case-insensitive). Backed by an unindexed scan; not intended to be efficient.
Authorizations
Request Body
application/json
{
}
Responses
The matched users (possibly empty)
application/json
{
}
Add a phone number to the login allowlist (admin only)
POST
/admin/login-allowlist/add
Admin-only. Adds a phone number to the pre-launch login allowlist. Idempotent: re-adding an existing number is a no-op, signalled by already_present: true in the response.
Authorizations
Request Body
application/json
{
}
Responses
Number added (or already present)
application/json
{
}
Remove a phone number from the login allowlist (admin only)
POST
/admin/login-allowlist/remove
Admin-only. Removes a phone number from the pre-launch login allowlist. No-op if absent.
Authorizations
Request Body
application/json
{
}
Responses
Number removed
application/json
{
}
List the login allowlist (admin only)
GET
/admin/login-allowlist
Admin-only. Returns the current pre-launch login allowlist, newest-first. The list is bounded (small early-access set).
Authorizations
Responses
Current allowlist entries (possibly empty)
application/json
{
}
List persisted bug reports, newest first (admin only)
GET
/admin/bug-reports
Admin-only. Cursor-paginated: pass the previous page's nextCursor back as cursor. Accepts session-cookie or Bearer (Poe API key) auth.
Authorizations
Parameters
Query Parameters
limit
Type
integer
Minimum
1Maximum
100cursor
Type
string
Max Length
256Responses
The newest bug reports (possibly empty)
application/json
{
}
Fetch a persisted bug report by id (admin only)
GET
/admin/bug-reports/{id}
Admin-only. Returns the persisted bug report, including the reporter's local-data dump when one was submitted. Accepts session-cookie or Bearer (Poe API key) auth.
Authorizations
Parameters
Path Parameters
id*
Type
Requiredstring
Min Length
1Max Length
128Responses
The persisted bug report
application/json
{
}
Me
Operations
GET/me/contacts/{targetUserId}PUT/me/contacts/{targetUserId}DELETE/me/contacts/{targetUserId}GET/me/contactsPOST/me/contacts/by-phonePOST/me/contacts/{targetUserId}/phone-checkGET/me/dev-hostsPOST/me/dev-hostsPUT/me/blocks/{targetUserId}DELETE/me/blocks/{targetUserId}GET/me/blocksPUT/me/direct-messages/{targetUserId}PUT/me/mutes/{targetUserId}DELETE/me/mutes/{targetUserId}GET/me/mutesGET/me/relationshipsGET/me/shared-rooms/{targetUserId}GET/me/room-peoplePATCH/me/profilePUT/me/presencePOST/me/profile-pictureDELETE/me/profile-picturePOST/me/onboarding/completePOST/me/phone/start-verificationPOST/me/phone/confirm-verificationGET/me/phoneGET/me/poe-linkDELETE/me/poe-linkPOST/me/poe-link/api-keyPOST/me/poe-link/pay-with-points/enableGET/me/poe-bot-accessGET/me/agentsGET/me/subscriptionPOST/me/subscription/checkoutPOST/me/subscription/portal
Get a single contact with the target user's public profile
GET
/me/contacts/{targetUserId}
Returns 200 + { edge, user } when an active contact edge exists, 404 otherwise. Gated on the caller having an active contact edge — the only single-uid lookup that returns a target user's profile.
Authorizations
Parameters
Path Parameters
targetUserId*
Type
Requiredstring
Min Length
1Max Length
255Responses
Contact with target user profile
application/json
{
}
Add a contact
PUT
/me/contacts/{targetUserId}
Idempotent. Re-adding an already-active contact returns 200 with the same updatedAt (no reordering). Gated on the caller and target both currently being members of (typeId, instanceId) — returns a single generic 403 if the target doesn't exist, the caller isn't a member, or the target isn't a member (all three collapse to the same response to avoid leaking uid existence or instance membership). In this product model, A's outgoing contacts list is A's send-permission list (see decideMessageSend in @lib/message-send), so unbounded uid-add would be unbounded send authorization.
Authorizations
Parameters
Path Parameters
targetUserId*
Type
Requiredstring
Min Length
1Max Length
255Request Body
application/json
{
}
Responses
Contact added (or already active)
application/json
{
}
Remove a contact
DELETE
/me/contacts/{targetUserId}
Idempotent. Returns 200 + the soft-removed edge when a row was active; returns 204 (no body) when nothing was removed (row missing or already removed).
Authorizations
Parameters
Path Parameters
targetUserId*
Type
Requiredstring
Min Length
1Max Length
255Responses
Contact removed
application/json
{
}
List the caller's outgoing contacts
GET
/me/contacts
Paginated by opaque nextCursor. Newest activity first (most recent updatedAt first). Each row inlines the target user's public profile (user). Rows whose target user row is missing (e.g. deleted account) are silently dropped, so a page can be shorter than limit and still have a nextCursor — treat the absence of nextCursor (not row count) as end-of-pages.
Authorizations
Parameters
Query Parameters
limit
Type
integer
Minimum
1Maximum
200cursor
Type
string
Max Length
1000Responses
Page of contacts
application/json
{
}
Bulk-add contacts by verified phone numbers
POST
/me/contacts/by-phone
Syncs a bounded phonebook batch into the caller's contacts. The server normalizes submitted phone numbers, resolves verified Poe users, adds outgoing contact edges where allowed, and returns hydrated contact rows for the matched phones. Failure reasons are collapsed by omission: malformed numbers, unregistered phones, self-adds, blocks, missing users, and contact-cap exhaustion are not distinguished per phone. The route only consumes rate-limit tokens for contacts it attempts to add.
Authorizations
Request Body
application/json
{
}
Responses
Hydrated contacts that correspond to submitted phone numbers and are now active contacts.
application/json
{
}
Check whether a contact is in a submitted device address book
POST
/me/contacts/{targetUserId}/phone-check
Returns whether targetUserId's verified phone is among the submitted phone numbers. Gated on the caller having an active contact edge to targetUserId (404 otherwise), returns only a boolean, and writes nothing — so it neither exposes the phone->user_id mapping nor re-adds contacts the way by-phone does. Powers the 'this contact will be re-added by sync' warning before removing a phone-synced contact.
Authorizations
Parameters
Path Parameters
targetUserId*
Type
Requiredstring
Min Length
1Max Length
255Request Body
application/json
{
}
Responses
Whether the target is reachable via one of the submitted phones.
application/json
{
}
List the caller's recently pushed dev / preview hosts
GET
/me/dev-hosts
Employee-only. Newest-first, capped at MAX_DEV_HOSTS_PER_USER. Populated by bun dev / /preview pushing to this user's account.
Authorizations
Responses
The caller's recent dev hosts
application/json
{
}
Push (upsert) a dev / preview host for the caller
POST
/me/dev-hosts
Employee-only. Idempotent per url: re-pushing the same URL floats it to the top. The model prunes back to MAX_DEV_HOSTS_PER_USER newest and drops entries older than the TTL.
Authorizations
Request Body
application/json
{
}
Responses
Host upserted
application/json
{
}
Add a block
PUT
/me/blocks/{targetUserId}
Idempotent. Re-adding an already-active block returns 200 with the same updatedAt (no reordering). Side effect: any active forward (caller → target, contact) edge is soft-removed (matches Twitter/X / Instagram block-auto-unfollows convention).
Authorizations
Parameters
Path Parameters
targetUserId*
Type
Requiredstring
Min Length
1Max Length
255Responses
Block added (or already active)
application/json
{
}
Remove a block
DELETE
/me/blocks/{targetUserId}
Idempotent. Returns 200 + the soft-removed edge when a row was active; returns 204 (no body) when nothing was removed (row missing or already removed).
Authorizations
Parameters
Path Parameters
targetUserId*
Type
Requiredstring
Min Length
1Max Length
255Responses
Block removed
application/json
{
}
List the caller's outgoing blocks
GET
/me/blocks
Paginated by opaque nextCursor. Newest activity first (most recent updatedAt first).
Authorizations
Parameters
Query Parameters
limit
Type
integer
Minimum
1Maximum
200cursor
Type
string
Max Length
1000Responses
Page of blocks
application/json
{
}
Materialize the canonical direct message room
PUT
/me/direct-messages/{targetUserId}
Idempotent. On first call materializes the canonical DM instance for (caller, target) (writes participant rows into $users + $userInfo); subsequent calls no-op and return the same instanceId. Self materializes the caller's note-to-self DM; other-user DMs are gated on the caller having an active outgoing contact edge to the target — same send-permission model as decideMessageSend. Returns 403 (generic 'Cannot start DM') for missing contact edge, unknown target, or any block in either direction — distinguishing them would leak uid existence / block-state.
Authorizations
Parameters
Path Parameters
targetUserId*
Type
Requiredstring
Min Length
1Max Length
255Responses
DM instance materialized (or already exists)
application/json
{
}
Add a mute
PUT
/me/mutes/{targetUserId}
Idempotent. Re-adding an already-active mute returns 200 with the same updatedAt (no reordering).
Authorizations
Parameters
Path Parameters
targetUserId*
Type
Requiredstring
Min Length
1Max Length
255Responses
Mute added (or already active)
application/json
{
}
Remove a mute
DELETE
/me/mutes/{targetUserId}
Idempotent. Returns 200 + the soft-removed edge when a row was active; returns 204 (no body) when nothing was removed (row missing or already removed).
Authorizations
Parameters
Path Parameters
targetUserId*
Type
Requiredstring
Min Length
1Max Length
255Responses
Mute removed
application/json
{
}
List the caller's outgoing mutes
GET
/me/mutes
Paginated by opaque nextCursor. Newest activity first (most recent updatedAt first).
Authorizations
Parameters
Query Parameters
limit
Type
integer
Minimum
1Maximum
200cursor
Type
string
Max Length
1000Responses
Page of mutes
application/json
{
}
Bulk lookup of outgoing edge state toward multiple users
GET
/me/relationships
Returns one row per unique userId in the ids param. Phantom IDs return all-false. ids is comma-separated; up to 50 unique IDs per request.
Authorizations
Parameters
Query Parameters
ids*
Type
Requiredstring
Min Length
1Max Length
12800Responses
Per-target relationship summary
application/json
{
}
App instances the caller and the target both belong to
GET
/me/shared-rooms/{targetUserId}
Returns the instances both the caller and targetUserId are currently members of, newest-first by the caller's join time, capped at the server maximum. Returns an empty list (not an error) for the self case and when either party has blocked the other. Materialized 1:1 DM instances are included so contact-add affordances can use them as shared-instance proof.
Authorizations
Parameters
Path Parameters
targetUserId*
Type
Requiredstring
Min Length
1Max Length
255Responses
Shared rooms (instance IDs only)
application/json
{
}
People from the caller's recent rooms
GET
/me/room-people
One cursor-paginated page of distinct other members of the caller's recent small, non-DM instances, newest-first by the caller's join time. Each person carries the hydrated user, an isContact flag, a representative sharedInstance (the add-gating context for PUT /me/contacts), and lastSharedAt. Blocked/blocking users (either direction) are excluded; excludeContacts=true also drops existing contacts. nextCursor is null once the caller's instances are exhausted; an empty people with a non-null nextCursor is valid (a page of all-DM/all-crowd/solo instances) — keep paging. Cross-page dedup is the client's job.
Authorizations
Parameters
Query Parameters
cursor
Type
string
Max Length
1000excludeContacts
Type
string
Max Length
8Responses
One page of room people
application/json
{
}
Update the caller's editable profile fields
PATCH
/me/profile
description, show_playing, and display_name are all optional and updated independently when present. An empty body is a 200 no-op. Empty / whitespace-only description clears the bio (stored as NULL); empty / whitespace-only display_name is rejected (400). description is capped at PROFILE_DESCRIPTION_MAX_LENGTH, display_name at DISPLAY_NAME_MAX_LENGTH.
Authorizations
Request Body
application/json
{
}
Responses
Profile updated; returns the canonical user record.
application/json
{
}
Record the caller as present now (global last-seen)
PUT
/me/presence
Body-less. Server-stamps the caller's global last_seen_at (coalesced, so a rapid repeat is a no-op). Read back — gated by symmetric share_last_seen — via GET /me/contacts.
Authorizations
Responses
Presence recorded (or coalesced no-op).
application/json
{
}
Upload the caller's profile picture
POST
/me/profile-picture
Raw image bytes in the request body; Content-Type declares the format (image/png, image/jpeg, or image/webp). Validated and stored as a self-hosted /blobs/{hash}.{ext} URL on the user's record. Rejects unsupported types (unsupported_image_type), oversized payloads (image_too_large), and bytes that don't match the declared type (invalid_image).
Authorizations
Responses
Profile picture set; returns the canonical user record.
application/json
{
}
Remove the caller's profile picture
DELETE
/me/profile-picture
Clears the caller's profile picture (sets it to null). Idempotent — clearing an already-absent picture returns 200.
Authorizations
Responses
Profile picture cleared; returns the canonical user.
application/json
{
}
Mark the caller's onboarding (NUX) as complete
POST
/me/onboarding/complete
Stamps users.onboarded_at so the onboarding gate stops showing the NUX. Idempotent — a repeat call keeps the original timestamp. No request body.
Authorizations
Responses
Onboarding marked complete; returns the user record.
application/json
{
}
Start phone verification
POST
/me/phone/start-verification
Initiates SMS-OTP verification for the supplied E.164 phone number. Returns the verification's expiry timestamp; the actual code arrives via SMS. Subject to per-IP (pre-auth) and per-user (post-auth) rate limits.
Authorizations
Request Body
application/json
{
}
Responses
Verification started; SMS sent
application/json
{
}
Confirm phone verification
POST
/me/phone/confirm-verification
Submits the SMS code; on success, the caller's verified phone is registered in the identifier index. Code validation is delegated to the provider; a local phone_verifications row binds the verification to the originating user_id so a different authed user can't claim a code that legitimately reached the target phone.
Authorizations
Request Body
application/json
{
}
Responses
Code accepted; phone now verified for the caller
application/json
{
}
Get the caller's verified phone status
GET
/me/phone
Returns whether the caller has a verified phone and, if so, the canonical E.164 string. The full E.164 is returned because it's the user's own self-disclosed identifier (displayed in account settings, same trust class as their email).
Authorizations
Responses
Phone status
application/json
{
}
Get linked Poe account state
GET
/me/poe-link
Authorizations
Responses
Poe link state
application/json
{
}
Unlink the current user's Poe account
Link a Poe account with a Poe API key
POST
/me/poe-link/api-key
Authorizations
Request Body
application/json
{
}
Responses
Linked Poe account state
application/json
{
}
Enable pay-with-points for the linked Poe account
POST
/me/poe-link/pay-with-points/enable
Authorizations
Responses
Updated Poe link state
application/json
{
}