API reference
Publish now, schedule, or save a draft to one or more connected accounts.
https://api.betterpost.dev/v1/postsGuide: Create a post. Platform options: Platforms.
| Field | Type | Required | Description |
|---|---|---|---|
content | string | Yes | Shared text for every target. May be "" when media is attached. |
platforms | array | Yes | At least one target (see below). |
mediaItems | array | No | Media shared by all targets (see below). Default []. |
publishNow | boolean | One mode | true to publish immediately. |
scheduledFor | string | One mode | ISO 8601 date-time in the future. Without an offset it is read in timezone. |
saveAsDraft | boolean | One mode | true to save without publishing (free). |
timezone | string | No | IANA time zone for scheduledFor. Default UTC. |
idempotencyKey | string | No | 1 to 255 characters. See Idempotency. |
Exactly one of publishNow: true, scheduledFor or saveAsDraft: true is required.
Target (platforms[]):
| Field | Type | Required | Description |
|---|---|---|---|
platform | string | Yes | twitter, linkedin, instagram or facebook. Must match the account. |
accountId | string | Yes | A connected account id. Each account at most once per post. |
content | string | No | Replaces the shared content for this target only. |
platformSpecificData | object | No | Network options, for example threadItems (X) or visibility (LinkedIn). Unknown keys are ignored. |
Media item (mediaItems[]):
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | image, video or gif. |
url | string | Yes | Public http(s) URL. |
altText | string | No | 1 to 1,000 characters. |
thumbnail | string | No | Thumbnail URL for videos. |
curl -s -X POST https://api.betterpost.dev/v1/posts \
-H "x-access-key: $BETTERPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Hello from BetterPost",
"platforms": [
{ "platform": "twitter", "accountId": "acc_2x9c4v7b1n8m3k5j0h6g2f4d" },
{ "platform": "linkedin", "accountId": "acc_5l1n7k3j9h2g8f4d6s0a1q3w" }
],
"publishNow": true,
"idempotencyKey": "hello-2026-10-21"
}'202 Accepted
{
"success": true,
"status": "published",
"postId": "post_8f3k2m9q1x7v4n6b0c5d2a1z",
"platforms": [
{
"platform": "twitter",
"accountId": "acc_2x9c4v7b1n8m3k5j0h6g2f4d",
"success": true,
"platformPostUrl": "https://betterpost.dev/sandbox/p/twitter/sbx_1849203756184930275"
},
{
"platform": "linkedin",
"accountId": "acc_5l1n7k3j9h2g8f4d6s0a1q3w",
"success": true,
"platformPostUrl": "https://betterpost.dev/sandbox/p/linkedin/sbx_6620194837561029384"
}
]
}| Field | Description |
|---|---|
success | false only when every target failed. |
status | published, partial, failed, publishing (still running after 8 s), scheduled or draft. |
postId | The new post's id. |
scheduledFor | Only for scheduled posts: the UTC instant. |
platforms[] | One result per target, in request order: platform, accountId, success, and platformPostUrl, error or warningMessage when present. Targets still in progress have success: true. |
A validation error lists every problem at once:
{
"success": false,
"message": "Validation failed",
"code": "validation_error",
"errors": [
{ "path": "platforms.0.content", "message": "X posts are limited to 280 characters (got 300)" },
{ "path": "platforms.1.mediaItems", "message": "Instagram requires at least one image or video" }
]
}| Status | Code | When |
|---|---|---|
202 | Accepted: published, partial, failed, publishing, scheduled or draft (see status). | |
200 | Replay of an earlier request with the same idempotencyKey. Nothing new is created or charged. | |
400 | validation_error | Missing fields, platform rule violations, or account/platform mismatches. |
400 | unknown_account | Every problem is an accountId not found in this project. |
400 | account_reauthorization_required | A target account must be reconnected. |
400 | invalid_publish_mode | Not exactly one of publishNow: true, scheduledFor, saveAsDraft: true. |
400 | invalid_timezone | Unknown timezone. |
400 | invalid_scheduled_for | scheduledFor is not an ISO 8601 date-time. |
400 | scheduled_in_past | scheduledFor is not in the future. |
400 | invalid_platform | Unknown platform value. |
400 | invalid_json | The body is empty or not JSON. |
401 | unauthorized | Missing or unknown API key. |
402 | insufficient_credits | Not enough credits: need X, have Y. Nothing was created. |
403 | platform_not_available | A platform BetterPost does not support yet. |
413 | payload_too_large | The body is over 1 MB. |
429 | rate_limited | More than 120 requests per minute. |