LinkedIn shows a document (PDF, PowerPoint or Word) as a carousel readers swipe through, page by page, under the post. Send the file as multipart/form-data with the profile in user and platform[]=linkedin.
A first document
curl -X POST https://api.reelspan.com/v1/posts/document \
-H "Authorization: Bearer $REELSPAN_API_KEY" \
-H "Idempotency-Key: q3-report-post" \
-F "user=my-brand" \
-F "platform[]=linkedin" \
-F "title=Q3 report" \
-F "description=Our quarter in 12 slides: what grew, what didn't, and what's next." \
-F "[email protected]"import { Reelspan } from 'reelspan';
import { readFile } from 'node:fs/promises';
const reelspan = new Reelspan();
const post = await reelspan.posts.document({
user: 'my-brand',
platforms: ['linkedin'],
title: 'Q3 report',
description: "Our quarter in 12 slides: what grew, what didn't, and what's next.",
document: { data: await readFile('q3-report.pdf'), name: 'q3-report.pdf', type: 'application/pdf' },
});from reelspan import Reelspan
reelspan = Reelspan()
post = reelspan.posts.document(
"my-brand", "q3-report.pdf", "Q3 report",
description="Our quarter in 12 slides: what grew, what didn't, and what's next.",
)The document
- Format: PDF, PowerPoint (
.pptx,.ppt) or Word (.docx,.doc). Reelspan reads the type from the file's own bytes, not its name orContent-Type. Other files, spreadsheets included, are refused with 400UNSUPPORTED_MEDIA. - Size: up to 100 MB. LinkedIn takes up to 300 pages.
- Where from: a file in
document, a public https URL indocument(Google Drive and Dropbox share links work), ordocument_media_idfrom an upload made withkind=document. Without any of them the answer is 400MEDIA_REQUIRED.
curl -X POST https://api.reelspan.com/v1/posts/document \
-H "Authorization: Bearer $REELSPAN_API_KEY" \
-F "user=my-brand" \
-F "platform[]=linkedin" \
-F "title=Q3 report" \
-F "document=https://example.com/q3-report.pdf"Title and post text
The two texts do different jobs:
titlenames the document; LinkedIn shows it on the document itself. It is required (400TITLE_REQUIREDwithout it) and is cut to 400 characters.document_titlenames the document differently from the post'stitleif you need both.- The post text above the document is
linkedin_description, elsedescription, elselinkedin_title, elsetitle.
LinkedIn only
Documents go to LinkedIn only. Naming any other network gets 400 UNSUPPORTED_POST_TYPE and nothing is posted. The sandbox network doesn't take documents either, so to try this without publishing, use a test key with a LinkedIn account connected to the profile.
Settings
| Field | What it does |
|---|---|
visibility |
PUBLIC (the default), CONNECTIONS, LOGGED_IN or CONTAINER, passed to LinkedIn as given. linkedin_visibility works too. |
first_comment |
Posted as the first comment once the post is live. linkedin_first_comment works too. |
target_linkedin_page_id |
Post as a LinkedIn Company Page instead of the member. |
Reelspan doesn't have LinkedIn's company-page access yet, so
GET /v1/linkedin/pagesreturns an empty list for now and posting as a Company Page may be refused by LinkedIn. Posting as the connected member works.
The response
LinkedIn processes the document before it can be posted, which can take a while for a long document. When it finishes within the request, the LinkedIn result also names the document:
{
"success": true,
"request_id": "a3e0c5d7b9f14e2a8c6b0d4f2e8a1c3b",
"results": {
"linkedin": {
"success": true,
"url": "https://www.linkedin.com/feed/update/urn:li:share:72…/",
"post_id": "urn:li:share:72…",
"document_urn": "urn:li:document:D4E…",
"content_type": "application/pdf",
"file_size": 2481152,
"filename": "q3-report.pdf"
}
},
"usage": { "count": 5, "limit": null, "last_reset": "2026-11-01T00:00:00.000Z" }
}When it takes longer than 59 seconds, or you sent async_upload=true, the answer has a message and the request_id; follow it with Post status or a webhook.
Later or repeated
scheduled_date with timezone, add_to_queue=true, or repeat_every with repeat_times work as for every other post; a scheduled answer is 202 with a job_id. See Scheduled posts.