Base URL: https://firefly.lupyd.com. Bodies are protobuf messages from
package firefly (crates/protos/protobufs/message.proto in the
firefly repository) unless noted. Protobuf
responses set Content-Type: application/x-protobuf; proto=firefly.<Message>.
Unless marked otherwise, every endpoint requires
Authorization: Bearer <access-token> (see Authentication).
- List parameters are separated by
%2C (a percent-encoded comma). Query values are
not otherwise URL-decoded.
- Write endpoints return
200 with an empty body unless a response is listed.
- Errors are plain text:
| Status |
Meaning |
400 |
Missing or invalid parameter, or an MLS validation failure (for example a stale commit). |
401 |
Invalid token or missing permission. |
403 |
Not allowed — not a group member, insufficient group permission, or plan limit reached. |
404 |
Not found, or unknown path. |
405 |
Method not allowed on this path. |
408 |
Request exceeded the server timeout (30 seconds by default). |
409 |
Conflict — for example a snapshot head changed or a re-add was already claimed. |
413 |
Body exceeds the endpoint’s limit. |
429 |
Too many requests for this resource. |
| Method and path |
Body |
Response |
Notes |
GET /jwks.json |
— |
JWKS (JSON) |
No auth. Firefly’s ES256 credential-signing keys. |
POST /user/device |
Address (8 KiB) |
Address |
Register a device; returns its id and deviceId. |
GET /user/devices |
— |
UserDevices |
|
GET /user/addresses |
— |
Addresses |
|
DELETE /user/device?id=<address_id> |
— |
— |
Removes the device and closes its session. |
POST /sign?device_id=&address_id= |
raw public key (4 KiB) |
SignedToken |
Issue a device credential. |
| Method and path |
Body |
Response |
Notes |
POST /user/preKeyBundles |
PreKeyBundleEntries (512 KiB) |
— |
Upload Signal prekey bundles. |
GET /user/preKeyBundles?other=<username> |
— |
PreKeyBundleEntries |
A user’s bundles. Add ids= to restrict to addresses. |
GET /user/preKeyBundles?ids=<address_ids> |
— |
PreKeyBundleEntries |
Bundles for specific addresses. |
GET /user/preKeyBundles?id=<address_id>[&onlyIds=true] |
— |
PreKeyBundleEntries |
Your own bundles. |
DELETE /user/preKeyBundles?addressId=&ids= |
— |
— |
|
DELETE /user/allPreKeyBundles |
— |
— |
|
POST /user/conversation?other=&settings= |
— |
— |
Start a conversation. |
GET /user/conversations |
— |
Conversations |
|
DELETE /user/conversations?ids= |
— |
— |
|
DELETE /user/messages?until=<message_id> |
— |
— |
Delete stored messages up to an ID. |
DELETE /user/message?conversationId=&messageId= |
— |
— |
Delete one stored message. |
Messages themselves are sent over the real-time connection.
Control who may message and call you. An admin variant with me=<username> exists for
server-to-server use.
| Method and path |
Body |
Response |
Notes |
GET /user/privacy |
— |
MessageRequestSettings |
|
PUT /user/privacy |
MessageRequestSettings (1 KiB) |
— |
Set requests_disabled. POST is also accepted. |
POST /user/accept?other=<username> |
— |
— |
Accept a user. |
DELETE /user/accept?other=<username> |
— |
— |
Revoke acceptance. |
GET /user/accepted |
— |
AcceptedUsers |
|
POST /user/accepted |
AcceptedUsers (512 KiB) |
count (text) |
Accept many users at once. |
POST /user/block?other=<username>&blocked=true |
— |
— |
Block or unblock (blocked=false). Also accepts a JSON body { other, blocked }. |
Usernames here are 3–30 characters of A–Z, a–z, 0–9, or _.
| Method and path |
Body |
Response |
Notes |
POST /group |
Group (512 KiB) |
Group { id } |
Create a group; state is the MLS GroupInfo. |
GET /group?id= |
— |
Group |
Name, description, settings, owner. |
GET /groups |
— |
Groups |
Groups you belong to. |
POST /group/update |
Group, or query id, name, description (64 KiB) |
— |
Rename or re-describe. |
DELETE /group?id= |
— |
— |
|
POST /group/upgrade?id=[&upgraded=true] |
— |
— |
Upgrade a group (requires a Pro or Organization plan). |
GET /group/policy?id= |
— |
JSON |
Effective plan limits for the group. |
GET /group/members/status?groupId= |
— |
GroupMembersOnlineStatus |
|
POST /group/history/sync?id=&enabled= |
— |
— |
Turn history sharing on or off. |
| Method and path |
Body |
Response |
POST /group/keyPackages?address= |
GroupKeyPackages (512 KiB) |
— |
GET /group/keyPackages?address_id=&device_id= |
— |
GroupKeyPackages (your own) |
GET /group/keyPackages?other=<username>&all=true |
— |
GroupKeyPackages (one per device) |
GET /group/keyPackages?other=<username>&address= |
— |
GroupKeyPackage |
DELETE /group/keyPackages?address=&ids= |
— |
— |
| Method and path |
Body |
Response |
Notes |
POST /group/commitAndWelcome |
GroupCommitAndWelcome (4 MiB) |
GroupMessage { id, epoch, groupId } |
Add members. |
POST /group/commit |
GroupMessage (512 KiB) |
GroupMessage { id, epoch, groupId } |
Any other commit. |
GET /group/commits?groupIds=&startAfter=&limit= |
— |
GroupMessages |
limit ≤ 100. Use groupId= for a single group. |
GET /group/syncCommits?address=[&cursor_group_id=&cursor_epoch=&limit=] |
— |
ServerMessage |
Paged commits across all of a device’s groups. |
POST /group/sync?address=&limit= |
GroupSyncRequests (512 KiB) |
GroupMessages |
Fetch messages after per-group cursors. |
POST /group/syncUpdate?address= |
GroupMemberUpdates (512 KiB) |
— |
Report sync progress for many groups. |
POST /group/member?address= |
GroupMemberUpdate (512 B) |
— |
Report your epoch and last message seen. |
DELETE /group/member?uname=&groupId=&address= |
— |
— |
Remove a member device’s record from the group. |
GET /group/invites?address= |
— |
GroupInvites |
Pending Welcomes. |
DELETE /group/invites?groupIds=&address= |
— |
— |
Dismiss invites. |
GET /group/messages?groupId=&startAfter=&startBefore=[&ascending] |
— |
GroupMessages |
Page through stored ciphertext. |
| Method and path |
Response |
Notes |
POST /group/reAdd?address=[&groupIds=] |
— |
Request to be re-added; group IDs in the query or as a JSON array body. |
GET /group/reAdds?groupIds=&address= |
GroupReAddRequests |
Pending requests in your groups. |
POST /group/reAdd/claim?groupId=&address=&myAddress= |
claimed |
Claim a request before re-adding. 409 if someone else claimed it. |
DELETE /group/reAdd?groupId=&address=&myAddress= |
— |
Clear a request. |
Encrypted snapshot blobs. The body is ciphertext (application/octet-stream); metadata
travels in headers.
| Method and path |
Limit |
Notes |
GET /group/pinned/{groupId} |
— |
Returns the current snapshot with an X-Snapshot-Id header. |
POST /group/pinned/{groupId} |
64 KiB |
Headers: X-Snapshot-Id, X-Expected-Snapshot, X-Message-Count. |
GET /group/messages_chunk/{groupId}/{id} |
— |
{id} is 32 hex characters. |
POST /group/messages_chunk/{groupId}/{id} |
200 KiB |
Also X-Start-Id, X-End-Id. |
GET /direct-pins/{username} |
— |
Pinned messages in a 1:1 conversation. |
POST /direct-pins/{username} |
64 KiB |
Also X-Ciphertext-Sha256 (64 hex characters); X-Message-Count 0–50. |
Writes return stored. 409 means X-Expected-Snapshot no longer matches the current
head — fetch, merge, and retry. Groups are limited to 16 snapshot writes per minute and
256 MiB of snapshot storage (429 beyond either).
| Method and path |
Body |
Response |
Notes |
GET /webrtcConfig |
— |
JSON RTCConfiguration |
ICE/TURN servers valid for one hour. |
POST /meeting/create |
CreateMeetingRequest (16 KiB) |
{ session, participant_token } |
Start a group meeting. |
POST /meeting/join |
JoinMeetingRequest |
{ session, participant_token } |
|
POST /meeting/leave |
LeaveMeetingRequest |
— |
|
POST /meeting/end |
EndMeetingRequest |
— |
|
GET /meeting/active?groupId=&channelId= |
— |
GetActiveSessionResponse |
|
| Method and path |
Body |
Response |
Notes |
GET /backups/direct |
— |
DirectBackupState |
Current backup state. |
GET /backups/direct?id= |
— |
protobuf |
One backup record. |
POST /backups/direct |
DirectBackupRequest (2 MiB) |
stored |
lease, put, commit, and release actions. 409 on a lease conflict. |
GET /backups/direct/object-policy?id=&method=&size=&hash= |
— |
204 or 403 |
Authorise a CDN upload or download. |
Blobs are at most 34 MiB and each account may store up to 512 MiB.