Authentication
Verify your account, claim trial credits and create a key in the dashboard. Send it as a Bearer header. Do not put keys in URLs or public browser code.
Resolve one post
curl 'https://api.twitterdownloads.com/v1/resolve' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: unique-request-001' \
--data '{"url":"https://x.com/username/status/2104150555035857259"}'JavaScript
const response = await fetch('https://api.twitterdownloads.com/v1/resolve', {
method: 'POST',
headers: {
Authorization: 'Bearer ' + process.env.TWITTERDOWNLOADS_API_KEY,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID()
},
body: JSON.stringify({ url: postUrl })
});
const result = await response.json();Python
import json, os, urllib.request, uuid
request = urllib.request.Request('https://api.twitterdownloads.com/v1/resolve',
data=json.dumps({'url': post_url}).encode(),
headers={'Authorization': 'Bearer ' + os.environ['TWITTERDOWNLOADS_API_KEY'],
'Content-Type': 'application/json',
'Idempotency-Key': str(uuid.uuid4())}, method='POST')
with urllib.request.urlopen(request) as response:
result = json.load(response)Response
{
"post": {
"id": "…",
"url": "…",
"text": "…",
"author": {
"name": "…",
"username": "…"
}
},
"media": [
{
"id": "…",
"type": "video",
"thumbnail": "…",
"duration_ms": null,
"variants": [
{
"url": "https://video.twimg.com/…mp4",
"content_type": "video/mp4",
"width": 1280,
"height": 720,
"label": "720p",
"bitrate": null
}
]
}
],
"cached": false,
"request_id": "…",
"credits_used": 1
}Animated GIF media may have type gif but the downloadable file remains MP4. Width, height, duration or bitrate may be null when unavailable.
Balance and usage
GET /v1/balance returns available credits and their batches, including remaining, reserved and expiry. GET /v1/usage returns status totals and the latest 30 calls. Both require your Bearer key.
Idempotency
Use an 8–128-character Idempotency-Key of letters, digits, underscores or hyphens. Reuse it only for the same post. Completed responses are retained for 24 hours and replayed without charging again. A different post with the same key returns 409. An active duplicate returns REQUEST_IN_PROGRESS; retry after the indicated delay.
Completed failures are also replayed. To try the source again after a completed failure, use a new key. After 24 hours, a reused key can become a new billable request.
Errors
| Status | Code | Next step |
|---|---|---|
| 400 | INVALID_URL / INVALID_REQUEST | Send one valid HTTPS post URL. |
| 401 | INVALID_API_KEY | Check or replace your key. |
| 402 | CREDITS_EXHAUSTED | Check your available credit batches. |
| 409 | IDEMPOTENCY_CONFLICT / REQUEST_IN_PROGRESS | Check the key or retry later. |
| 422 | NO_VIDEO | The returned post has no downloadable MP4. |
| 429 | RATE_LIMITED / CONCURRENCY_LIMIT / TRIAL_DAILY_LIMIT | Wait for Retry-After. |
| 503 | UPSTREAM_UNAVAILABLE / UPSTREAM_TIMEOUT / SERVICE_BUSY | Retry later. |
Limits and headers
Paid accounts: 60 resolve requests/minute, 3 concurrent. Trial: 10/minute, 1 concurrent, 20 successful parses per UTC day. Limits are shared across keys. Global protective budgets and a bounded queue also apply. Errors do not spend credits but still count toward limits.
Responses include X-Request-Id. Rate limits include Retry-After. Replays include Idempotent-Replayed: true.
Supported scope
One URL per request. Batch arrays are rejected. Public posts only, original MP4 links, no hosting or permanent-link guarantee. X may change its interface; no fixed SLA is promised.