Skip to main content
The Batch Compliance endpoints let you upload large datasets of Post IDs or user IDs and learn what action is needed to keep your datasets aligned with user intent and the current state of content on X. This guide covers the concepts the quickstart glosses over: URL lifecycle, job lifecycle, error handling, and operational best practices.

Signed URLs

Creating a job returns two signed, single-purpose URLs on api.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 tweets job is created or in_progress, creating another tweets job returns 409 Conflict. A users job can run concurrently with a tweets job.
  • Processing is asynchronous. Poll GET /2/compliance/jobs/:id with backoff (e.g. every 30–60 seconds). Processing time scales with dataset size.
  • Jobs expire. A job left in created without an upload expires. Jobs can also end failed; both free the active-job slot.

Uploads

The upload is a single PUT 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:
  1. Absence means clean. An ID missing from the results has no compliance event; your stored copy requires no action.
  2. action tells you what to do: delete (remove your stored copy), rehydrate (re-fetch the Post — it was edited and your copy is stale), or scrub_geo (remove stored location data for the Post).
  3. One event per ID. When several events apply, you receive the highest-impact one: delete outranks rehydrate, which outranks scrub_geo.
Deletion events additionally carry 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/jobs lists 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 rehydrate via 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