Skip to main content

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 legacy status 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 emit 400 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 to failed_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 no tone or preferredHookStyle option — 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 inside source.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

When the job finishes, 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 tPostLinkedIn row and will surface as an analytics endpoint 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 / DELETE are out of scope for v1. Use the dashboard for edits.