Posting
Two endpoints post: a plain-text one-liner for shell scripts and cron jobs, and a JSON endpoint that fans one post out over several connections.
#The one-liner
The request body is the post. It goes to a single connection and is the simplest thing to call from a shell script. Content-Type must be text/plain or application/x-www-form-urlencoded, or be left off; anything else is 415.
curl -X POST "https://api.yeetpost.com/api/v2/post/linkedin" \
-H "x-api-key: $YEETPOST_API_KEY" \
-H "content-type: text/plain" \
-d 'shipped the new api docs today'This is the original v2 endpoint and it is not going away, but new integrations should prefer the JSON one below: it takes several connections, supports Idempotency-Key, and returns post ids.
#JSON fan-out
POST /posts posts the same text to every slug in connectionSlugs. Duplicate slugs are ignored.
curl -X POST "https://api.yeetpost.com/api/v2/posts" \
-H "x-api-key: $YEETPOST_API_KEY" \
-H "content-type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"text": "shipped the new api docs today",
"connectionSlugs": [
"linkedin",
"x"
]
}'{
"results": [
{
"connectionSlug": "linkedin",
"status": "sent",
"postId": "6f1b0c8a-1f3d-4d3e-9a2b-2f5a1c9e7d10",
"url": "https://www.linkedin.com/feed/update/urn:li:share:1234567890",
"scheduledFor": null,
"threadCount": 0,
"hasFirstComment": false,
"error": null
},
{
"connectionSlug": "x",
"status": "failed",
"postId": "0b7e4a52-9f0c-4a1c-8b3a-6d2f5b1c0e44",
"url": null,
"scheduledFor": null,
"threadCount": 0,
"hasFirstComment": false,
"error": {
"code": "platform_rejected",
"message": "X: duplicate post was detected"
}
}
]
}200, and every connection has its own entry in results with a status of sent, scheduled or failed. Check the per-item status and error. The request only fails as a whole (400 invalid_connection) when none of the slugs match.#Scheduling
Both endpoints schedule instead of sending when you give them a time. It has to be in the future and within 30 days. The one-liner takes a query parameter, scheduled_for; the JSON endpoint takes a field, scheduledFor. Both are ISO 8601.
curl -X POST "https://api.yeetpost.com/api/v2/posts" \
-H "x-api-key: $YEETPOST_API_KEY" \
-H "content-type: application/json" \
-d '{
"text": "shipped the new api docs today",
"connectionSlugs": ["linkedin"],
"scheduledFor": "2026-09-15T15:00:00Z"
}'Or let the connection pick the time. Send queue: true instead of scheduledFor and each connection takes its next free posting slot, coming back as scheduled with the slot it took. Slots are wall times on the connection's own clock, set with PATCH /connections/{connectionId}/queue and read back with GET /connections/{connectionId}/queue. A connection with no slots fails that one item with queue_empty; one whose slots are all taken inside the next 30 days fails it with queue_full.
Pausing a queue. Send { "paused": true } to PATCH /connections/{connectionId}/queue and every queued post on that connection holds where it is: nothing goes out until you resume it with { "paused": false }. A post scheduled by hand, rather than with queue: true, still goes out on time either way. Resuming moves any post that fell overdue while paused onto the connection's next free slot, one at a time in the order it was originally due, so a backlog never bursts out at once. Both GET and PATCH .../queue report the current state in paused. The PATCH is strict: an unknown field in the body is 400 invalid_request, not silently ignored.
A scheduled post sits in status scheduled until the worker picks it up. Nobody is waiting on an HTTP response by then, which is what webhooks are for.
#Drafts
Send isDraft: true on POST /posts and nothing is published: each connection gets a stored post with status draft and a postId. A draft is validated against its connection the way a real send is, so it cannot sit in a shape the connection could never publish.
Edit one in place with PATCH /posts/{postId}: text, shape, images and tags, and only what you send is written. Editing anything that is not a draft is 400 invalid_request.
POST /posts/{postId}/publish turns it into a real send: now, at scheduledFor, or with queue: true into the connection's next free slot. The post keeps the id it had as a draft, so an id you are already holding stays valid. Publishing a second time is 404 not_found.
curl -X POST "https://api.yeetpost.com/api/v2/posts/6f1b0c8a-1f3d-4d3e-9a2b-2f5a1c9e7d10/publish" \
-H "x-api-key: $YEETPOST_API_KEY" \
-H "content-type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"scheduledFor": "2026-09-15T15:00:00Z"
}'queue_empty, queue_full, payment_method_required, spend_cap_reached, fair_use_reached and connection_limit_exceeded. Every other refusal is what happened to the post, so the draft becomes a failed post.#Cancelling and deleting
DELETE /posts/{postId} is one verb with three meanings, decided by what the post is.
curl -X DELETE "https://api.yeetpost.com/api/v2/posts/6f1b0c8a-1f3d-4d3e-9a2b-2f5a1c9e7d10" \
-H "x-api-key: $YEETPOST_API_KEY"- Still waiting: the post is cancelled. The row is kept, moves to status
cancelledand still shows up inGET /posts. If the worker claims it while your request is in flight you get409 post_not_cancellable: it was not cancelled and is on its way out, so read it back withGET /posts/{postId}to see how it ended. - A draft: it is deleted outright. A draft never went out, so there is no record to keep. The answer is
{ id, deleted: true }and the row and its images are gone. - Already published: the post comes down on its platform, head post and every reply it published, and the row moves to status
deletedwith adeletedAt.
X, Bluesky, Mastodon, Telegram, Discord, LinkedIn, Reddit and Facebook have a delete. Instagram, Threads, SMS, email and Slack do not and answer 400 delete_unsupported. A platform that refuses the delete answers 422 platform_rejected and the post stays up. Deleting the same post twice answers 200 with the same body.
post.deleted webhook.#AI polish
Optional: an account can add its own Claude or OpenAI key in the console (Settings > AI polish) and have outgoing posts rewritten a little in its own tone of voice before they go out. It uses the account's own key and its own billing, never ours, and is off by default.
Say so per post with rewrite: true or rewrite: false on POST /posts (the same field on the MCP create_post tool); either wins over everything else. Leave it out and the connection's own AI polish mode decides. The one-liner has nowhere for a JSON field, so it takes a ?rewrite=true or ?rewrite=false query param instead. This is decided when the post is created, not when it is sent, so a scheduled or queued post already carries its rewritten text.
A post's response carries originalText (what you sent) alongside text (what actually went out) and aiRewriteStatus: null when AI polish never touched it, "rewritten" when text is the model's own words, "failed" when the model or the account's key errored, and "skipped" for a thread or other multi-segment post, since only a single-text post rewrites.
warning says why, e.g. "AI rewrite failed, posted your original text: Your AI key was rejected".#Listing posts
Your posts, newest first, one row per connection they went to. Filter with status (sent, scheduled, processing, draft, failed, cancelled, deleted) and page with limit (1 to 100, default 25) and offset.
curl "https://api.yeetpost.com/api/v2/posts" \
-H "x-api-key: $YEETPOST_API_KEY"tags are your own labels, at most 10 per post, set on POST /posts or PATCH /posts/{postId}. Filter by them with ?tag=, repeated once per tag: a post has to carry every tag you name, not any of them.
A tag is 1 to 40 lowercase letters, digits, dashes, underscores, colons, dots and slashes. A tag you send is lowercased and trimmed before it is checked, so Cms:Launch and cms:launch are the same tag; anything else in it is 400 invalid_request.
Timestamps go both ways in ISO 8601 UTC: createdAt, scheduledFor and deletedAt come back as 2026-08-26T09:24:11.412Z.
#Full schemas
Every parameter, field and error for these endpoints is on the API reference, generated from the spec.