curl --request POST \
--url https://api.scripe.io/v1/posts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"projectId": "proj_a1b2c3d4e5f6g7h8",
"content": "<string>",
"title": "<string>",
"contentType": "PERSONAL",
"scheduledFor": "2026-08-18T09:00:00+02:00"
}
'import requests
url = "https://api.scripe.io/v1/posts"
payload = {
"projectId": "proj_a1b2c3d4e5f6g7h8",
"content": "<string>",
"title": "<string>",
"contentType": "PERSONAL",
"scheduledFor": "2026-08-18T09:00:00+02:00"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
projectId: 'proj_a1b2c3d4e5f6g7h8',
content: '<string>',
title: '<string>',
contentType: 'PERSONAL',
scheduledFor: '2026-08-18T09:00:00+02:00'
})
};
fetch('https://api.scripe.io/v1/posts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.scripe.io/v1/posts",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'projectId' => 'proj_a1b2c3d4e5f6g7h8',
'content' => '<string>',
'title' => '<string>',
'contentType' => 'PERSONAL',
'scheduledFor' => '2026-08-18T09:00:00+02:00'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.scripe.io/v1/posts"
payload := strings.NewReader("{\n \"projectId\": \"proj_a1b2c3d4e5f6g7h8\",\n \"content\": \"<string>\",\n \"title\": \"<string>\",\n \"contentType\": \"PERSONAL\",\n \"scheduledFor\": \"2026-08-18T09:00:00+02:00\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.scripe.io/v1/posts")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"projectId\": \"proj_a1b2c3d4e5f6g7h8\",\n \"content\": \"<string>\",\n \"title\": \"<string>\",\n \"contentType\": \"PERSONAL\",\n \"scheduledFor\": \"2026-08-18T09:00:00+02:00\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.scripe.io/v1/posts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"projectId\": \"proj_a1b2c3d4e5f6g7h8\",\n \"content\": \"<string>\",\n \"title\": \"<string>\",\n \"contentType\": \"PERSONAL\",\n \"scheduledFor\": \"2026-08-18T09:00:00+02:00\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "post_a1b2c3d4e5f6g7h8",
"status": "waitingProcessing",
"statusCategory": "suggested",
"platform": "<string>",
"contentType": "<string>",
"title": "<string>",
"content": "<string>",
"contentTruncated": true,
"publishedAt": "2023-11-07T05:31:56Z",
"delivery": {
"state": "not_scheduled",
"willRetry": true,
"retryUntil": "2023-11-07T05:31:56Z",
"detail": "<string>"
},
"review": {
"state": "not_requested",
"awaitingDecision": true,
"reviewerUserId": "<string>",
"deadline": "2023-11-07T05:31:56Z",
"overdue": true,
"decidedAt": "2023-11-07T05:31:56Z",
"notes": "<string>",
"requiredByWorkspace": true,
"detail": "<string>"
},
"media": {
"kind": "images",
"images": [
{
"key": "rrb7bw8pfuc.png",
"alt": "<string>",
"name": "<string>"
}
]
},
"createdAt": "2023-11-07T05:31:56Z",
"projectId": "<string>",
"statusId": "<string>",
"statusTitle": "<string>",
"funnelStage": "REACH",
"scheduledAt": "2023-11-07T05:31:56Z",
"lastPublishError": "<string>",
"lastPublishAttemptAt": "2023-11-07T05:31:56Z",
"updatedAt": "2023-11-07T05:31:56Z"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}Create a draft (or scheduled) post
Creates a tPost row in draft status (or scheduled if
scheduledFor is set). Always pairs the post with an empty
tTranscription placeholder for parity with the dashboard.
With scheduledFor set, the post is scheduled at that
timestamp and the project’s LinkedIn connection is verified
first: no access token on file is an immediate refusal, and
otherwise a live /me ping + token refresh for personal brands
(non-empty admin set for company pages). If LinkedIn isn’t
usable the request fails 409 conflict and nothing is created.
The 409 body carries details.reason
(not_connected | token_invalid | no_company_page_admin),
details.retryable: false — only a human can repair a
connection — and details.reconnectUrl, the page they open to
do it (null for no_company_page_admin, whose repair is on a
different project). The post-scheduler cron handles the publish
itself; if that later fails, the post moves to
failed_to_publish (same path as the dashboard).
Required scope: posts:write.
curl --request POST \
--url https://api.scripe.io/v1/posts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"projectId": "proj_a1b2c3d4e5f6g7h8",
"content": "<string>",
"title": "<string>",
"contentType": "PERSONAL",
"scheduledFor": "2026-08-18T09:00:00+02:00"
}
'import requests
url = "https://api.scripe.io/v1/posts"
payload = {
"projectId": "proj_a1b2c3d4e5f6g7h8",
"content": "<string>",
"title": "<string>",
"contentType": "PERSONAL",
"scheduledFor": "2026-08-18T09:00:00+02:00"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
projectId: 'proj_a1b2c3d4e5f6g7h8',
content: '<string>',
title: '<string>',
contentType: 'PERSONAL',
scheduledFor: '2026-08-18T09:00:00+02:00'
})
};
fetch('https://api.scripe.io/v1/posts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.scripe.io/v1/posts",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'projectId' => 'proj_a1b2c3d4e5f6g7h8',
'content' => '<string>',
'title' => '<string>',
'contentType' => 'PERSONAL',
'scheduledFor' => '2026-08-18T09:00:00+02:00'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.scripe.io/v1/posts"
payload := strings.NewReader("{\n \"projectId\": \"proj_a1b2c3d4e5f6g7h8\",\n \"content\": \"<string>\",\n \"title\": \"<string>\",\n \"contentType\": \"PERSONAL\",\n \"scheduledFor\": \"2026-08-18T09:00:00+02:00\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.scripe.io/v1/posts")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"projectId\": \"proj_a1b2c3d4e5f6g7h8\",\n \"content\": \"<string>\",\n \"title\": \"<string>\",\n \"contentType\": \"PERSONAL\",\n \"scheduledFor\": \"2026-08-18T09:00:00+02:00\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.scripe.io/v1/posts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"projectId\": \"proj_a1b2c3d4e5f6g7h8\",\n \"content\": \"<string>\",\n \"title\": \"<string>\",\n \"contentType\": \"PERSONAL\",\n \"scheduledFor\": \"2026-08-18T09:00:00+02:00\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "post_a1b2c3d4e5f6g7h8",
"status": "waitingProcessing",
"statusCategory": "suggested",
"platform": "<string>",
"contentType": "<string>",
"title": "<string>",
"content": "<string>",
"contentTruncated": true,
"publishedAt": "2023-11-07T05:31:56Z",
"delivery": {
"state": "not_scheduled",
"willRetry": true,
"retryUntil": "2023-11-07T05:31:56Z",
"detail": "<string>"
},
"review": {
"state": "not_requested",
"awaitingDecision": true,
"reviewerUserId": "<string>",
"deadline": "2023-11-07T05:31:56Z",
"overdue": true,
"decidedAt": "2023-11-07T05:31:56Z",
"notes": "<string>",
"requiredByWorkspace": true,
"detail": "<string>"
},
"media": {
"kind": "images",
"images": [
{
"key": "rrb7bw8pfuc.png",
"alt": "<string>",
"name": "<string>"
}
]
},
"createdAt": "2023-11-07T05:31:56Z",
"projectId": "<string>",
"statusId": "<string>",
"statusTitle": "<string>",
"funnelStage": "REACH",
"scheduledAt": "2023-11-07T05:31:56Z",
"lastPublishError": "<string>",
"lastPublishAttemptAt": "2023-11-07T05:31:56Z",
"updatedAt": "2023-11-07T05:31:56Z"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}{
"error": {
"code": "not_found",
"message": "<string>",
"request_id": "req_a1b2c3d4e5f6",
"docs_url": "<string>",
"details": "<unknown>"
}
}Authorizations
Pass Authorization: Bearer scripe_sk_live_<...> (or
scripe_sk_test_<...> for test keys) on every request. Keys
are scoped to a single workspace and can be revoked from the
Scripe dashboard.
The same header also accepts an OAuth 2.1 access token
(scripe_oat_*); both credentials share one scope vocabulary
and every operation below documents the scope it requires.
An API key can hold every scope named on this surface except
webhooks:manage, which is grantable to OAuth tokens only
today — the webhook-endpoint operations answer
403 scope_missing to every API key. Operations that name no
scope accept any valid token of the workspace.
Headers
Pin the API version. Format YYYY-MM-DD. Omit to receive the
currently rolling default. Unknown versions return 400 version_unsupported.
"2026-08-10"
Opaque string (1–64 chars, [A-Za-z0-9_-]) used to dedup
retried writes. Within 24h of the first request, the same key
- same body returns the original response (
Idempotent-Replayed: true). Same key + different body returns409 idempotency_key_conflict.
Strongly recommended for every write — see
/docs/api/v1/idempotency.
^[A-Za-z0-9_-]{1,64}$Body
"proj_a1b2c3d4e5f6g7h8"
Post body. Defaults to a single space (TipTap rejects truly-empty content).
100000The post's INTERNAL name — shown in the dashboard and in
teammates' notifications. It is not published as post copy,
with one exception: on a post whose attached media asset (a
document or an image) carries no filename of its own, a title
YOU set is used as that asset's display title on LinkedIn.
Only a title you set is eligible — a title Scripe derived is
never published, and such a post's asset goes out titled
Document.
Omitting it and sending "" do the same thing here: Scripe
names the post after its own opening line, so a post created
with content is never nameless. Send a real title and the
name is yours: nothing overwrites it afterwards, not a later
PATCH of content and not the AI passes that rewrite the
draft.
500PERSONAL, BUSINESS_INTERNAL, BUSINESS_EXTERNAL, EDUCATIONAL, UNKNOWN Future ISO timestamp at which the post should publish. If
omitted/null, the post stays in draft.
A timestamp WITHOUT an offset (2026-08-18T09:00:00) is
resolved in the project's calendar timezone — not UTC and
not the server's zone — because that is what "9am" means to
the person asking. Send an offset or Z when you mean an
absolute instant. A date alone (2026-08-18) is rejected
with 422: it would schedule the post at midnight.
"2026-08-18T09:00:00+02:00"
Response
Post created (or replayed via Idempotency-Key).
Show child attributes
Show child attributes