X (Twitter) credits

Post to X as a pay-per-use add-on of the paid plans: what each post uses, what is left, and top-ups.

View as MarkdownOpenAPIBase URL https://postwire.io

X charges for every post, so posting to X uses X credits: 1 X credit = 1 X post; a post with a link uses 10 (X charges about 13 times more for links); each image or video adds 1. Each paid plan includes X credits every month (Starter 30, Pro 150, Agency 600, Scale 2,000; they renew on the 1st and do not roll over) and a daily cap of X posts (5, 20, 60, 150). Top-up credits (80 for $5, 340 for $20, one-time) are used after the plan's credits and do not expire. X is not available on Free or during the trial. Connect X like any OAuth network (POST /api/oauth/x/url) and publish with platforms: ["x"]: the credits are taken before anything is sent to X and come back if X rejects the post. Text is fitted to 280 characters as X counts them (a link counts 23, an emoji 2); up to 4 images or one video.

A thread, or a reply under the post. per_platform.x.thread is a list of up to 25 posts that go out after per_platform.x.text, each as a reply to the one before; options.x.reply (or per_platform.x.reply) is one more post under the last of them, the usual place for a link (X shows posts with a link to fewer people). Each post of the chain is at most 280 characters as X counts them; an empty or longer one, or a 26th, is refused with bad_x_thread before anything goes out (thread posts are never cut). Media goes on the first post only. Every post of the chain is an X post: it uses credits by the same rule and counts toward the daily cap, and the whole chain is reserved before the first post is sent, so it is never half-paid: "This thread uses 13 X credits: 4 X posts, 1 with a link." GET /api/x/quote with thread and reply prices it first.

The X result is ok once the first post is out, with thread_url and thread.tweets (every post's id, in order). If X refuses a later post, the posts before it stay published and thread.status is pending: PostWire posts the rest on its own on later runs (at least 60 s apart, up to 3 passes), each a reply to the last post that went out. The first post is never sent again and no post goes out twice. After 3 failed passes the status is failed with X's reason and the credits of the posts that never went out are returned; if X does not answer a post, the chain stops there (unknown), that post's credits stay used and the rest come back. A thread sent with /api/post also gets a schedule_id: follow it with GET /api/schedule/{id}. Brand approval rules read the whole chain (a link in any post is "contains a link") and the approval page shows it post by post.

  • GET /api/x: X (Twitter) credits: what posting to X uses and what this account has left
  • GET /api/x/quote: How many X credits one post (or a whole thread) would use, before it is published or scheduled
  • POST /api/x/credits/checkout: A Stripe Checkout link to buy X top-up credits (one-time payment)

X (Twitter) credits: what posting to X uses and what this account has left

GET/api/x

X is a pay-per-use add-on of the paid plans (Starter, Pro, Agency, Scale; not Free, not during a trial). 1 X credit = 1 X post; a post with a link uses 10 (X charges about 13 times more for links); each image or video adds 1. Each paid plan includes X credits every month (Starter 30, Pro 150, Agency 600, Scale 2,000; they renew on the 1st and do not roll over) and a daily cap of X posts (5, 20, 60, 150); top-up credits (80 for $5, 340 for $20, one-time) are used after them and do not expire. An X post without enough credits is not sent to X: its result has code x_credits_required and the links to buy more. A thread or a reply under the post: each post is an X post (same credits, counts toward the daily cap), all reserved together before the first one is sent.

Needs an API key: Authorization: Bearer pw_live_….

Responses

StatusMeans
200{ network: "x", available, mode: paid|free|trial|internal, allowed, killed, pricing: { rule, credits, plans, packs, notes }, credits?: { plan_allowance, allowance_used, allowance_left, topup, total_left, resets_on }, daily?: { used, max }, buy?: [{ pack, usd, credits, url, label }], history: [{ at, kind, what, credits, from_plan, from_topup }], upgrade? (Free), house? (internal: { spent_usd, cap_usd }) }
curl
curl "https://postwire.io/api/x" \
  -H "Authorization: Bearer $POSTWIRE_API_KEY"
Node
const res = await fetch("https://postwire.io/api/x", {
  method: "GET",
  headers: { Authorization: `Bearer ${process.env.POSTWIRE_API_KEY}` },
});
console.log(res.status, await res.json());
Python
import os, requests

r = requests.get(
    "https://postwire.io/api/x",
    headers={"Authorization": f"Bearer {os.environ['POSTWIRE_API_KEY']}"},
)
print(r.status_code, r.json())

Example response

JSON
{
  "network": "x",
  "available": true,
  "mode": "paid",
  "allowed": true,
  "credits": {
    "plan_allowance": 150,
    "allowance_used": 12,
    "allowance_left": 138,
    "topup": 80,
    "total_left": 218,
    "resets_on": "2026-11-01"
  },
  "daily": {
    "used": 3,
    "max": 20
  },
  "buy": [
    {
      "pack": "5",
      "usd": 5,
      "credits": 80,
      "url": "https://postwire.io/api/x/credits/go?t=…&pack=5"
    },
    {
      "pack": "20",
      "usd": 20,
      "credits": 340,
      "url": "https://postwire.io/api/x/credits/go?t=…&pack=20"
    }
  ]
}

Abridged: it also returns pricing (the rule, every plan's X credits and daily cap, the packs) and history (the last movements).

How many X credits one post (or a whole thread) would use, before it is published or scheduled

GET/api/x/quote

Needs an API key: Authorization: Bearer pw_live_….

Parameters

FieldTypeDescription
textquery, stringThe X post's text. A link anywhere in it (also a bare domain such as postwire.io/x) makes it a post with a link.
imagesquery, integerImages attached (X takes up to 4).
videoquery, booleanA video attached (one, on its own).
threadquery, array of stringThe posts of a thread after text (repeat the parameter; POST takes a JSON array). The whole chain is priced.
replyquery, stringA reply under the post (or under the thread), priced with it.

Responses

StatusMeans
200{ credits, has_link, images, video, sentence } — e.g. sentence "This post uses 10 X credits: it contains a link." With thread or reply, the whole chain: also x_posts, with_link and posts: [{ n, role: main|thread|reply, credits, has_link }] — e.g. "This thread uses 13 X credits: 4 X posts, 1 with a link."
400bad_x_thread: a post of the thread or the reply is empty, not text or over 280 characters as X counts them, or there are more than 25.
curl
curl "https://postwire.io/api/x/quote?text=The+new+docs+are+live&reply=Read+them%3A+https%3A%2F%2Fpostwire.io%2Fdocs%2F" \
  -H "Authorization: Bearer $POSTWIRE_API_KEY"
Node
const res = await fetch("https://postwire.io/api/x/quote?text=The+new+docs+are+live&reply=Read+them%3A+https%3A%2F%2Fpostwire.io%2Fdocs%2F", {
  method: "GET",
  headers: { Authorization: `Bearer ${process.env.POSTWIRE_API_KEY}` },
});
console.log(res.status, await res.json());
Python
import os, requests

r = requests.get(
    "https://postwire.io/api/x/quote?text=The+new+docs+are+live&reply=Read+them%3A+https%3A%2F%2Fpostwire.io%2Fdocs%2F",
    headers={"Authorization": f"Bearer {os.environ['POSTWIRE_API_KEY']}"},
)
print(r.status_code, r.json())

Example response

JSON
{
  "credits": 11,
  "has_link": true,
  "images": 0,
  "video": false,
  "sentence": "This post with its reply uses 11 X credits: 2 X posts, 1 with a link.",
  "x_posts": 2,
  "with_link": 1,
  "posts": [
    {
      "n": 1,
      "role": "main",
      "credits": 1,
      "has_link": false
    },
    {
      "n": 2,
      "role": "reply",
      "credits": 10,
      "has_link": true
    }
  ]
}

Without thread or reply it prices one post: { credits, has_link, images, video, sentence }. A thread: repeat thread= once per post (or POST a JSON array).

A Stripe Checkout link to buy X top-up credits (one-time payment)

POST/api/x/credits/checkout

Packs: "5" = 80 credits for $5, "20" = 340 credits for $20. Nothing is charged until the person pays on the Stripe page. Paid plans only (403 x_paid_plan_required otherwise, with the plan that has X).

Needs an API key: Authorization: Bearer pw_live_….

Body (JSON)

FieldTypeDescription
pack requiredstringOne of: 5, 20.

Responses

StatusMeans
200{ url } of the Stripe Checkout page
403x_paid_plan_required (Free or a running trial) or internal_account
curl
curl -X POST "https://postwire.io/api/x/credits/checkout" \
  -H "Authorization: Bearer $POSTWIRE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "pack": "5"
}'
Node
const res = await fetch("https://postwire.io/api/x/credits/checkout", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.POSTWIRE_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "pack": "5"
  }),
});
console.log(res.status, await res.json());
Python
import os, requests

r = requests.post(
    "https://postwire.io/api/x/credits/checkout",
    headers={"Authorization": f"Bearer {os.environ['POSTWIRE_API_KEY']}"},
    json={
        "pack": "5",
    },
)
print(r.status_code, r.json())

Example response

JSON
{
  "url": "https://checkout.stripe.com/c/pay/cs_live_…"
}