X replies, mentions and costs
The platform’s X developer app is pay-per-use: X deducts every API call from
prepaid credits, and when they run out it answers 402 to every call,
publishing included. Super admins see the bill, and switch on reply delivery,
from Overview › Usage › X API spend.
Who can connect X, and the monthly allowance
Section titled “Who can connect X, and the monthly allowance”Any org owner or member can connect their own X account on /channels/new › Post › X. Posts are published with that account’s own OAuth token, so each one is authored by the person who connected it. Nobody can reach another user’s account.
Every org’s posts are billed to the platform’s single X developer app. To keep
that bill bounded, each organization gets 50 X posts per calendar month
(UTC), free (X_FREE_POSTS_PER_ORG_PER_MONTH). A first comment is a created
post, so it counts as one. When an org reaches the limit, its next X post or
first comment is refused before X is called. The card goes back to review with
the date posting resumes, on the 1st of next month. A pending first comment is
posted on its own after that date. Posts by super admins (ADMIN_EMAILS) do
not count, because they are the platform’s own spend.
X posts are never charged to AI credits. X’s Developer Policy (Pay to engage) bars receiving money or virtual compensation for X actions, posts included. So the limit is a free allowance, not a price.
What X charges
Section titled “What X charges”X’s price list (https://docs.x.com/x-api/getting-started/pricing), as the
platform uses it (X_API_PRICES_USD in @viite-ai/platform-domain):
| Action | USD |
|---|---|
| Create a post with no URL | 0.015 |
| Create a post with a URL, replies included | 0.20 |
| One X Activity event delivered to our webhook | 0.005 |
The X API spend card
Section titled “The X API spend card”- Credit balance: live from X’s
GET /2/usage/credits(total, prepaid, free, and the next free grant’s expiry). It reads Credits depleted on a402. - This month (estimate): derived by the platform, because X exposes no spend
history. Every X post the platform published this UTC month, across every
org, is priced by its
body, plus its first comment when one was actually posted. A post is dated
by
metadata.externalRef.postedAt, stamped at publish. Posts published before that stamp existed are dated by their last update and counted on the card as approximate. Hand-posted rows (posted_by_hand) cost nothing and are excluded.
Both reads take the connected account’s own OAuth2 user token. No app-only Bearer token is needed.
Reply and mention notifications
Section titled “Reply and mention notifications”Enable reply notifications on the card does three things, and is idempotent:
- Registers
${API_URL}/api/x/webhookswith X (POST /2/webhooks). If it is already registered but invalid, it re-runs X’s check instead (PUT). - Subscribes the connected account to two
X Activity events,
post.reply.createandpost.mention.create, and nothing else. Likes, quotes, reposts and follows are deliberately not subscribed, because every delivered event is billed. - Tags each subscription
viite:<orgId>:<userId>:<handle>. X echoes the tag on every event, and the tag is how a sessionless delivery finds its org and its owner.
Each delivered reply or mention becomes a todo card in the Inbox of the admin
who enabled it, tagged from:x plus x:reply or x:mention, linking to the
post on X. A reply to a post the platform published hangs under that post’s
card (parentId), found by the tweet id the publish step recorded. A
retried or replayed delivery files nothing new, because the card is keyed on the
reply post.
Security
Section titled “Security”/api/x/webhooks is public. It is authenticated by HMAC-SHA256 with
X_CLIENT_SECRET, the OAuth 2.0 client secret the X connector already
needs:
- CRC:
GET ?crc_token=answers{"response_token": "sha256=" + base64(hmac(secret, crc_token))}. X runs it on registration, hourly, and on re-validation. The answer is the HMAC of its input, which is exactly an event signature, so acrc_tokencontaining{(which every event body does) or longer than 512 characters is refused. Otherwise the endpoint would sign forged events on request. - Events: the
X-Twitter-Webhooks-Signature-OAuth2header is checked against the raw body in constant time before anything is parsed.
Without the secret, both verbs answer 503 and the card refuses to register.
An event the platform ignores (another type, a tag it did not mint) is
acknowledged with 200, so X does not retry it, because a retry would bill
again.
You can check the result at console.x.com › your app › Webhooks.
Invoices
Section titled “Invoices”X has no invoice API. To get X’s receipts onto /accounting, forward them to
the mailbox the Collect supplier invoices workflow reads, using a mail
filter rather than a hand forward. A filter keeps the PDF and the original
sender. The invoice gate accepts a receipt on its PDF or its
pay.stripe.com/receipts/… link plus the word “receipt”, with no supplier
entry. Do not add stripe.com as a supplier: it would accept every
merchant’s Stripe receipts.
