X (Twitter)
Connect an X account to Pact's social studio — OAuth 2.0 PKCE, text posting, replies, and per-tweet analytics, with the current tier and media limits spelled out.
X (Twitter)
X is one of Pact's social studio providers. Once connected, you can publish and
schedule text posts, reply to tweets, pull per-tweet analytics, and delete posts
— all through the same /v1/social/* routes that drive LinkedIn, Threads,
Bluesky, and the rest. The provider talks to the X v2 API using OAuth 2.0 with
PKCE.
Partial — real, but with real limits
The X adapter genuinely calls the live X v2 API: connect, post, reply, analytics,
and delete are implemented (core/integrations/social/twitter/). But posting is
text-only today, engagement polling and mention ingestion are stubbed, and
direct messages are not supported. It also depends on you bringing your own X app
credentials and a paid X tier — see below.
What works today
| Capability | Status |
|---|---|
| Connect via OAuth 2.0 (PKCE) | Live — authorize_url + complete_oauth + token refresh |
| Publish a text post | Live — POST /2/tweets, truncated to 280 chars |
| Reply to a tweet | Live — reply with in_reply_to_tweet_id |
| Per-post analytics | Live — reads public_metrics (impressions, likes, replies, retweets) |
| Delete a post | Live — deletes on-platform and soft-deletes in Pact |
| Image / video upload | Not yet — media_urls is ignored; follows the v1.1 media flow later |
| Engagement + mention polling | Stubbed — returns empty until the listening worker is wired |
| Direct messages | Not supported — requires a paid tier above Basic |
Connecting an account
Account connect/disconnect is admin/owner-only (it touches the credential store); any user can create posts once an account is connected.
# Start OAuth — returns an authorize_url to send the admin to
curl -s -X POST "https://api.pact.place/v1/social/twitter/connect" \
-H "Authorization: Bearer $PACT_API_KEY"
The callback (GET /v1/social/twitter/callback) completes the PKCE exchange and
redirects back to the app. Tokens are stored in the credential store, never in
audit payloads. Every connect/disconnect/refresh writes an audit event
(social.account.connected, .disconnected, .token_refreshed, …).
Bring your own X app
X gates the v2 API behind paid tiers. Pact treats X as opt-in: the Connect
button surfaces a "requires X Basic tier (~$100/mo)" notice, and you supply your
own X app's client_id / client_secret — resolved per tenant from the
credential store (credential key oauth_app_twitter), or from TWITTER_CLIENT_ID
/ TWITTER_CLIENT_SECRET as a fallback.
- Basic (~$100/mo): 100k reads + 50k writes per month — the practical floor.
- Pro ($5k/mo): higher quotas plus real-time Filtered Stream.
- Free: 1,500 posts / 24h, no Filtered Stream.
The default OAuth scopes are tweet.read, tweet.write, users.read, and
offline.access.
Rate limits are surfaced, not swallowed
When X returns a 429, the provider raises a rate-limit error carrying the
x-rate-limit-reset retry-after so the scheduler can back off cleanly rather
than hammering the API.