Anjal LinkedIn — Sessions API

Login on behalf of the customer (email + password, with PIN or mobile-app verification challenge support), or import existing cookies. Cookies are AES-256-GCM encrypted at rest. Each session is tenant-scoped.

Operations 8

POST /linkedin/sessions Login (may challenge) #
GET /linkedin/sessions List sessions #
POST /linkedin/sessions/verify Submit PIN to complete a challenge #
POST /linkedin/sessions/poll Collect mobile-app approval #
POST /linkedin/sessions/import Import existing cookies #
GET /linkedin/sessions/{id} Get session metadata #
DELETE /linkedin/sessions/{id} Revoke session #
GET /linkedin/sessions/utilization Per-session quota utilization #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/anjal-app:anjal-app-linkedin-sessions-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

anjal-app-linkedin-sessions-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Anjal LinkedIn — Sessions API
  version: 1.0.0
  description: 'Anjal is a multi-tenant email API for AI agents.


    Create inboxes on the default `@send.anjal.app` domain or your own custom

    domains, send and receive mail, manage drafts, subscribe to real-time

    events over WebSocket, and isolate resources across workspaces so your

    reseller customers can serve their own end-customers under a single

    billing relationship.


    Install the TypeScript SDK: `npm install anjal`

    (npm)


    MCP server for Claude Desktop / Cursor: `npx -y anjal-mcp`

    (npm)


    ## Authentication


    Every request is authenticated with the `X-API-Key` header (or

    `Authorization: Bearer `). Four key prefixes, each with increasing

    scope:


    | Prefix | Scope | Use it for |

    |----------|-----------------------------------------|-----------------------------------------|

    | `sa_...` | Super admin | Tenant lifecycle (platform operator) |

    | `tk_...` | Tenant master | Everything in your tenant across workspaces |

    | `wk_...` | Workspace master | One workspace only |

    | `ak_...` | Inbox (agent) scoped | One inbox — its messages, drafts, lists |


    ## Resource hierarchy


    ```

    Tenant (billing root)

    └── Workspace (isolation container)

    ├── Inboxes

    │ ├── Messages / Threads / Attachments

    │ ├── Drafts

    │ └── Lists

    └── Domains

    ```


    ## Sending mail


    Mail is sent via `POST /v1/inboxes/{id}/send`. Replies go through

    `/reply/{messageId}`, reply-all through `/reply-all/{messageId}`, and

    forwards through `/forward/{messageId}`. The `From` header always uses

    the inbox''s **primary alias** — add a custom-domain address with

    `POST /v1/inboxes/{id}/addresses` to change it.


    ### Safe retries (Idempotency-Key)


    Every send endpoint (`/send`, `/reply`, `/reply-all`, `/forward` and

    `POST /v1/inboxes/{id}/drafts/{did}/send`) accepts an `Idempotency-Key`

    header. Send a unique value (a UUID works) and retry freely: the first

    request sends and stores its response; a retry with the same key and the

    identical request returns that stored response with the header

    `Idempotent-Replayed: true` and sends nothing.


    | Situation | Result |

    |---|---|

    | Same key, different request (body, inbox or endpoint) | `409 IDEMPOTENCY_KEY_REUSED` |

    | Same key while the first request is still running | `409 IDEMPOTENCY_IN_PROGRESS` |

    | Header present but blank, or longer than 255 characters | `400 IDEMPOTENCY_KEY_INVALID` |

    | The first send failed (non-2xx) | The key is freed; retry with the same key |


    Keys are scoped to your account and expire after 24 hours.


    ### Labels, open tracking, scheduled send


    - **Labels**: pass `labels` (array, or a comma-separated string) on any

    send, reply, reply-all, forward or draft. Change them later with

    `PATCH /v1/inboxes/{id}/messages/{messageId}` or

    `PATCH /v1/inboxes/{id}/threads/{threadId}`, and filter lists with

    `?labels=a,b`. Labels are stored lowercase and deduplicated; at most 32

    per message, each at most 64 characters, no commas.

    - **Open tracking**: `trackOpens: true` on a send with an HTML body adds a

    1x1 pixel (the stored copy never contains it). The first open fires

    `message.opened`. Opens are a hint, not proof: Apple Mail Privacy

    Protection fetches images on delivery, and clients that block images

    never report an open.

    - **Scheduled send**: set `sendAt` on a draft and Anjal sends it at that

    time. See the Drafts endpoints.

    - **Delivery status**: sent messages carry `deliveryStatus`

    (`delivered`, `bounced`, `complained` or `rejected`; null until known)

    and `deliveredAt`.


    ## Receiving mail


    Inbound mail arrives via SMTP on the default domain or any verified

    custom domain. Subscribe to delivery events either by registering a

    webhook (`POST /v1/webhooks`) or by opening a WebSocket

    (`wss://anjal.app/v1/ws`) — useful for in-line 2FA flows where

    the agent doesn''t have a public webhook receiver.'
servers:
- url: https://anjal.app
  description: Production
security:
- apiKeyHeader: []
tags:
- name: LinkedIn — Sessions
  description: 'Login on behalf of the customer (email + password, with PIN or mobile-app

    verification challenge support), or import existing cookies. Cookies are

    AES-256-GCM encrypted at rest. Each session is tenant-scoped.'
paths:
  /linkedin/sessions:
    post:
      tags:
      - LinkedIn — Sessions
      summary: Login (may challenge)
      description: 'Begin a LinkedIn login on behalf of the customer using their email +

        password. Returns `{ ok: true, sessionId }` on direct success, or

        `{ challenge: true, challengeId, challengeType }` if LinkedIn requires

        a PIN (email/SMS) or mobile-app approval. Follow up with

        `/sessions/verify` (PIN) or `/sessions/poll` (mobile approval).'
      operationId: linkedinLogin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - email
              - password
              properties:
                email:
                  type: string
                  format: email
                password:
                  type: string
                  format: password
                label:
                  type: string
                  description: Optional friendly label for this session.
      responses:
        '200':
          description: Either a successful login or a challenge requirement.
          content:
            application/json:
              schema:
                oneOf:
                - type: object
                  properties:
                    ok:
                      type: boolean
                      enum:
                      - true
                    sessionId:
                      type: string
                    label:
                      type: string
                      nullable: true
                    createdAt:
                      type: string
                      format: date-time
                - type: object
                  properties:
                    ok:
                      type: boolean
                      enum:
                      - false
                    challenge:
                      type: boolean
                      enum:
                      - true
                    challengeId:
                      type: string
                    challengeType:
                      type: string
                    verifyEndpoint:
                      type: string
                    pollEndpoint:
                      type: string
                    expiresAt:
                      type: string
                      format: date-time
        '401':
          description: Invalid email/password.
        '402':
          description: LinkedIn module not subscribed.
        '403':
          description: Session limit reached.
    get:
      tags:
      - LinkedIn — Sessions
      summary: List sessions
      operationId: linkedinListSessions
      responses:
        '200':
          description: Array of session metadata (cookies are never returned).
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  sessions:
                    type: array
                    items:
                      type: object
                      properties:
                        id:
                          type: string
                        label:
                          type: string
                          nullable: true
                        workspaceId:
                          type: string
                          nullable: true
                        status:
                          type: string
                        createdAt:
                          type: string
                          format: date-time
                        lastUsedAt:
                          type: string
                          format: date-time
                          nullable: true
  /linkedin/sessions/verify:
    post:
      tags:
      - LinkedIn — Sessions
      summary: Submit PIN to complete a challenge
      description: Submit the email/SMS PIN LinkedIn sent to the customer.
      operationId: linkedinVerifyPin
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - challengeId
              - pin
              properties:
                challengeId:
                  type: string
                pin:
                  type: string
                label:
                  type: string
      responses:
        '200':
          description: Session created.
        '401':
          description: Wrong PIN.
        '404':
          description: Challenge not found or expired.
  /linkedin/sessions/poll:
    post:
      tags:
      - LinkedIn — Sessions
      summary: Collect mobile-app approval
      description: 'For mobile-approval challenges, after the customer taps "I''ve approved"

        in the LinkedIn mobile app. Returns 202 + `pending: true` if not yet

        approved (try again in a few seconds).'
      operationId: linkedinPollMobileApproval
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - challengeId
              properties:
                challengeId:
                  type: string
                label:
                  type: string
      responses:
        '200':
          description: Session created.
        '202':
          description: Approval not yet processed.
        '404':
          description: Challenge not found or expired.
  /linkedin/sessions/import:
    post:
      tags:
      - LinkedIn — Sessions
      summary: Import existing cookies
      description: 'Escape hatch for customers who already extracted `li_at` + `JSESSIONID`

        from a logged-in browser. Skip when you can use `/sessions` instead.'
      operationId: linkedinImportSession
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - liAt
              - jsessionId
              properties:
                liAt:
                  type: string
                jsessionId:
                  type: string
                label:
                  type: string
      responses:
        '200':
          description: Session created.
  /linkedin/sessions/{id}:
    parameters:
    - name: id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    get:
      tags:
      - LinkedIn — Sessions
      summary: Get session metadata
      operationId: linkedinGetSession
      responses:
        '200':
          description: Session metadata (cookies never returned).
        '404':
          description: Not found.
    delete:
      tags:
      - LinkedIn — Sessions
      summary: Revoke session
      operationId: linkedinRevokeSession
      responses:
        '200':
          description: Revoked.
        '404':
          description: Not found or already revoked.
  /linkedin/sessions/utilization:
    get:
      tags:
      - LinkedIn — Sessions
      summary: Per-session quota utilization
      description: 'Returns each active LinkedIn session for the tenant with its

        action counts in the trailing 24 hours and the remaining daily

        budget per action type. Drives the "Today''s utilization" panel

        in the dashboard and lets you decide when connecting another

        LinkedIn account would add headroom.


        Sessions used: search_signal (recurring polls), search_history

        (one-shot lookups), profile_lookup (Pass-2 verification),

        connection_request, send_message, post_like.'
      operationId: linkedinSessionUtilization
      responses:
        '200':
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  budget:
                    type: object
                    additionalProperties:
                      type: integer
                    description: Per-action daily budget per session (the operator's safety cap).
                  sessions:
                    type: array
                    items:
                      $ref: '#/components/schemas/LinkedInSessionUtilization'
components:
  schemas:
    LinkedInSessionUtilization:
      type: object
      properties:
        sessionId:
          type: string
        label:
          type: string
          nullable: true
        status:
          type: string
        rateLimitedUntil:
          type: string
          format: date-time
          nullable: true
        rateLimitReason:
          type: string
          nullable: true
        counts:
          type: object
          additionalProperties:
            type: integer
          description: 'Action → count of calls in the trailing 24h. Keys: search_signal, search_history, profile_lookup, connection_request, send_message, post_like.'
        remaining:
          type: object
          additionalProperties:
            type: integer
          description: Action → remaining daily budget for this session.
  securitySchemes:
    apiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key
      description: 'Any of `sa_`, `tk_`, `wk_`, `ak_`, or `mam_` keys. Scope is enforced

        per-endpoint — see individual operation descriptions.

        '