Overview
Connecting X Ads lets the dashboard:- Create, update, pause, resume, and delete campaigns and line items (X’s term for ad sets).
- Upload image and video media into the connected account’s media library.
- Create and manage promoted tweets that reference an existing tweet plus a line item.
- Pull spend, impressions, engagements, and conversions from
/stats/accounts. - Run creative experiments by rotating multiple line items under one campaign.
Prerequisites
Before connecting, make sure you have:- An active X Ads account with developer / API access.
- Approved access to the X Ads API — gated behind partner review. Without approval, the integration runs in dry-run mode and returns synthetic mock ids on every write.
- An OAuth 2.0 application configured for the dashboard’s redirect URI. The
client_idandclient_secretare set on the platform side asX_ADS_CLIENT_IDandX_ADS_CLIENT_SECRET.
OAuth scopes
The connection requests the following scopes:offline.access is required for refresh tokens. ads.read covers list and report operations; ads.write covers all create / update / delete writes.
PKCE flow
X Ads requires PKCE (Proof Key for Code Exchange — RFC 7636). The dashboard:- Generates a 32-byte random
code_verifierat start time and stores it server-side for the duration of the round-trip. - Computes
code_challenge = base64url(sha256(code_verifier))and emitscode_challenge+code_challenge_method=S256on the authorize URL. - Sends the matching
code_verifieron the token-exchange call so the platform can verify the relationship.
Connect
- Open the Paid UA → Integrations page in the dashboard.
- Click Connect on the X (Twitter) Ads tile.
- Sign in with the X account that has access to your ads account.
- Approve the requested scopes.
- After redirect, the dashboard exchanges the auth code (with the PKCE verifier) for tokens and probes
/accountsto confirm access.
What happens behind the scenes
| Operation | API endpoint |
|---|---|
| List ad accounts the user can access | GET /accounts |
| Create / list campaigns | POST / GET /accounts/{id}/campaigns |
| Update / pause / resume / delete campaign | PUT / DELETE /accounts/{id}/campaigns/{id} |
| Create / update line items | POST / PUT /accounts/{id}/line_items[/{id}] |
| Create / update promoted tweets | POST / PUT /accounts/{id}/promoted_tweets[/{id}] |
| Upload media library entry | POST /accounts/{id}/media_library |
| Pull stats | GET /stats/accounts/{id}?entity=...&entity_ids=...&metric_groups=... |
{ paused: true | false } instead of a status enum.
Every write request carries an Idempotency-Key HTTP header so retries do not produce duplicate resources, even if the platform connection drops mid-request.
Rate limiting
Requests are throttled per(client_id, ads_account_id) using a sliding window built on Redis. The default budget is 50 requests per minute per account (X’s gated tier). If the platform reports HTTP 429, the request is automatically retried with exponential backoff. X also surfaces sliding-window limits in response headers; these are observed but not yet auto-respected.
Reporting
Metrics are pulled hourly (or on demand) from the stats endpoint. Default columns include engagement metrics (impressions, clicks, retweets, likes) and billing metrics (spend, billable cost). Stats are pulled perentity=CAMPAIGN (or LINE_ITEM for split tests) with the campaign id forwarded as entity_ids.
Spend is converted from native currency to USD on ingestion. The native currency, the FX rate used, and the local-currency spend are kept alongside the USD value for downstream analytics.
Experiments (multi-line-item rotation)
X Ads does not expose a native experiment primitive. The dashboard maps experiment variants onto multiple line items under a single campaign, with each variant carrying its own targeting and creative tweet. The control variant ships unpaused; treatments ship paused until the experiment runner toggles them. Per-variant performance is read back from the stats endpoint withentity=LINE_ITEM.
Dry-run mode
X Ads API access is gated behind partner review. To insulate production tenants while the review is in flight, the integration ships in dry-run mode by default — write operations return synthetic mock ids and do not call X. Set the platform-side environment flagX_ADS_LIVE_MODE=true once approval lands so writes execute against the production API.
Disconnect
Click Disconnect on the integration tile. The OAuth refresh token is removed from the dashboard. To fully revoke access, also remove the third-party app authorisation from your X account’s connected apps list.Troubleshooting
- “X Ads /accounts returned 401” — the OAuth token has been revoked or has expired without a valid refresh token. Re-connect the integration.
- “X Ads create_campaign: 429 — …” — your account’s per-minute API quota was breached. The dashboard backs off automatically; if the error persists, lower campaign-mutation frequency.
- “webhooks not supported on x_ads” — X’s Account Activity API is gated separately from the Ads API and out of scope. State changes are detected via the hourly metrics-sync job instead.
- Synthetic / mock ids returned in production — the platform is running in dry-run mode (likely because partner approval has not yet landed). Ask your administrator to set
X_ADS_LIVE_MODE=trueonce approval is granted. - Token exchange returns 400 on PKCE flow — the
code_verifierdid not match thecode_challengesent during authorize. The state TTL is 10 minutes; if the user took longer than that, restart the connect flow.

