Inbox

The inbox provides conversation thread management and direct message retrieval. An inbox item represents a conversation thread between a user and a contact on a specific platform, tracking unread count, archive state, and per-user pin state.

All endpoints require authentication via the X-API-Key header. See Authentication.

Conversation thread model

Each inbox item is uniquely identified by a combination of userId + contactId + platform. Inbox items are created and updated automatically when direct messages are sent or received — there is no explicit create endpoint.

Each inbox item tracks:

  • unreadCount — number of unread messages in the thread.
  • unreadFixed — whether the thread was manually marked unread by the user who owns the inbox. It keeps the thread in the unread view after unreadCount reaches 0. No API key endpoint sets it; it is raised from the Stacked app.
  • archived — whether the thread is archived.
  • isPinned — a per-user map (Record<userId, boolean>). Each user has independent pin state on the same inbox item.

Type filters

The type query parameter on inbox items determines the view:

TypeDescription
unreadItems with unreadCount greater than 0 or unreadFixed set (not archived).
inboundItems with at least one inbound message (not archived).
outboundItems with only outbound messages (not archived).
archivedArchived items.
pinnedItems pinned by the current user.

List inbox channels

GET /inboxes

Returns available inbox channels for the authenticated user based on their configuration.

Channel types:

  • stacked-sms — users with a stacked phone configured.
  • twitter — users with Twitter DMs enabled.
  • web-chat — if web-chat is configured for the org.

Example request

curl https://<host>/api/inboxes \
  -H "X-API-Key: your-api-key"

Response

{
  "success": true,
  "data": [
    {
      "_id": "stacked-sms/675a...",
      "name": "John Smith",
      "userId": "675a...",
      "type": "stacked-sms",
      "channel": "+15551234567",
      "profileImg": null
    }
  ]
}

List inbox items

GET /inboxes/items

Returns conversation threads with populated contact, user, and message data.

Query parameters

ParameterTypeDefaultNotes
userIdstring—Required. User ID to fetch inbox items for.
platformstring—Filter by platform.
typestring—unread, inbound, outbound, archived, or pinned.
searchstring—Case-insensitive search on contact name.
pagenumber00-indexed.
perPagenumber25Items per page.

Example request

curl "https://<host>/api/inboxes/items?userId=675a...&type=unread&perPage=25" \
  -H "X-API-Key: your-api-key"

Response

{
  "success": true,
  "data": [
    {
      "_id": "675b...",
      "userId": "675a...",
      "contactId": "664a...",
      "platform": "stacked-sms",
      "unreadCount": 3,
      "archived": false,
      "isPinned": {},
      "lastMessageTimestamp": 1716000000000,
      "contact": {
        "_id": "664a...",
        "firstName": "Jane",
        "lastName": "Doe"
      },
      "user": {
        "_id": "675a...",
        "firstName": "John",
        "lastName": "Smith"
      },
      "lastMessage": {
        "_id": "676a...",
        "body": "Hey, checking in!",
        "direction": "in",
        "timestamp": 1716000000000
      },
      "lastInboundMessage": { "..." : "..." },
      "lastOutboundMessage": null
    }
  ],
  "pagination": {
    "page": 0,
    "perPage": 25,
    "totalItems": 12,
    "totalPages": 1
  }
}

Find an inbox item

GET /inboxes/items/find

Finds a specific inbox item by user, contact, and platform.

Query parameters

ParameterTypeDefaultNotes
userIdstring—Required. User ID.
contactIdstring—Required. Contact ID.
platformstring—Platform.

Example request

curl "https://<host>/api/inboxes/items/find?userId=675a...&contactId=664a...&platform=stacked-sms" \
  -H "X-API-Key: your-api-key"

Response

{
  "success": true,
  "data": {
    "_id": "675b...",
    "userId": "675a...",
    "contactId": "664a...",
    "platform": "stacked-sms",
    "unreadCount": 3,
    "archived": false,
    "isPinned": {},
    "contact": { "..." : "..." },
    "user": { "..." : "..." },
    "lastMessage": { "..." : "..." }
  }
}

Mark inbox item as read

POST /inboxes/items/mark-read

Sets the unreadCount to 0 for an inbox item.

An API key acts as a dedicated API user belonging to the organization, not as the person who created the key, and reaches every inbox in that organization. That API user never owns an inbox itself, so unreadFixed is never cleared: only unreadCount is zeroed, and a thread its owner marked unread stays in their unread view.

Request body

FieldTypeRequiredNotes
idstringYesInbox item ID.

Example request

curl -X POST https://<host>/api/inboxes/items/mark-read \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{ "id": "675b..." }'

Response

{
  "success": true,
  "message": "Success",
  "error": false
}

Errors

StatusMessageCause
400Validation errorInvalid ObjectId format.
404Not foundInbox item not found in your org.

Archive or unarchive an inbox item

POST /inboxes/items/archive

Request body

FieldTypeRequiredNotes
idstringYesInbox item ID.
valuebooleanYestrue to archive, false to unarchive.

Example request

curl -X POST https://<host>/api/inboxes/items/archive \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{ "id": "675b...", "value": true }'

Response

{
  "success": true,
  "message": "Success",
  "error": false
}

Errors

StatusMessageCause
400Validation errorInvalid ObjectId or value not boolean.
404Not foundInbox item not found in your org.

Pin or unpin an inbox item

POST /inboxes/items/pin

Pin state is per-user — each user has independent pin state on the same inbox item. Returns the updated inbox item with full lookups.

Request body

FieldTypeRequiredNotes
idstringYesInbox item ID.
valuebooleanYestrue to pin, false to unpin.

Example request

curl -X POST https://<host>/api/inboxes/items/pin \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{ "id": "675b...", "value": true }'

Response

{
  "success": true,
  "message": "Success",
  "data": {
    "_id": "675b...",
    "userId": "675a...",
    "contactId": "664a...",
    "platform": "stacked-sms",
    "unreadCount": 0,
    "archived": false,
    "isPinned": { "675a...": true },
    "contact": { "..." : "..." },
    "user": { "..." : "..." },
    "lastMessage": { "..." : "..." }
  }
}

Errors

StatusMessageCause
400Validation errorInvalid ObjectId or value not boolean.
404Not foundInbox item not found in your org.

Get conversation messages

GET /inboxes/conversations

Returns direct messages for a conversation between a user and a contact, sorted by timestamp descending (newest first).

Query parameters

ParameterTypeDefaultNotes
userIdstring—Required. User ID.
contactIdstring—Required. Contact ID.
platformstring or string[]—Single platform or array of platforms.
pagenumber00-indexed.
perPagenumber30Messages per page.

Example request

curl "https://<host>/api/inboxes/conversations?userId=675a...&contactId=664a...&platform=stacked-sms" \
  -H "X-API-Key: your-api-key"

Response

{
  "success": true,
  "data": [
    {
      "_id": "676a...",
      "direction": "in",
      "body": "Hey, checking in!",
      "media": null,
      "platform": "stacked-sms",
      "status": "received",
      "from": "+15559876543",
      "to": "+15551234567",
      "timestamp": 1716000000000
    },
    {
      "_id": "676b...",
      "direction": "out",
      "body": "Thanks for reaching out!",
      "media": ["https://storage.googleapis.com/..."],
      "platform": "stacked-sms",
      "status": "sent",
      "from": "+15551234567",
      "to": "+15559876543",
      "timestamp": 1715999000000
    }
  ],
  "pagination": {
    "page": 0,
    "perPage": 30,
    "totalItems": 47,
    "totalPages": 2
  }
}

Response envelope

All endpoints use the standard envelope:

{
  "error": false,
  "success": true,
  "message": "...",
  "code": 200,
  "data": "...",
  "pagination": {}
}

Error responses:

{
  "error": true,
  "success": false,
  "message": "...",
  "code": 400,
  "data": null
}

data is null for a general failure, such as a database error or an upstream timeout. When the request is rejected because of a specific field, data carries the field(s) at fault instead:

{
  "error": true,
  "success": false,
  "message": "phone: Phone parse failed: The string supplied did not seem to be a phone number (input: <masked>)",
  "code": 400,
  "data": {
    "issues": [
      {
        "path": "phone",
        "message": "Phone parse failed: The string supplied did not seem to be a phone number (input: <masked>)"
      }
    ]
  }
}

Each issue carries a path (the dotted path into the request body) and a message. A label is added when the path is not self-describing, which today means custom fields only, for example "path": "fields.68b0a1f39c2e4d0012ab77e5", "label": "Class Year". issues is an array, but treat it as holding at most one entry: validation stops at the first failure.

On this page