Posts
A post is the LinkedIn-ready version of a note. Posts move through a state machine — draft → waiting approval → approved → scheduled → published — and the v1 API exposes every state. The API is read-only in v1: triggering post creation, editing content, scheduling, and publishing are all dashboard-only operations. Phase 3 introduces the write endpoints.GET /v1/posts
List posts inside a project. projectId is required.
Query parameters
Status values
The legacystatus enum carries one of:
We’re moving towards a custom status system in the dashboard
(
customStatusId), but v1 only exposes the legacy enum. The two are
synced bidirectionally inside the dashboard, so filtering by the legacy
status here will catch the right rows. Custom statuses will surface in
v1.x as an additive customStatus field; existing clients won’t break.
Response
Field reference
Internal fields like
customStatusId, dfyStatus, dfyLiked,
chosenHook, postLength, postCreativity, rating, reasoning, the
multi-image asset payloads, and reshare context are intentionally not
in the response. They’re dashboard-only state and may be removed or
restructured without notice.
Pagination caveats
- Order is
(createdAt DESC, id DESC). - Changing any filter (
projectId,status,dateFrom,dateTo,limit) mid-loop will likely emit400 bad_cursor. Restart the loop.
GET /v1/posts/{postId}
Single post read. Same shape, wrapped in data.
Response
Errors
POST /v1/posts
Create a draft post (or schedule one for a future timestamp). Required
scope: posts:write.
Request body
Response
LinkedIn-token caveat
API-driven scheduling does NOT validate the project’s LinkedIn token at create-time. The post-scheduler cron picks the post up at publish time and refreshes the token. If the refresh permanently fails, the post moves tofailed_to_publish (same path as the dashboard). This is the
only intentional behavioural delta between API-scheduled and
dashboard-scheduled posts.
Errors
POST /v1/posts/generations
Generate a post draft from a piece of free-form text or a saved Note.
The request kicks off an async job — the response is a Job
envelope you poll until status === "DONE" (then result.postId
points to the freshly-created Post). Required scope: posts:write.
Request body
The worker already loads the project’s tone-of-voice profile, voice samples, content pillars, and knowledge base before drafting. There is intentionally notoneorpreferredHookStyleoption — tone is configured per-project in the dashboard, and the hook generator picks its own top-ranked hook. If you want to steer a specific draft, put the steering text insidesource.text(audience, format hints, example phrases, etc.) — the worker weaves it into the prompt. |wait_for_completion_ms| integer | no | If set, the API holds the connection up to this many ms (capped server-side at 25,000 ms) waiting for the job to finish. Useful for short-running flows that want a synchronous result. Falls through to standard polling on timeout. |
Response
result becomes { "postId": "post_01J9ZA…" }
and you can fetch the post via GET /v1/posts/{postId}.
Errors
What’s NOT here (yet)
- Engagement metrics (likes, comments, views). They live on a
separate
tPostLinkedInrow and will surface as ananalyticsendpoint in Phase 4. - Variations and version history. v1 returns the canonical post body. Per-version reads are queued for the dashboard’s content studio integration.
- Approval / reviewer state. The
status === "waitingApproval"signal exists, but the reviewer queue, decisions, and notes are dashboard-only in v1. - Update / delete endpoints.
PATCH/DELETEare out of scope for v1. Use the dashboard for edits.