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-posts-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: 3.2.0
info:
title: Anjal LinkedIn — Posts 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 — Posts
paths:
/linkedin/posts:
post:
tags:
- LinkedIn — Posts
summary: Publish a new post on the connected account's feed
description: 'Creates a feed post on the connected LinkedIn account. Text is
required; an inline image is optional. The endpoint handles the
underlying three-step LinkedIn upload flow (register upload → PUT bytes →
create share) so the customer just sends one JSON request.
**Visibility:**
- `ANYONE` (default) — public on the feed.
- `CONNECTIONS` — visible to 1st-degree connections only.
**Image:**
Pass an `image.dataUrl` (full data URL with embedded mime + base64) for
the simplest agent shape, OR a separate `image.base64` + `image.contentType`.
Accepts `image/png`, `image/jpeg`, `image/gif`, `image/webp` up to ~10 MB.
Quota: 1 `connection_request` slot per call (shared outbound-write
bucket).'
operationId: linkedinCreatePost
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- sessionId
- text
properties:
sessionId:
type: string
text:
type: string
minLength: 1
maxLength: 3000
description: Post commentary. LinkedIn caps at 3000 characters.
visibility:
type: string
enum:
- ANYONE
- CONNECTIONS
default: ANYONE
image:
type: object
description: 'Optional inline image. Pass either `dataUrl` (preferred — embeds
mime + base64 in one field) or `base64` + `contentType`.
'
properties:
dataUrl:
type: string
description: Full data URL, e.g. "data:image/png;base64,iVBORw0KG..."
base64:
type: string
description: Bare base64-encoded bytes (no `data:` prefix).
contentType:
type: string
description: Required when using `base64`. Ignored when using `dataUrl`.
enum:
- image/png
- image/jpeg
- image/gif
- image/webp
altText:
type: string
maxLength: 420
description: Accessibility alt-text on the rendered post.
filename:
type: string
maxLength: 255
responses:
'200':
description: Post created.
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
shareUrn:
type: string
example: urn:li:share:7455538267644641280
shareId:
type: string
postUrl:
type: string
format: uri
mediaUrn:
type: string
nullable: true
description: Set when an image was uploaded.
'400':
description: 'Validation error or LinkedIn rejected the post. Real LinkedIn
errors are surfaced verbatim in `error` (e.g. commentary too
long, invalid visibility enum).
'
'404':
description: Session not found.
'429':
description: Daily quota exceeded for this session.
/linkedin/posts/by-author:
get:
tags:
- LinkedIn — Posts
summary: List recent posts by a profile or company
description: 'Pull the most recent posts authored by a LinkedIn profile or
company page. Same helper the engagement-signal runner uses
internally; exposed as a GET so agents can do one-shot lookups
("show me Acme''s last 5 posts") without spinning up a recurring
signal.
`profileUrl` accepts either a profile URL
(`https://www.linkedin.com/in//`) or a company URL
(`https://www.linkedin.com/company//`). The route resolves
to a `urn:li:fsd_profile:` or `urn:li:fsd_company:` URN
automatically.
Quota: each call burns one `engagement_poll` slot from the
per-session daily budget.'
operationId: linkedinListPostsByAuthor
parameters:
- name: sessionId
in: query
required: true
schema:
type: string
- name: profileUrl
in: query
required: true
schema:
type: string
- name: count
in: query
schema:
type: integer
minimum: 1
maximum: 20
default: 20
- name: sinceUrn
in: query
schema:
type: string
description: Watermark — return only posts whose activityUrn is newer than this.
responses:
'200':
description: Author identity + array of posts (newest first).
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
author:
type: object
properties:
kind:
type: string
enum:
- profile
- company
urn:
type: string
label:
type: string
count:
type: integer
posts:
type: array
items:
type: object
properties:
activityUrn:
type: string
example: urn:li:activity:7332661864792854528
postedAt:
type: integer
nullable: true
description: Unix ms; null when LinkedIn omits it.
excerpt:
type: string
'400':
description: Could not resolve the profile/company URL.
'404':
description: Session not found.
'429':
description: Daily quota exceeded for this session.
/linkedin/posts/{postUrn}/comments:
get:
tags:
- LinkedIn — Posts
summary: List comments on a post
description: 'Fetch commenters on a LinkedIn post. Returns the engager array
(profile URN, name, headline, comment text), plus the total
reported by LinkedIn. Same helper the engagement signal uses
to enumerate commenters on tracked actor posts.
`postUrn` is the activity URN — `urn:li:activity:`. URL-
encode it (the `:` characters need to be `%3A` in path
position).
Quota: one `engagement_poll` slot per call.'
operationId: linkedinListPostComments
parameters:
- name: postUrn
in: path
required: true
schema:
type: string
description: URL-encoded urn:li:activity:<id>
- name: sessionId
in: query
required: true
schema:
type: string
- name: count
in: query
schema:
type: integer
minimum: 1
maximum: 50
default: 20
responses:
'200':
description: Commenters + total comment count reported by LinkedIn.
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
postUrn:
type: string
total:
type: integer
nullable: true
description: LinkedIn-reported total. May be null if the count call failed.
count:
type: integer
description: Number of commenters returned in this page.
comments:
type: array
items:
type: object
properties:
profileUrn:
type: string
slug:
type: string
name:
type: string
headline:
type: string
commentText:
type: string
postedAt:
type: integer
nullable: true
'404':
description: Session not found.
'429':
description: Daily quota exceeded for this session.
/linkedin/posts/{postUrn}/reactions:
get:
tags:
- LinkedIn — Posts
summary: List reactions on a post
description: 'Fetch reactors on a LinkedIn post. Returns each reactor''s
profile URN, name, headline, and reaction type
(`LIKE` / `CELEBRATE` / `SUPPORT` / `LOVE` / `INSIGHTFUL` /
`FUNNY`).
Quota: one `engagement_poll` slot per call. LinkedIn''s per-call
rate-limiter cares about call count, not response size — large
`count` values are no more expensive than small ones.'
operationId: linkedinListPostReactions
parameters:
- name: postUrn
in: path
required: true
schema:
type: string
description: URL-encoded urn:li:activity:<id>
- name: sessionId
in: query
required: true
schema:
type: string
- name: count
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 100
responses:
'200':
description: Array of reactors.
content:
application/json:
schema:
type: object
properties:
ok:
type: boolean
postUrn:
type: string
count:
type: integer
reactions:
type: array
items:
type: object
properties:
profileUrn:
type: string
slug:
type: string
name:
type: string
headline:
type: string
reactionType:
type: string
example: LIKE
'404':
description: Session not found.
'429':
description: Daily quota exceeded for this session.
components:
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.
'