Publish and schedule posts, threads, images and video on X (Twitter) through one REST endpoint. Connect your X account once: you do not need an X developer account or an X API plan of your own.
Base URL https://api.so-me.studio/v1 · Auth X-API-Key · socialMedia: TWITTER
Send one of these values as postType. Every post needs text, and photos or videos are attached by fileId from your media library.
TEXT
A text-only post.
IMAGE
One image.
CAROUSEL
2 to 4 images in one post.
VIDEO
One video.
Upload the media, then create the post. Leave out scheduledAt to publish right away.
POST /v1/media/presign-upload adds the file to your media library and returns a fileId and a presigned uploadUrl.
curl -X POST https://api.so-me.studio/v1/media/presign-upload \
-H "X-API-Key: $SOME_API_KEY" \
-H "Content-Type: application/json" \
-d '{"files":[{"filename":"onboarding.png","mimetype":"image/png","size":412300}]}'
# → [{ "fileId": "…", "uploadUrl": "https://…", "fileSrc": "…", "s3Key": "…" }]PUT the bytes to uploadUrl with exactly the Content-Type you declared. The URL is valid for one hour.
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: image/png" \
--upload-file ./onboarding.pngPOST /v1/posts with the fileId. The response is the post with status SCHEDULED, and it publishes at scheduledAt through a queue that retries on failure.
curl -X POST https://api.so-me.studio/v1/posts \
-H "X-API-Key: $SOME_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"socialMedia": "TWITTER",
"postType": "IMAGE",
"text": "We rebuilt our onboarding. 3 things we learned:",
"fileIds": ["<fileId>"],
"threadParts": [
{ "text": "1. Ask for one thing per screen." },
{ "text": "2. Show the result before the setup." },
{ "text": "3. Measure drop-off per step, not per funnel." }
],
"scheduledAt": "2026-11-06T13:00:00Z"
}'accountId is optional when the workspace has one X account. Otherwise pass an id from GET /v1/accounts.
Files attached with fileIds are checked against the X limits below before anything is queued. A file that breaks a rule returns 400 with a report per file.
The fields that change how a post is published on X.
| Field | What it does |
|---|---|
| threadParts | The posts that follow, in order, published as a reply chain under the first one. Up to 24 parts of 280 characters, each with up to 4 fileIds. |
| firstComment | A reply posted under the post, or under the last part of a thread, once it is live. Up to 280 characters, and X counts every link as 23. |
| accountId | The X account to post from. Optional when the workspace has only one. |
Social media studio checks each file against these limits before it publishes. They are our configured limits, not a promise from X: GET /v1/media/rules returns the live table, and POST /v1/media/validate checks a file without posting it.
Post type TEXT
Text only, no media
Post type IMAGE
Post type CAROUSEL
Post type VIDEO
Publishing is asynchronous. Subscribe to post.published and post.failed, or poll GET /v1/posts/:id until postStatus is POSTED or FAILED.
curl -X POST https://api.so-me.studio/v1/webhooks/subscriptions \
-H "X-API-Key: $SOME_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/hooks/social",
"categories": ["post"],
"events": ["post.published", "post.failed"]
}'
# The response includes the secret that signs every delivery.POST /hooks/social
X-Webhook-Event: post.published
X-Webhook-Signature: <hex HMAC-SHA256 of the raw body>
{
"event": "post.published",
"category": "post",
"timestamp": "2026-11-03T15:00:07.412Z",
"data": { "postId": "…", "platform": "TWITTER" }
}Every delivery is signed with HMAC-SHA256 in X-Webhook-Signature. post.failed fires after the last retry; fix the cause and call POST /v1/posts/:id/retry. A first comment reports on its own with post.first_comment_posted or post.first_comment_failed.
The same API key drives an MCP server with 200+ tools and a CLI, so an agent can post to X without hand-written HTTP calls.
Add the server to Claude, Cursor or any MCP client with your API key. OAuth works too. For X, the agent calls these tools with the same fields as the REST request:
{
"mcpServers": {
"so-me-studio": {
"url": "https://api.so-me.studio/mcp",
"headers": { "X-API-Key": "sk_live_…" }
}
}
}Install it with npm i -g @social-media-scheduler/cli. It uploads local files for you and prints JSON, so a script or a CI job can schedule a X post in one command.
so-me posts:create --platform TWITTER --post-type IMAGE \
-m ./onboarding.png \
-c "We rebuilt our onboarding. 3 things we learned:" \
--thread "1. Ask for one thing per screen." \
--thread "2. Show the result before the setup." \
-s 2026-11-06T13:00:00ZAPI, CLI and MCP access is included on every paid plan: Lite, Creator, Standard, Advanced and Lifetime. See plans and limits
Yes. Put the first post in text and the rest in threadParts. Each part can carry its own text and fileIds. If a later part fails, the parts already live stay up and the post records how many were published.
No. You connect your X account to Social media studio once, and publishing runs through the Social media studio X integration. Your code only sends your Social media studio API key.
No. You connect the X account once in the Social media studio dashboard through the X sign-in. Its tokens are stored encrypted and refreshed on our side, and your code only sends your Social media studio API key in the X-API-Key header.
No. It returns as soon as the post is queued. Publishing runs in a worker that retries on failure, so listen for post.published and post.failed, or poll GET /v1/posts/:id.
The REST API, the CLI and the MCP server are included on every paid plan: Lite, Creator, Standard, Advanced and Lifetime. Lite, Creator, Standard and Advanced start with a 7-day trial (card required), and Lifetime is a one-time payment.
Stop juggling tabs, draft, customize, and publish to 20 platforms from a single calendar.