API Docs/Reference/Create Draft
API Reference

Create Draft

Create a new post draft. Attach media by setting post_type and one of media_url or media_urls. Requires a paid Creator or Lifetime plan.

POSThttps://api.linkedmash.com/v1/posts

POST /v1/posts creates a LinkedIn post draft in LinkedMash. content is the only required field; everything else — title, format, and the media fields — is optional. The draft is created unscheduled, so nothing is published by this call, and the returned id is what you pass to the schedule, update and delete endpoints.

To attach media, upload or re-host it first with POST /v1/media/upload and pass the returned URL as media_url (or several as media_urls), together with post_type and media_mime_type. Passing a LinkedIn-hosted or short-lived URL directly is the usual cause of a draft that publishes without its image.

Example request

Authenticate with a bearer token in the Authorization header. Create a key on the API page in your LinkedMash settings and export it as LINKEDMASH_API_KEY before running this.

curl
curl -X POST "https://api.linkedmash.com/v1/posts" \
  -H "Authorization: Bearer $LINKEDMASH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "5 lessons from shipping every week for a year:",
    "title": "Weekly shipping lessons",
    "format": "text"
  }'

Header parameters

AuthorizationstringRequired

Your secret API key as a Bearer token.

Body parameters

contentstringRequired

The post body text.

titlestringOptional

An optional title for the draft.

formatstringOptional

Post format: text, image, video, article, or poll.

post_typestringOptional

Media type to attach: text, image, video, or pdf. Set this when attaching media.

media_urlstringOptional

Public URL of an image, video, or PDF to attach.

media_urlsarrayOptional

Multiple public media URLs to attach.

media_mime_typestringOptional

MIME type of the attached media (e.g., 'image/png').

Response

{
  "status": true,
  "data": {
    "id": "drf_8f3a21c9",
    "content": "5 lessons from shipping every week for a year:",
    "title": "Weekly shipping lessons",
    "format": "text",
    "status": "draft",
    "created_at": "2026-06-22T09:14:02.118Z",
    "updated_at": "2026-06-22T09:14:02.118Z"
  }
}

Response fields

Every response is wrapped in the same envelope: a status flag and a data payload, with meta present where there is pagination or a plan limit to report.

statusboolean

true on success. Every LinkedMash API response carries this envelope flag alongside data, so a client can branch on it without re-reading the HTTP status.

data.idstring

Draft identifier. Pass it to GET, PATCH and DELETE /v1/posts/{id} and to the schedule route.

data.contentstring | object

The post body. Drafts written in the Studio editor can store this as an object with a text field rather than a bare string — handle both.

data.titlestring

Internal working title. It is never published to LinkedIn; it exists so drafts are findable.

data.formatstring

Composition format, e.g. text or carousel.

data.statusstring

One of draft, scheduled or published.

data.created_atstring

ISO-8601 creation timestamp.

data.updated_atstring

ISO-8601 timestamp of the last edit.

From idea to scheduled post

Creating a draft is step one of four. Nothing publishes until you call the schedule route.

1

Upload media first, if there is any

POST /v1/media/upload returns a stable URL. Doing this before the draft exists means you can attach it in one call rather than creating and then patching.

2

Create the draft

Send content, plus title and format if you want them. Keep the returned data.id — it is required by every later step and there is no way to look a draft up by content.

3

Revise with PATCH

Iterating on a hook is PATCH /v1/posts/{id}, not a new create. Creating a second draft for each revision is the most common way these libraries fill with near-duplicates.

4

Schedule when it is ready

POST /v1/posts/{id}/schedule with publish_at as an ISO timestamp, 'next-slot', or 'now'. A draft that is never scheduled simply stays a draft.

Behaviour and limits

Requires a Creator or Lifetime plan

Everything under /v1/posts, /v1/queue, /v1/schedule, /v1/media and /v1/analytics sits behind one middleware that checks for an unlocked Studio, so a Reader-plan or free key gets 402 with code SUBSCRIPTION_REQUIRED before the handler runs. Bookmark and label reads are on a separate router and are not affected by this gate.

Returns 201, not 200

A successful create answers 201 with the new draft in data. Clients that only branch on status === 200 will treat a successful create as a failure.

content is validated here, before the draft store sees it

A missing or empty content field returns 400 with "content is required." immediately, so an empty draft is never persisted.

media_url and media_urls are the same field

A single media_url is folded into the media_urls array the draft actually stores. Send one or the other; sending both means media_urls wins.

Errors

Failures carry the same envelope as a success, with the reason in message and a stable machine-readable code where the status alone is ambiguous — several distinct conditions share a 402.

Status
Meaning
400Bad Request
content was missing or empty.
401Unauthorized
The Authorization header is missing or the key is not a valid LinkedMash API key. Create or rotate keys on the API page in your LinkedMash settings.
402Payment Required
Returned with code SUBSCRIPTION_REQUIRED. The content API requires a Creator or Lifetime plan; Reader and free accounts get this on every /v1/posts, /v1/queue, /v1/schedule, /v1/media and /v1/analytics call.
500Server Error
Something failed on our side. The body carries message "Failed to create post". Retry once; if it persists the request is not the problem.