Skip to content

02 Get your data in /Integrations

X

Send posts that mention your X account, and your replies, to Dimension Labs with the universal API.

  1. X XTickets, chats, calls, reviews
  2. ⟳ Your jobPull new · map fields · every 15 min
  3. { } Universal APIPOST /track · /trackhistorical
  4. Dimension LabsRead in full · enriched daily

This brings in the public posts that mention your brand’s X account, replies to your posts included, and your account’s replies to them: each X conversation as one conversation, with the public’s posts as customer messages and your replies as your side. You run it on your side: a small script pulls from the X API v2 and posts each message to the universal API.

  • An X developer account and app, created in the Developer Console, with the Developer Agreement and Policy accepted.
  • Access and cost: X’s self-serve access is pay-per-use: you buy credits in the Developer Console and each request draws them down. There is no subscription. X’s pricing page lists reads per resource returned, for example $0.005 per Post read, and Owned Reads at $0.001 per resource for your own mentions and posts when the request is made as the account that owns the app. Pay-per-use is capped at 3 million Post reads per monthly billing cycle; above that, X offers Enterprise plans. X says prices are subject to change: check the current rates in the Developer Console before you run a history load.
  • Your app’s Bearer Token and your brand account’s numeric user id.
  • A Dimension Labs API key for a new Universal API source. See Your API key.

X’s steps are in Getting access.

  1. Sign in at console.x.com with your X account, accept the Developer Agreement and Policy, and complete your profile.
  2. Select New App, enter a name, description and use case, and generate the credentials. Copy the Bearer Token right away: X shows credentials only once.
  3. Buy credits in the Developer Console. Set a Spending limit for the billing cycle so a large history load can’t run up an unexpected bill.
  4. Find your account’s user id once: curl "https://api.x.com/2/users/by/username/YOUR_HANDLE" -H "Authorization: Bearer $X_BEARER_TOKEN".

The code below uses the Bearer Token (app-only), which is the simplest. X’s Owned Read price applies when the request is made in user context as the account that owns the app (OAuth 1.0a with the app’s Access Token and Secret, or OAuth 2.0); see X’s authentication docs if you want that rate.

Terminal window
curl -G "https://api.x.com/2/users/USER_ID/mentions" -H "Authorization: Bearer $X_BEARER_TOKEN" \
--data-urlencode "tweet.fields=created_at,author_id,conversation_id,in_reply_to_user_id,lang" \
--data-urlencode "start_time=2026-10-01T00:00:00Z" --data-urlencode "max_results=100"
  • GET /2/users/:id/mentions returns posts that mention your account, replies and quote posts included. GET /2/users/:id/tweets returns your own posts; the code keeps only your replies to other accounts.
  • Incremental: start_time (ISO 8601) returns posts from that time on. since_id works too.
  • Pagination: max_results goes from 5 to 100. Pass meta.next_token as pagination_token until it is missing.
  • How far back: the mentions timeline returns up to the 800 most recent mentions, and your posts timeline up to 3,200. For older history, X’s full-archive search (GET /2/tweets/search/all) is available on pay-per-use.
  • conversation_id is the id of the post that started the thread, so it groups a complaint and your reply in one conversation.
  • Rate limits (X’s): mentions allows 450 requests per 15 minutes per app, your posts timeline 10,000. A 429 means wait until the x-rate-limit-reset time.
  • Cost: every Post returned is billed. X doesn’t charge twice for the same Post within one UTC day, so a re-run on the same day is not billed again (X calls this a soft guarantee).

Each X conversation is one session. Posts by other accounts are incoming; your account’s replies are outgoing.

Dimension LabsXNotes
texttext
userIdauthor_idX’s numeric user id, prefixed: x-2244994945. Don’t send the username or display name.
incomingauthor_idfalse when author_id is your account, true otherwise.
sessionIdconversation_idPrefix it, for example x-1225470895902412800.
conversationIdconversation_idSame value as sessionId.
dimensionlabs_timestampcreated_atISO 8601. Convert with toMillis.
platformJsonpost id, lang, in_reply_to_user_id, which timeline it came fromThe post id is what you need to honor a deletion on X.

Personal data. Posts often hold @handles, names and order numbers: redact them before you send. See PII redaction.

// x.mjs. Environment: X_BEARER_TOKEN, X_USER_ID (your brand account's numeric id), DL_API_KEY.
import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
const HOUR = 60 * 60 * 1000;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const API = 'https://api.x.com/2';
const ME = process.env.X_USER_ID;
// Retries X's rate limits (429) and server errors (5xx). On a 429 it waits until x-rate-limit-reset (or Retry-After).
async function get(url) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.X_BEARER_TOKEN}` } });
if (res.ok) return res.json();
if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`X returned ${res.status}: ${await res.text()}`);
const reset = Number(res.headers.get('x-rate-limit-reset')) * 1000 - Date.now();
await sleep(Number(res.headers.get('retry-after')) * 1000 || (reset > 0 ? reset + 1000 : 2 ** attempt * 1000));
}
}
// One timeline (mentions or your own posts), from `since` on, every page.
async function* timeline(kind, since) {
let token;
do {
const q = new URLSearchParams({ max_results: '100', 'tweet.fields': 'created_at,author_id,conversation_id,in_reply_to_user_id,lang',
start_time: new Date(since).toISOString().replace(/\.\d{3}Z$/, 'Z'), ...(token && { pagination_token: token }) });
const page = await get(`${API}/users/${ME}/${kind}?${q}`);
yield page.data ?? [];
token = page.meta?.next_token;
} while (token);
}
// Sends posts that mention your account, and your replies to other accounts, created at or after `since` (Unix ms).
// Returns the new `since`.
export async function syncX({ since, historical = false }) {
let newest = since;
for (const kind of ['mentions', 'tweets']) {
for await (const posts of timeline(kind, since)) {
for (const p of posts) {
const mine = p.author_id === ME;
if (kind === 'mentions' && mine) continue; // your own posts come from the second timeline
if (kind === 'tweets' && (!p.in_reply_to_user_id || p.in_reply_to_user_id === ME)) continue; // keep replies to others only
if (!p.text?.trim()) continue;
const ts = toMillis(p.created_at);
newest = Math.max(newest, ts);
const sessionId = `x-${p.conversation_id ?? p.id}`;
await sendToDimensionLabs({
text: p.text.trim(),
userId: `x-${p.author_id}`,
incoming: !mine,
sessionId,
conversationId: sessionId,
dimensionlabs_timestamp: ts,
platformJson: { postId: p.id, lang: p.lang, inReplyToUserId: p.in_reply_to_user_id, timeline: kind },
}, { historical: historical || ts < Date.now() - HOUR });
}
}
}
return newest;
}

The first timeline sends mentions; the second sends your replies into the same conversations. Your own posts that aren’t replies are skipped: on their own, they have no customer words to enrich.

Save the code as x.mjs in a project set up as in Set up your project, and run it with the scheduled sync runner: import { syncX as sync } from './x.mjs';.

  • History first: node run.mjs 2026-01-01 sends every mention and reply the timelines still return since that date to /trackhistorical. Then start a backfill from the Backfill tab.
  • Then every 15 minutes: the scheduled job sends new posts to /track, and they are enriched automatically every day. Anything more than an hour old when the job sees it goes to /trackhistorical, and needs a backfill.
  • Re-runs are safe: duplicates are removed automatically.

Deletions. X asks you to delete or update stored posts that are deleted or changed on X, as soon as reasonably possible, or within 24 hours of a request from X or the account owner. X’s batch compliance endpoints tell you which of your stored post ids were deleted or changed. You can’t delete individual records in Dimension Labs yourself: email support@dimensionlabs.io with the source and the post ids for a targeted deletion.

If you have posts in a file instead, upload it and check it first with the file checker, then run a backfill. The same X terms apply to a file.

Checked against X’s documentation on 2026-10-26.