X API reference

Batch compliance, the endpoint that costs nothing

Batch compliance is a job-based X API endpoint you hand a list of post IDs to; it reports which of them are no longer available, and because it does not appear in the pricing tables, keeping stored content in step with X is free.

Last verified Primary source: docs.x.com — batch compliance

Why it exists

If you store X content offline, X’s developer policy requires you to keep it in step with the current state of that content — deleting or modifying your copy when the original is deleted or modified, as soon as reasonably possible and within 24 hours of a removal request. Anything you display has the same obligation: content that stops being available through the API has to come off your service.

That obligation needs a mechanism, because there is no webhook that tells you a post you saved last March has been deleted. Batch compliance is the mechanism, and the fact that it is unbilled is a deliberate signal: X is not charging anyone for complying.

The endpoints and the flow

MethodEndpointPurpose
POST/2/compliance/jobsCreate a job, of type posts or users. Returns an upload URL and a job ID.
GET/2/compliance/jobs/:idPoll for status, and collect the download URL once the job completes.
  1. Create the job

    Ask for a posts job. You get back a job ID, a URL to upload your dataset to, and a URL to download results from later.
  2. Upload the IDs you hold

    One post ID per line, to the upload URL from step one. The endpoint is built for bulk: datasets in the millions are the intended scale, so batch by whatever size your own storage makes convenient rather than trickling IDs through.
  3. Poll until it finishes

    Jobs are asynchronous. Poll the job endpoint; this costs nothing and does not consume post reads.
  4. Download and act on the results

    The results name the IDs that are no longer available and why — deleted by the author, the account suspended, the post withheld in some jurisdictions. All three mean the same thing for your copy.
a nightly job, in outline
create posts job          → { id, upload_url, download_url }
PUT  upload_url            ← every post ID you have stored
poll /2/compliance/jobs/id → until status is complete
GET  download_url          → the IDs that are gone
                           → tombstone them locally, before anything is emailed

The alternative, and what it would cost

The obvious wrong approach is to check each stored post by fetching it and seeing whether it comes back. It works, and it is expensive in a way that scales with your success:

Stored posts checked nightlyVia batch complianceVia post lookups
10,000$0.00$50.00 per night
50,000$0.00$250.00 per night
250,000$0.00$1,250.00 per night
Post lookups are billed per resource returned at the Posts: Read rate, so a nightly existence check re-reads content you already have and pays for it again every day.

There is also a design reason beyond the money. A stored post never needs re-fetching because post content is immutable — the only fact about it that changes is whether it still exists. Batch compliance answers exactly that question and nothing else, which makes it the right tool as well as the free one. If you want the arithmetic on the paid side, it is on the post read page.

Two implementation notes

Tombstone rather than hard-delete. A row that vanishes is indistinguishable from a bug to the person who remembers saving it. Replacing the content with a marker — this post is no longer available on X — satisfies the obligation, since the content is gone, while leaving the user a coherent library instead of a mysterious gap.

Run it before anything is sent. The worst version of getting this right is a compliance job that runs after the digest goes out, so a deleted post is removed from the app an hour after being emailed to someone. Ordering the nightly work compliance-first, digest-second costs nothing and closes that gap. The same ordering is why an export never contains a tombstoned post: it is removed from the library before anything can carry it out of the product.

What people get wrong

Expecting a push. There is no notification when a post is deleted. Compliance is something you go and ask about on a schedule, which is why it belongs in a cron job and not in a request handler.

Treating suspension and withholding as different problems. Deleted, suspended and withheld all mean the content is no longer available through the API, and the display obligation is the same for all three. One code path.

Skipping it because nobody is watching. The exposure here is not a fine; it is access. X can rate-limit, revoke or suspend an app for non-compliance, and for a product built on one endpoint that is indistinguishable from being switched off. A free nightly job against that is a good trade.

We built the thing this describes

Sift reads your bookmarks once, sorts them into categories, and emails you what you saved. The rules on this page are the reason it costs what it costs — a one-result probe instead of a full page, one paid pass per UTC day, and never an expansion parameter.

Connect X