Signed URLs
Creating a job returns two signed, single-purpose URLs onapi.x.com:
Each URL embeds a token that binds it to exactly one operation on exactly one
job until its expiry. Requests to these URLs must also carry your
Authorization: Bearer header — the same App credential that created the job.
This differs from the previous Google Cloud Storage-based flow, where
pre-signed URLs were used without an Authorization header; if you are
migrating an existing integration, add the header to your upload and download
requests. Still treat the URLs as secrets: do not log them or embed them in
client-side code.
The exact expiry instants are returned as upload_expires_at and
download_expires_at on the job object. If the upload window lapses before
you use it, cancel the job (DELETE /2/compliance/jobs/:id) and create a
new one — upload URLs are not refreshable.
Job lifecycle
- One active job per type. While a
tweetsjob iscreatedorin_progress, creating anothertweetsjob returns409 Conflict. Ausersjob can run concurrently with atweetsjob. - Processing is asynchronous. Poll
GET /2/compliance/jobs/:idwith backoff (e.g. every 30–60 seconds). Processing time scales with dataset size. - Jobs expire. A job left in
createdwithout an upload expires. Jobs can also endfailed; both free the active-job slot.
Uploads
The upload is a singlePUT of a plain-text body, one numeric ID per line,
up to 1 GiB. There is no resumable/chunked mode: if a transfer is
interrupted, retry the whole PUT while the upload URL is valid, or cancel
the job and start over. For very large datasets, split IDs across multiple
sequential jobs — results are per-ID, so partitioning is safe.
Invalid lines (non-numeric, malformed) don’t fail the job; each comes back
in the results as an error record:
Interpreting results
Results are newline-delimited JSON. Three rules cover the contract:- Absence means clean. An ID missing from the results has no compliance event; your stored copy requires no action.
actiontells you what to do:delete(remove your stored copy),rehydrate(re-fetch the Post — it was edited and your copy is stale), orscrub_geo(remove stored location data for the Post).- One event per ID. When several events apply, you receive the
highest-impact one:
deleteoutranksrehydrate, which outranksscrub_geo.
deleted_at (an alias of redacted_at)
for compatibility with consumers of the legacy results format. redacted_at
can be null for events that predate timestamp tracking; the obligation to
act is the same.
Error handling
Errors use RFC 7807 problem+json bodies.Best practices
- Run compliance jobs on a schedule. X requires stored content to reflect current compliance state; a recurring (e.g. daily or weekly) job over your stored IDs, acting on every returned event, is the intended integration pattern. Pair batch jobs with the compliance streams if you need real-time signals between runs.
- Upload immediately after creating a job. The ~15-minute upload window is comfortably enough for automation but not for manual workflows with long pauses.
- Store job IDs with your submissions.
GET /2/compliance/jobslists recent jobs, but correlating submissions to results is your side’s responsibility. - Download results promptly and persist them; the download URL lapses after ~7 days and results are not retrievable afterward.
- Act on
rehydratevia the Posts lookup endpoints — re-fetching returns the current revision of an edited Post along with its edit history.
Next steps
Quickstart
Create your first compliance job
Compliance streams
Real-time compliance events
API Reference
Full endpoint documentation