Skip to main content

Documentation Index

Fetch the complete documentation index at: https://apidocs.scripe.io/llms.txt

Use this file to discover all available pages before exploring further.

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.
curl -i 'https://api.scripe.io/v1/posts?projectId=proj_01J9ZA…&status=draft,scheduled' \
  -H "Authorization: Bearer scripe_sk_live_…" \
  -H "Scripe-Api-Version: 2026-08-01"

Query parameters

NameTypeRequiredDefaultNotes
projectIdstringyesproj_* id. 404 if it doesn’t belong to the workspace.
statusstringnoComma-separated list of statuses (see below). Unknown values → 400 invalid_request.
dateFromstringnoYYYY-MM-DD. Filters by post createdAt. Inclusive.
dateTostringnoYYYY-MM-DD. Inclusive end-of-day UTC.
limitintegerno501…200.
cursorstringnoOpaque cursor from a previous response.

Status values

The legacy status enum carries one of:
ValueMeaning
waitingProcessingThe post is being generated by the AI pipeline.
draftEditable draft, not yet sent for approval or scheduling.
waitingApprovalOut for review (only used in workspaces with approval flows).
approvedApproved by the reviewer; awaiting scheduling.
rejectedReviewer rejected the draft. Stays in the dashboard for edit.
scheduledScheduled to publish at scheduledAt.
publishedPublished to LinkedIn.
suggestedAI-suggested but not yet acted on by the user.
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

HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": [
    {
      "id": "post_01J9ZA…",
      "projectId": "proj_01J9ZA…",
      "status": "draft",
      "platform": "LINKEDIN",
      "contentType": "EDUCATIONAL",
      "title": "Three lessons from rolling out a pricing change",
      "content": "1/ Don't ship in December …",
      "scheduledAt": null,
      "createdAt": "2026-05-15T08:21:00.000Z",
      "updatedAt": "2026-05-15T09:11:42.000Z"
    }
  ],
  "pagination": {
    "next_cursor": "eyJjcmVhdGVkQXQiOiIyMDI2L…",
    "has_more": false
  }
}

Field reference

PathTypeNotes
idstring (post_*)
projectIdstring (proj_*) | nullThe owning project. null only for legacy rows pre-Phase 1; modern posts always carry one.
statusstringLegacy status enum (see above).
platformstringLINKEDIN. New platforms are additive; treat unknown values as opaque.
contentTypestringOne of EDUCATIONAL, PERSONAL, BUSINESS, INSPIRATIONAL, NEWS, UNKNOWN.
titlestringDisplay title. May be empty for very early drafts.
contentstringBody of the post. Plain text with newlines; no HTML.
scheduledAtstring | nullISO 8601 UTC. null unless status === "scheduled".
createdAtstringISO 8601 UTC.
updatedAtstring | nullISO 8601 UTC. null if never edited.
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.
curl -i https://api.scripe.io/v1/posts/post_01J9ZA… \
  -H "Authorization: Bearer scripe_sk_live_…" \
  -H "Scripe-Api-Version: 2026-08-01"

Response

HTTP/1.1 200 OK
Content-Type: application/json

{
  "data": {
    "id": "post_01J9ZA…",
    "projectId": "proj_01J9ZA…",
    "status": "scheduled",
    "platform": "LINKEDIN",
    "contentType": "EDUCATIONAL",
    "title": "Three lessons from rolling out a pricing change",
    "content": "1/ Don't ship in December …",
    "scheduledAt": "2026-05-20T13:00:00.000Z",
    "createdAt": "2026-05-15T08:21:00.000Z",
    "updatedAt": "2026-05-19T17:42:11.000Z"
  }
}

Errors

StatusCode
400invalid_request (bad status), bad_cursor, bad_pagination
401unauthenticated, invalid_token, key_revoked, key_expired
403scope_missing (need posts:read), plan_not_eligible
404not_found
429rate_limited

POST /v1/posts

Create a draft post (or schedule one for a future timestamp). Required scope: posts:write.
curl -i https://api.scripe.io/v1/posts \
  -X POST \
  -H "Authorization: Bearer scripe_sk_live_…" \
  -H "Scripe-Api-Version: 2026-08-01" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: a-unique-id-from-your-side" \
  -d '{
    "projectId": "proj_01J9ZA…",
    "title": "Three lessons from rolling out a pricing change",
    "content": "1/ Don'"'"'t ship in December…",
    "scheduledFor": "2026-06-01T13:00:00Z"
  }'

Request body

FieldTypeRequiredNotes
projectIdstring (proj_*)yesMust belong to the workspace; otherwise 404 not_found.
contentstringnoTipTap-compatible body. Up to 100,000 chars. Defaults to a single space (TipTap rejects truly-empty content).
titlestringnoUp to 500 chars. Empty by default.
contentTypestringnoOne of PERSONAL, BUSINESS_INTERNAL, BUSINESS_EXTERNAL, EDUCATIONAL, UNKNOWN. Defaults to PERSONAL.
scheduledForstring (ISO 8601)noFuture timestamp. When set, the post status becomes scheduled and a calendar slot is created. Past timestamps → 422 unprocessable.

Response

HTTP/1.1 200 OK
Content-Type: application/json
Idempotent-Replayed: false

{
  "data": {
    "id": "post_01J9ZA…",
    "projectId": "proj_01J9ZA…",
    "status": "scheduled",
    "platform": "LINKEDIN",
    "contentType": "PERSONAL",
    "title": "Three lessons from rolling out a pricing change",
    "content": "1/ Don't ship in December…",
    "scheduledAt": "2026-06-01T13:00:00.000Z",
    "createdAt": "2026-05-15T08:21:00.000Z",
    "updatedAt": null
  }
}

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

StatusCode
400invalid_request (missing projectId)
401unauthenticated, invalid_token, key_revoked, key_expired
403scope_missing (need posts:write), plan_not_eligible
404not_found (project not in workspace)
409idempotency_key_conflict
413payload_too_large (content > 100KB or title > 500 chars)
422unprocessable (bad shape, bad enum, past scheduledFor)
429rate_limited

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.
curl -i https://api.scripe.io/v1/posts/generations \
  -X POST \
  -H "Authorization: Bearer scripe_sk_live_…" \
  -H "Scripe-Api-Version: 2026-08-01" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: a-unique-id-from-your-side" \
  -d '{
    "projectId": "proj_01J9ZA…",
    "source": {
      "type": "text",
      "text": "Three lessons we learned shipping a pricing change…"
    },
    "options": {
      "contentType": "EDUCATIONAL"
    }
  }'

Request body

FieldTypeRequiredNotes
projectIdstring (proj_*)yesMust belong to the workspace; otherwise 404 not_found.
source.typestringyestext or note.
source.textstringwhen type=textUp to 100,000 chars. Past the cap → 413 payload_too_large.
source.noteIdstring (note_*)when type=noteNote must belong to the same project; otherwise 422 unprocessable.
scheduledForstring (ISO 8601)noFuture timestamp. Stored on the resulting post; v1 doesn’t auto-schedule via this path yet.
options.languagestringnoShort language code (≤16 chars). Defaults to the project’s saved preference.
options.contentTypestringnoOne of PERSONAL, BUSINESS_INTERNAL, BUSINESS_EXTERNAL, EDUCATIONAL, UNKNOWN.
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

HTTP/1.1 200 OK
Content-Type: application/json
Idempotent-Replayed: false

{
  "data": {
    "id": "job_01J9ZA…",
    "type": "POST_GENERATION",
    "status": "QUEUED",
    "projectId": "proj_01J9ZA…",
    "startedAt": null,
    "completedAt": null,
    "progress": null,
    "result": null,
    "errorCode": null,
    "errorMessage": null,
    "attemptCount": 0,
    "estimatedCompletionMs": 25000,
    "createdAt": "2026-05-15T08:21:00.000Z",
    "updatedAt": "2026-05-15T08:21:00.000Z"
  }
}
When the job finishes, result becomes { "postId": "post_01J9ZA…" } and you can fetch the post via GET /v1/posts/{postId}.

Errors

StatusCode
400invalid_request (missing projectId, missing source fields)
401unauthenticated, invalid_token, key_revoked, key_expired
402spend_cap_exceeded (workspace daily AI cap reached)
403scope_missing (need posts:write), plan_not_eligible
404not_found (project / note not in workspace)
409idempotency_key_conflict
413payload_too_large (source.text > 100KB)
422unprocessable (bad shape, unknown enum, note belongs to a different project, past scheduledFor)
429rate_limited

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.