Skip to content
Docs

Posts

Create a post with words, images, a video or a GIF; read one post; delete the account's own; pin and unpin.

Early access. Tell us what you're building and we'll set you up. Contact us

Create A Post#

POST /posts (accounts.write, Idempotency-Key required) posts from the account's own X. JSON for words only:

Shell
curl -sS -X POST "https://api.lempile.com/posts" \
  -H "X-API-KEY: $LEMPILE_API_KEY" \
  -H "Idempotency-Key: launch-post-1" \
  -H "Content-Type: application/json" \
  -d '{ "account_id": "janedemo", "text": "Shipping today." }'

multipart/form-data for pictures or a video:

Shell
curl -sS -X POST "https://api.lempile.com/posts" \
  -H "X-API-KEY: $LEMPILE_API_KEY" \
  -H "Idempotency-Key: launch-post-2" \
  -F account_id=janedemo \
  -F "text=Before and after" \
  -F attachments=@before.png \
  -F attachments=@after.png
JSON
{ "object": "PostCreated", "provider": "X", "post_id": "1850000000000000001" }
  • text: required, up to 3,000 characters. More than 280 needs X Premium on the account: without it X keeps the post in its box, and the answer is 422 errors/not_sent with that reason in detail.
  • attachments (multipart only): up to 4 images (JPEG, PNG, WebP or GIF, 8 MB each), or one MP4 of up to 50 MB on its own.
  • gif: a GIF from X's own GIF search, { "query": "ship it", "url": "…" } (a JSON string in a form field). url is a result of GET /posts/gifs; the browser searches X again and refuses if that GIF is gone. Not with attachments.
  • dry_run: true: everything but the Post button, answered 200 { "object": "PostDryRun", "not_sent": true }. dry_run is part of the request, so the real post needs a new Idempotency-Key.

To reply, use POST /posts/{post_id}/comments. There is no quote parameter.

Posts are paced: at least 45 seconds after the account's last write, 70 writes in 24 hours. The request waits up to 140 seconds; past that it answers 504 errors/timeout and the receipt says what happened.

Search X's GIFs#

GET /posts/gifs?account_id=&q=&limit= (accounts.read): X's GIF search as the composer shows it (q 2–40 characters, limit 1–24, default 12). Nothing is typed into a post.

JSON
{ "object": "GifList", "provider": "X", "query": "ship it",
  "items": [{ "url": "https://media1.giphy.com/media/demoGifId/giphy.gif", "title": "Ship it", "width": 498, "height": 280 }] }

Read A Post#

GET /posts/{post_id}?account_id= (accounts.read):

JSON
{
  "object": "Post", "provider": "X", "id": "1850000000000000001",
  "author": "Jane Demo",
  "author_details": { "id": "janedemo", "public_identifier": "janedemo", "profile_url": "https://x.com/janedemo", "profile_picture_url": "https://pbs.twimg.com/profile_images/0/demo.jpg" },
  "date": "2026-11-06T09:12:00.000Z", "text": "Shipping today.",
  "share_url": "https://x.com/janedemo/status/1850000000000000001",
  "reply_counter": 3, "comment_counter": 3, "reaction_counter": 12, "repost_counter": 1,
  "quote_counter": 0, "bookmark_counter": 2, "impressions_counter": 840, "user_reacted": false
}

quote_counter, bookmark_counter and impressions_counter are null when the post's page was read instead of X's own data.

Delete A Post#

DELETE /posts/{post_id}?account_id= (accounts.write) deletes one of the account's own posts or replies through X's menu on it, then opens it again to check it's gone: 200 { "object": "PostDeleted", "post_id": "…" }.

  • 403 errors/not_own_post: someone else's post. Nothing was deleted.
  • 404 errors/post_not_found: missing or already deleted.
  • 422 errors/delete_unavailable: X's menu, its Delete item or the confirmation didn't show. Nothing was deleted.
  • 504 errors/timeout: Delete was confirmed but the post may still be there. Open it before trying again.

Pin And Unpin#

POST /accounts/{account_id}/posts/{status_id}/pin and /unpin (accounts.write, optional Idempotency-Key, optional body { "dry_run": true }). X keeps one pinned post, so a new pin replaces the old one. The answer comes once the profile shows it:

JSON
{ "object": "PostPin", "provider": "X", "status": "pinned", "status_id": "1850000000000000001",
  "previous_pinned_id": "1840000000000000002", "already": false }

status is pinned, unpinned, already_pinned or already_unpinned. Only the account's own posts (400 errors/not_own_post); 502 errors/pin_unconfirmed means check the profile before trying again.

Was this page helpful?