# Madnion LinkedIn API — native V2 inbox pagination

Updated 30 October 2026. Read-only: no sends, campaigns, publishing, mark-read
updates, recovery changes or webhook activation are required.

Canonical guide: https://www.madnion.com/docs/linkedin-api#inbox-pagination

## Authentication and scope

```http
POST https://api.madnion.com/functions/v1/linkedin-api/actions
Content-Type: application/json
apikey: <existing Madnion platform key>
x-service-account-key: <this app's backend service-account key>
```

Use AdvisorPro V2's own credential for `app_advisorpro_ai_vercel`, not the
existing production app's key. Keep secrets backend-only. Required read actions
must be granted at app and shared-account levels. Preserve the current customer
workspace and account authorization; never change customer scope to bypass an
error. `key_name` is the assigned credential label, not its secret.

## 1. Discover inboxes

```json
{
  "app_id": "app_advisorpro_ai_vercel",
  "app_workspace_id": "g97tz5",
  "key_name": "UnipileV2",
  "account_id": "acc_01m5a9g94jeh0v9q9wg6tkssc7",
  "action": "listInboxesV2",
  "params": {},
  "data": {}
}
```

Use an actual inbox ID from this account's response. `CLASSIC_PRIMARY` below is
the existing authorized Joshua test inbox, not a value to invent for all accounts.
Do not reuse legacy V1 inbox IDs or an inbox belonging to a different account.

## 2. First chat page

```json
{
  "app_id": "app_advisorpro_ai_vercel",
  "app_workspace_id": "g97tz5",
  "key_name": "UnipileV2",
  "account_id": "acc_01m5a9g94jeh0v9q9wg6tkssc7",
  "action": "listChats",
  "params": { "inbox_id": "CLASSIC_PRIMARY" },
  "data": {}
}
```

## 3. Next chat page

```json
{
  "app_id": "app_advisorpro_ai_vercel",
  "app_workspace_id": "g97tz5",
  "key_name": "UnipileV2",
  "account_id": "acc_01m5a9g94jeh0v9q9wg6tkssc7",
  "action": "listChats",
  "params": { "inbox_id": "CLASSIC_PRIMARY" },
  "data": { "cursor": "EXACT_CURSOR_RETURNED_BY_PREVIOUS_CHAT_PAGE" }
}
```

**Move `params.cursor` to `data.cursor`.** `params` is exclusively for path IDs.
`params.cursor` and `params.limit` produce HTTP 400 `Unexpected path parameter`
before provider dispatch. These inbox actions do not use the search-specific
top-level `query` object. Madnion converts `data.cursor` into the upstream GET
query parameter and URL-encodes it once; callers must not pre-encode the cursor.

## 4. Message history pagination

```json
{
  "app_id": "app_advisorpro_ai_vercel",
  "app_workspace_id": "g97tz5",
  "key_name": "UnipileV2",
  "account_id": "acc_01m5a9g94jeh0v9q9wg6tkssc7",
  "action": "listMessages",
  "params": { "chat_id": "CHAT_ID_RETURNED_FOR_THIS_ACCOUNT" },
  "data": { "cursor": "EXACT_CURSOR_FROM_PREVIOUS_MESSAGE_PAGE" }
}
```

Omit `data.cursor` for the first message page. Do not use a chat-list cursor for
message history or reuse one chat's cursor in another chat.

Native aliases `listInboxChatsV2` and `listChatMessagesV2` use the same placement
(`params.inbox_id` / `params.chat_id`, respectively; `data.cursor`), but each
alias requires its own explicit action grants.

Optional page size is `data.limit`, an integer 1–100 at the Madnion boundary.
Provider-specific limits/defaults still apply. Omit it to preserve the provider's
default. The successful first page returning 20 chats does not establish a
universal default or maximum. Do not combine cursor and offset paging.

## Responses and safe polling

Responses preserve native provider pagination, inside `data` when wrapped.
Read the continuation field returned by the specific action; do not assume all
V2 actions return the same cursor field or item-array shape. This guide defines
request placement, not a newly normalized response envelope.

- Keep account, app, customer workspace, inbox/chat and filters unchanged per run.
- Treat cursors as opaque; stop when continuation is absent or repeats. Bound
  each run and retain a checkpoint only for its exact scope.
- Upsert chats by account + chat ID and messages by account + chat ID + message
  ID within the app/customer tenant. Deduplicate webhook and polling ingestion.
- Keep successful earlier pages if a later page fails. Mark coverage incomplete;
  a 400/429/timeout is not an empty inbox and must not delete saved conversations.
- Respect `Retry-After` and governor refusals. Use bounded jitter/backoff for
  transient read errors; do not repeatedly retry an unchanged invalid request.
- Use the existing approved scheduled read fallback. This does not establish a
  new polling allowance, activate webhooks or authorize any outbound action.

## AdvisorPro read-only verification checklist

1. Use V2's own backend key and the existing authorized g97tz5/Joshua account.
2. Verify inbox discovery, page one, then page two with `data.cursor`.
3. Read one returned chat's messages and a second page if a cursor exists. If
   none exists, report that pagination test as not applicable, not a failure.
4. Check repeated-page deduplication, bounded loops and retained earlier pages.
5. Test 400/429/transient failure handling and cross-tenant denial using local
   mocks/fixtures; do not intentionally exhaust a live account's limits.
6. Confirm correct inbox placement with no send, updateChat/mark-read, campaign,
   SDR, publishing, webhook subscription or recovery changes.
7. Return UTC times, action, sanitized path IDs, page/item counts, request IDs,
   HTTP status and whether continuation was present. Do not share credentials,
   opaque cursor values, message bodies or private participant information.

Route-construction tests cover valid/invalid cursor placement and opaque cursor
encoding without contacting LinkedIn. End-to-end page-two success still needs
the authenticated read-only verification in AdvisorPro.
