---
name: tweet-archive
description: Archive a tweet (X post) permanently, free, no API key required. POST a tweet URL to Tweet Archive's public API to trigger a Wayback Machine snapshot, then poll for the result. Use when asked to archive, preserve, back up, or save a tweet before it might be deleted, or to check whether a tweet has already been archived.
---

# Tweet Archive

[Tweet Archive](https://x-dot-com-archive.vercel.app) is a free,
unauthenticated service that captures a permanent archive.org (Wayback
Machine) snapshot of a tweet, plus a screenshot, so the tweet survives
even if it's later deleted from X.

## Archive a tweet

```
POST https://x-dot-com-archive.vercel.app/api/v1/tweets
Content-Type: application/json

{"urls": ["<tweet URL or bare numeric ID>", ...]}
```

1 to 10 items per request.

```bash
curl -X POST https://x-dot-com-archive.vercel.app/api/v1/tweets \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://x.com/user/status/1234567890"]}'
```

Returns a JSON array, same order as the input `urls`. Each entry is
either a tweet object (see shape below) or `{"input": "...", "error":
"..."}` for anything unparseable or unfetchable. One bad item never fails
the rest of the batch.

Archiving happens in the background after the response returns;
`snapshots` may still be empty right away. Poll the status endpoint below
every few seconds until a snapshot appears; it usually completes within
about a minute.

**Without a token, text over ~280 characters gets trimmed.** This
service reads X's free public syndication API by default, which
truncates long tweets the same way X's own oEmbed widget does. If you
have your own X API access, pass it: it costs nothing extra for a
normal-length tweet (see below), and it means the tweet gets archived in
full instead of truncated, a better permanent record for anyone who
looks it up later, not just for you.

Two optional top-level fields, applying to the whole request:
- `x_bearer_token`: your own X API Bearer Token, for full-length
  tweets. Only spent when actually needed: the free API always runs
  first, and your token is only used if that result is truncated or the
  free call fails outright, so a normal-length tweet costs you nothing
  even if you supply one. You'll need your own X developer access to
  have a token; this service never provides or pays for one.
- `agent`: a name to self-identify as (e.g. `"my-bot/1.0"`), recorded
  on the archive for attribution. Falls back to your User-Agent header if
  omitted.

## Archive a whole thread or reply context

Instead of archiving just the one tweet, you can pull in the tweets
around it: what it's replying to (free), and/or the author's own
continuation tweets (needs your own X API access).

```
POST https://x-dot-com-archive.vercel.app/api/v1/tweets/{id}/thread/check
POST https://x-dot-com-archive.vercel.app/api/v1/tweets/{id}/thread/ingest
Content-Type: application/json

{"x_bearer_token": "<optional>"}
```

`check` is free preview, `ingest` actually archives -- calling `ingest`
at all is the confirmation, there's no separate confirm flag.

- **Ancestors** (what `{id}` is replying to): always free, no key, in
  both calls. Walked backward one tweet at a time via the free API.
- **Forward** (the author's own later tweets in that conversation): only
  with `x_bearer_token`. `check` pays for exactly *one* X API search call
  to find the author's latest reply in the conversation, then walks
  backward from it for free -- so discovering a whole thread costs about
  one search call regardless of how long the thread is, not one call per
  tweet. `check`'s response includes a full preview (every discovered
  tweet's text, and whether it's individually truncated) plus a real
  `estimated_full_text_cost_usd` for upgrading the truncated ones -- no
  money is spent beyond that one search call until you call `ingest`.
  `ingest` then upgrades each truncated tweet to full text one at a time
  (pass `"upgrade_truncated": false` to skip that and just archive them
  truncated, or `"max_cost_usd"` to cap how much the upgrade step can
  spend). Only covers the last 7 days of replies unless your token has a
  pricier full-archive search tier.

`check`'s response: `{"id", "ancestors": [...], "is_reply", "heuristic":
{"conversation_count", "looks_like_thread"}, "forward": {...}}`.
`ingest`'s response: `{"id", "ancestors": [...], "forward": {"tweets":
[...], "upgraded_count", "conversation_id"} | null,
"skipped_forward_reason": "no_token" | "budget_exhausted" | null}`. Every
tweet entry in both is the same shape as the response below.

## Check archive status

```
GET https://x-dot-com-archive.vercel.app/api/v1/tweets/{id}
```

Read-only, no side effects; safe to poll repeatedly. Returns 404 if the
ID has never been submitted.

## Response shape (one tweet)

```json
{
  "id": "2095401070080184644",
  "status": "live",
  "tweet": { "...raw syndication payload..." },
  "truncated": false,
  "note": "present only when truncated, explains how to get the full text",
  "archive_url": "https://x-dot-com-archive.vercel.app/tweets/2095401070080184644",
  "first_archived_at": "2026-09-01T09:00:00.000Z",
  "removed_at": null,
  "removed_snapshot": null,
  "snapshots": [
    {
      "wayback_url": "https://web.archive.org/web/.../...",
      "archived_at": "2026-09-07T15:21:22.000Z",
      "screenshot_url": "https://.../....png",
      "event": "edited"
    }
  ]
}
```

- `snapshots` is newest-first: `snapshots[0].wayback_url` is the
  permanent archive link to hand back to whoever asked.
- `status` is `"live"` or `"removed"` (deleted from X since being
  archived; the archive itself still exists in `snapshots`).
- `tweet` is `null` once a tweet has been removed.
- `truncated` is `true` when the stored text is a cut-off preview.
- `first_archived_at` is when we first archived this tweet -- not the
  tweet's own creation time on X (that's inside `tweet`, if not removed).
- `snapshots[].event` is `"created"` for the tweet's first archived
  version, `"edited"` for every later one whose content actually changed.
- `removed_at`/`removed_snapshot` are set once the tweet is found removed
  (`removed_snapshot` is the archived version that was live right before
  that, `snapshots[0]`); both `null` while live. `removed_at` can be
  `null` even when `status` is `"removed"` for tweets removed before this
  field existed -- the removal time just isn't known for those.

## Notes

- No API key, no account, no auth header required.
- No rate limiting is enforced yet; be a reasonable citizen: batch up
  to 10 URLs per request instead of looping single-item calls.
- Machine-readable OpenAPI 3.1 spec (for GPT Actions and similar
  tool-calling frameworks): https://x-dot-com-archive.vercel.app/openapi.json
- Full docs: https://x-dot-com-archive.vercel.app/agents
