Messages

Post, list, reply to, react to, and cross-post messages. The feed combines public messages, your own messages, and messages from people you follow.

Endpoint table

MethodPathAuthDescription
GET/api/messagesPublic (optional auth)List messages. Unauthenticated returns public messages; a logged-in session/token personalises the feed. Query: limit, offset, onlyMine (bool), tag (string).
GET/api/messages/searchSession or BearerSearch top-level messages by content (case-insensitive substring), scoped to your feed visibility. Query: q (required, 1–200 chars), limit (default 20, max 100), offset (default 0), onlyMine (bool, default false).
POST/api/messagesSession or BearerCreate a message (see body reference below). Free for a plain text post; a subscription is required only when the post includes cross-posting, image/video, or scheduling. Returns 201.
GET/api/messages/:idPublic (optional auth)Get one message by ID. A public message is viewable without auth.
PATCH/api/messages/:idSession or BearerUpdate a future-scheduled message (scheduledAt, scheduledCrossPostConfig).
DELETE/api/messages/:idSession or BearerDelete a message (own messages only). Cascades replies and blob media. Returns 409 if other posts still push this message (remove those pushes first).
GET/api/messages/scheduledSession or BearerUpcoming scheduled messages. Query: range (today, week, or month, default month).
GET/api/messages/:id/repliesPublic (optional auth)Replies to a message. Returns 200 with no auth for a publicly-visible message; a private message's replies require the owner's session cookie.
POST/api/messages/:id/digSession or BearerAdd an I Dig! reaction (idempotent). Returns { digCount, dugByMe, isNewDig } (isNewDig is false when you had already dug it).
DELETE/api/messages/:id/digSession or BearerRemove your dig.
POST/api/messages/:id/reportSession or BearerReport a message. Body: { reason, detail? }.
POST/api/messages/images/uploadSession or BearerUpload an image for attachment. Subscriber only.
POST/api/messages/videos/uploadSession or BearerUpload a video for attachment. Subscriber only.

Listing messages

GET /api/messages?onlyMine=true&limit=2&offset=0
Authorization: Bearer 3f1c9e...<64 hex chars>...a8
{
  "messages": [
    {
      "id": "msg_001",
      "content": "Just shipped a new feature!",
      "publiclyVisible": true,
      "tags": ["dev", "release"],
      "digCount": 4,
      "dugByMe": false,
      "replyCount": 1,
      "createdAt": "2025-06-10T14:22:00.000Z",
      "user": { "id": "...", "username": "yourhandle", "displayName": "Your Name", "avatar": null }
    }
  ],
  "pagination": { "total": 42, "limit": 2, "offset": 0, "hasMore": true }
}

The list is returned under the top-level messages key (not data), and each message's author is under user (with an avatar field): there is no author/avatarUrl.

Searching messages

GET /api/messages/search?q=feature&limit=20&onlyMine=false
Authorization: Bearer 3f1c9e...<64 hex chars>...a8

Matching is a case-insensitive substring over message content, restricted to top-level messages (replies excluded) and scoped to your feed visibility (honors your viewingPreference). Each item uses the same message shape as GET /api/messages. The response wraps results in { "messages": [ ... ], "pagination": { "total", "limit", "offset", "hasMore" } }. An empty or over-200-character q, or a limit above 100, returns 400.

Creating a message

Body fields

FieldTypeDescription
contentstringRequired unless pushing with no comment. Max length from your account setting (default 666 chars).
publiclyVisiblebooleanDefaults to your account's default visibility.
parentIdstringReply to this message ID. Mutually exclusive with pushedMessageId.
pushedMessageIdstringRepost this public message ID. content may be empty (plain repost) or non-empty (repost with comment).
tagsstring[]Free-form string labels. Case-sensitive; lowercase recommended. To discover or autocomplete existing public tags, see the tag-discovery endpoints in Utility Endpoints.
imageUrlsstring[]Up to 8 URLs from POST /api/messages/images/upload. Subscriber only.
videoUrlsstring[]1 URL from POST /api/messages/videos/upload. Subscriber only.
scheduledAtstring (ISO 8601)Future datetime to publish. Cannot combine with parentId or pushedMessageId. Subscriber only.
mastodonProviderIdsstring[]Linked Mastodon identity IDs (from GET /api/user/identities) to cross-post to. Subscriber only.
crossPostToBlueskybooleanCross-post to linked Bluesky account. Subscriber only.
crossPostToLinkedInbooleanCross-post to linked LinkedIn account. Subscriber only.
crossPostToTwitterbooleanCross-post to linked X (Twitter) account. Subscriber only.

Simple public message

POST /api/messages
Authorization: Bearer 3f1c9e...<64 hex chars>...a8
Content-Type: application/json

{ "content": "Hello from the API!", "publiclyVisible": true, "tags": ["api"] }

Reply

POST /api/messages
Content-Type: application/json

{ "content": "Great point.", "publiclyVisible": true, "parentId": "msg_001" }

Scheduled cross-post (subscriber)

POST /api/messages
Content-Type: application/json

{
  "content": "Launching at 9 AM tomorrow!",
  "publiclyVisible": true,
  "scheduledAt": "2025-09-01T13:00:00.000Z",
  "crossPostToBluesky": true,
  "crossPostToTwitter": true,
  "mastodonProviderIds": ["clx9abc000002"]
}

Creation response

The 201 response has this shape:

{
  "message": "Message created successfully",
  "data": { "id": "msg_003", "content": "...", "publiclyVisible": true, "createdAt": "..." },
  "scheduledAt": "2025-09-01T13:00:00.000Z",
  "crossPostResults": [ ... ],
  "crossPosts": [ ... ],
  "org": { ... }
}
  • data: the created message object.

  • scheduledAt: present only for scheduled posts (echoed back).

  • org: present only when the post targets an organization.

  • crossPosts: always present (may be empty). Each entry:

    {
      "platform": "mastodon",
      "providerId": "clx9abc000002",
      "status": "ok",
      "externalUrl": "https://mastodon.social/@you/123",
      "error": null
    }
    

    platform is one of "mastodon" | "bluesky" | "linkedin" | "twitter" and is always present here. providerId, externalUrl, and error are optional; status is "ok" | "failed" | "pending".

  • crossPostResults: present only when non-empty. Each entry:

    {
      "providerId": "clx9abc000002",
      "instanceName": "mastodon.social",
      "success": true,
      "url": "https://mastodon.social/@you/123",
      "error": null,
      "errorCode": null,
      "statusCode": 200
    }
    

    This array has no platform field. url, error, errorCode, and statusCode are optional.

Guidance: read the platform from crossPosts[].platform, not from crossPostResults[].

Uploading images and videos

Two-step flow: upload first, then reference the URL in imageUrls / videoUrls.

POST /api/messages/images/upload
Content-Type: multipart/form-data

file=<binary>

Response: { "url": "https://cdn.interlinedlist.com/images/abc123.webp" }

Image constraints: JPEG/PNG/WebP/GIF; auto-resized to 1200 px max side; ≤ 1.4 MB output.
Video constraints: MP4/WebM/QuickTime/AVI; max 3 MB; no server transcoding.

Cross-posting details

Cross-posting only works when the corresponding provider identity is linked to your account. See Authentication & OAuth for linking flows. For Mastodon, use the id of the identity from GET /api/user/identities (not the handle). LinkedIn additionally supports posting to organization pages: see LinkedIn Integration and Organizations.

Reactions and replies

POST /api/messages/msg_001/dig          # → { "digCount": 5, "dugByMe": true, "isNewDig": true }
DELETE /api/messages/msg_001/dig        # → { "digCount": 4, "dugByMe": false }
GET /api/messages/msg_001/replies       # → array of reply messages