HTTP API
Base URL: https://api.pigeonsms.aldi.best
Every response is JSON. Errors look like { "error": { "code": "forbidden", "message": "…", "request_id": "…" } } and carry the matching HTTP status. Ids are snowflake strings, timestamps are epoch milliseconds, and field names are snake_case.
Authentication
curl -X POST https://api.pigeonsms.aldi.best/auth/login \
-H 'content-type: application/json' \
-d '{"login":"you","password":"…","device_name":"curl"}'
Returns { token, user }. Send it as Authorization: Bearer <token> on everything else. Bots use Authorization: Bot PGB.<bot_id>.<secret> instead — see Bots.
Accounts are invite-only: GET /auth/invite/:code checks a code, POST /auth/signup redeems it. Two-factor lives at /auth/totp/{setup,enable,disable}; a login that needs it fails with totp_required until you resend with totp.
Conversations
| Method | Path | What it does |
|---|---|---|
GET |
/dms |
your DM list with unread counts and last message |
POST |
/dms/open |
{ user_id } → the DM channel id, creating it if needed |
GET |
/channels/:id/messages |
a page of messages, ?before=<seq> to paginate |
POST |
/channels/:id/messages |
send; { content, nonce, reply_to?, attachment?, ttl?, send_at? } |
PATCH |
/messages/:id |
edit |
DELETE |
/messages/:id |
delete (tombstoned, fanned out) |
PUT/DELETE |
/messages/:id/reactions/:emoji |
react / unreact |
PUT/DELETE |
/messages/:id/pin |
pin / unpin |
PUT |
/channels/:id/read |
{ seq } — move your read marker |
POST |
/channels/:id/typing |
typing indicator, fire and forget |
nonce is a client-generated uuid: it makes sends idempotent and lets the client match its optimistic bubble to the server's copy. ttl (seconds) makes a message disappear; a future send_at (epoch ms) schedules it instead, and GET /scheduled lists what is pending.
Nests
GET /spaces returns your nests with their channels and unread counts. POST /spaces creates one, POST /spaces/:id/channels adds a channel (text, voice or forum), POST /spaces/:id/invites mints an SPC-… code, and GET /spaces/invites/:code/preview shows what a code points at without consuming it.
Members live under /spaces/:id/members — the list carries each member's base role, their custom role_ids and an is_bot flag. Roles are /spaces/:id/roles with a permission bitfield; GET /spaces/:id/permissions resolves what you may do, optionally for one channel. Moderation is DELETE /spaces/:id/members/:userId (kick) and POST /spaces/:id/bans.
Custom emoji and stickers: /spaces/:id/emojis, plus /spaces/emojis/mine for everything you can use anywhere.
Forums and threads
Forum channels reject the plain message endpoints. Use /channels/:id/forum/posts (?sort=active|recent|oldest, ?tag=), /channels/:id/forum/posts/:postId for a post and its replies, and /channels/:id/forum/posts/:postId/replies to answer. Threads hang off any message: POST /channels/:id/threads with { message_id }, then /threads/:id/messages.
Search
GET /search?q= searches every nest you are in plus your DMs. GET /spaces/:id/search?q= scopes to one nest, GET /channels/:id/search?q= to one channel. Encrypted messages are skipped server-side.
Media
POST /media/upload?filename=&type= takes raw bytes and returns an attachment. Files over 5 MiB should go through the resumable path instead: POST /uploads opens a session, PUT /uploads/:id/parts/:n sends each part, POST /uploads/:id/complete finishes. Avatars and banners have their own endpoints under /media. Anything stored is served from /media/:key.
Realtime
wss://api.pigeonsms.aldi.best/gateway?token=<session token>
Frames are { "t": "message.new", "d": { … } }. Types include message.new, message.edit, message.delete, reaction.add, reaction.remove, typing, read, mention.new, forum.post, forum.reply, forum.like, poll.update, pin.add, pin.remove, super_pin.set, channel.new, channel.update, channel.delete, space.update, friend.request, friend.accept and — for bot tokens — interaction.create. Send ping every 25 seconds; the server answers pong.
Calls use a separate socket per channel: wss://…/calls/:channelId/ws?mode=voice|video&token=…, relaying offer, answer, ice, mute and camera between participants.
Notifications
GET /notifications is the inbox, PUT /notifications/read clears it, and /notifications/preferences holds per-scope modes (all, mentions, mute) with sound, vibration and badge switches. Browsers subscribe to push with POST /push/web after fetching the VAPID key from GET /push/web/key; Android registers an FCM token at POST /push/tokens.
Rate limits
A shared quota per user, drained faster by heavier actions: sending a message costs 1, a reaction 2, a forum post 3. Over the line you get 429 rate_limited with Retry-After. pigeonsms.js paces requests locally so you rarely see one.