X (Twitter) Post API. Send a post link or ID.
Full text, media and exact view counts. We handle the proxies, blocks and platform changes.
X (Twitter) Post API at a glance
Returns a public X (formerly Twitter) post from its link or ID: full text, author, media, and exact view, like, repost, reply and bookmark counts. Long posts and X Articles come whole. It also has the quote count, the post it quotes or replies to with its own text and counts, its poll with exact votes, link card, hashtags, mentions, cashtags, community and edit history.
GET /v1/x/post
- 1 credit per request, $0.75–$1.97 per 1,000 requests
- 22 typed fields null when unknown
urlis all it needs, e.g.https://x.com/NASA/status/2117633046002225363- 100 credits free when you sign up, no card, so you can build before you spend
Send one request. Get 22 fields back.
Copy the request in your language, or hand it to your agent. Sign up for an API key and your first 100 credits are free.
$ npm install socialtapurl = https://x.com/NASA/status/2117633046002225363const params = new URLSearchParams({
url: "https://x.com/NASA/status/2117633046002225363",
});
const res = await fetch(`https://socialtap.dev/v1/x/post?${params}`, {
headers: { "x-api-key": process.env.SOCIALTAP_API_KEY ?? "" },
});
const body = await res.json();
if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
const { data, meta } = body;
console.log(data, meta.credits_charged);import os
import requests
res = requests.get(
"https://socialtap.dev/v1/x/post",
params={"url": "https://x.com/NASA/status/2117633046002225363"},
headers={"x-api-key": os.environ["SOCIALTAP_API_KEY"]},
)
body = res.json()
if not res.ok:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
data, meta = body["data"], body["meta"]
print(data, meta["credits_charged"])require "json"
require "net/http"
uri = URI("https://socialtap.dev/v1/x/post")
uri.query = URI.encode_www_form('url' => 'https://x.com/NASA/status/2117633046002225363')
req = Net::HTTP::Get.new(uri)
req["x-api-key"] = ENV.fetch("SOCIALTAP_API_KEY")
res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(req) }
body = JSON.parse(res.body)
raise "#{body["error"]["code"]}: #{body["error"]["message"]}" unless res.is_a?(Net::HTTPSuccess)
pp body["data"]
puts body["meta"]["credits_charged"]<?php
$query = http_build_query([
'url' => 'https://x.com/NASA/status/2117633046002225363',
]);
$ch = curl_init('https://socialtap.dev/v1/x/post?' . $query);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('SOCIALTAP_API_KEY')],
]);
$body = json_decode(curl_exec($ch), true);
if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) >= 400) {
throw new RuntimeException($body['error']['code'] . ': ' . $body['error']['message']);
}
print_r($body['data']);
echo $body['meta']['credits_charged'], PHP_EOL;package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"net/url"
"os"
)
func main() {
q := url.Values{}
q.Set("url", "https://x.com/NASA/status/2117633046002225363")
req, err := http.NewRequest("GET", "https://socialtap.dev/v1/x/post?"+q.Encode(), nil)
if err != nil {
log.Fatal(err)
}
req.Header.Set("x-api-key", os.Getenv("SOCIALTAP_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
var body struct {
Data json.RawMessage `json:"data"`
Meta map[string]any `json:"meta"`
Error *struct {
Code string `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
if err := json.NewDecoder(res.Body).Decode(&body); err != nil {
log.Fatal(err)
}
if body.Error != nil {
log.Fatalf("%s: %s", body.Error.Code, body.Error.Message)
}
fmt.Println(string(body.Data), body.Meta["credits_charged"])
}curl -G 'https://socialtap.dev/v1/x/post' \
--data-urlencode 'url=https://x.com/NASA/status/2117633046002225363' \
-H "x-api-key: $SOCIALTAP_API_KEY"{
"data": {
"approximate_fields": [],
"author": {
"avatar_url": "https://pbs.twimg.com/profile_images/1321163587679784960/0ZxKlEKB.jpg",
"display_name": "NASA",
"id": "11348282",
"is_verified": true,
"url": "https://x.com/NASA",
"username": "NASA"
},
"coauthors": [],
"duration_ms": null,
"hashtags": [],
"id": "2117633046002225363",
"is_nsfw": false,
"is_pinned": null,
"is_sponsored": null,
"language": "en",
"media": [
{
"alt_text": "An intricate cloud of white dust takes center stage in this photo, rising in a windy pillar. The background features rust-colored dust with dots of distant stars and galaxies shining through.",
"duration_ms": null,
"expires_at": null,
"height": 1385,
"thumbnail_url": null,
"type": "image",
"url": "https://pbs.twimg.com/media/HWNYCEHWsAAO3PW.jpg?name=orig",
"width": 2000
}
],
"mentions": [
"NASAWebb"
],
"metrics": {
"comment_count": 181,
"like_count": 2300,
"quote_count": 42,
"repost_count": 315,
"save_count": 120,
"share_count": null,
"view_count": 490407
},
"platform": "x",
"platform_specific": {
"x": {
"article": null,
"author_metrics": {
"follower_count": 92294019,
"following_count": 115,
"post_count": 74490,
"total_like_count": null,
"total_view_count": null
},
"author_verified_type": "government",
"card": null,
"cashtags": [],
"community": null,
"conversation_id": "2117633046002225363",
"edit_post_ids": [
"2117633046002225363"
],
"is_author_blue_verified": true,
"is_edited": false,
"links": [],
"media_variants": [],
"poll": null,
"quoted_post": null,
"quoted_post_id": null,
"reply_to_author": null,
"reply_to_post_id": null,
"reposted_post": null,
"reposted_post_id": null,
"source": null
}
},
"platform_type": "post",
"published_at": "2026-11-03T15:00:36Z",
"text": "This smoky specter is the Cat’s Head Nebula, captured in intricate detail by @NASAWebb. Its name comes from its shape, which resembles a cosmic kitty sitting with its back toward the viewer. What do you see?",
"thumbnail_url": "https://pbs.twimg.com/media/HWNYCEHWsAAO3PW.jpg?name=orig",
"title": null,
"type": "image",
"unknown_fields": [],
"url": "https://x.com/NASA/status/2117633046002225363"
},
"meta": {
"request_id": "req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f",
"platform": "x",
"source": "api",
"is_cached": false,
"credits_charged": 1,
"latency_ms": 272,
"fetched_at": "2026-10-25T12:00:00Z",
"input": {
"kind": "post",
"id": "2117633046002225363",
"url": "https://x.com/NASA/status/2117633046002225363"
},
"credits_remaining": 9999,
"client_request_id": null,
"warnings": []
}
}import { SocialTap } from "socialtap";
const client = new SocialTap(); // apiKey defaults to process.env.SOCIALTAP_API_KEY
const res = await client.x.post.get({ url: "https://x.com/NASA/status/2117633046002225363" });
console.log(res.data);
console.log(res.credits.charged, res.meta.request_id);import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
public class Main {
public static void main(String[] args) throws Exception {
String query = "url=" + encode("https://x.com/NASA/status/2117633046002225363");
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://socialtap.dev/v1/x/post?" + query))
.header("x-api-key", System.getenv("SOCIALTAP_API_KEY"))
.GET()
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
// An error is {"error": {"code", "message", ...}}; success is {data, meta}, meta.credits_charged its price.
if (response.statusCode() >= 400) throw new RuntimeException(response.body());
System.out.println(response.body());
}
static String encode(String value) {
return URLEncoder.encode(value, StandardCharsets.UTF_8);
}
}using System.Text.Json;
using var http = new HttpClient();
http.DefaultRequestHeaders.Add("x-api-key", Environment.GetEnvironmentVariable("SOCIALTAP_API_KEY"));
var query = string.Join("&",
"url=" + Uri.EscapeDataString("https://x.com/NASA/status/2117633046002225363"));
var res = await http.GetAsync("https://socialtap.dev/v1/x/post?" + query);
using var body = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
if (!res.IsSuccessStatusCode)
{
var error = body.RootElement.GetProperty("error");
throw new Exception($"{error.GetProperty("code")}: {error.GetProperty("message")}");
}
Console.WriteLine(body.RootElement.GetProperty("data"));
Console.WriteLine(body.RootElement.GetProperty("meta").GetProperty("credits_charged"));// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }, serde_json = "1"
use std::env;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let res = reqwest::blocking::Client::new()
.get("https://socialtap.dev/v1/x/post")
.query(&[("url", "https://x.com/NASA/status/2117633046002225363")])
.header("x-api-key", env::var("SOCIALTAP_API_KEY")?)
.send()?;
let ok = res.status().is_success();
let body: serde_json::Value = res.json()?;
if !ok {
return Err(format!("{}: {}", body["error"]["code"], body["error"]["message"]).into());
}
println!("{} {}", body["data"], body["meta"]["credits_charged"]);
Ok(())
}200 OK · GET https://socialtap.dev/v1/x/postDocs One schema on 21 platforms.
The response reference lists every field, its type and when it is null. See the full schema.
Null means unknown
A field X doesn't show is null, never zero or an empty string, so a real zero always means zero.
What it accepts
X post: a link (x.com or twitter.com, /<username>/status/<id> with or without a view such as /photo/1 or a query such as ?s=20, /i/web/status/<id>, /i/status/<id>, /<username>/article/<id>, old /statuses/ and #! links, mobile.twitter.com) or a bare numeric post ID.
Write one parser
X, TikTok, Instagram and 18 more platforms answer in these fields, so the code that reads one reads them all.
platformstring- Platform ID (tiktok, youtube, …).
idstring- Platform-native post ID (string; Instagram shortcode, Reddit with its t3_ prefix).
urlstring- Canonical post URL.
typestring- Normalized content shape. New values may be added; treat unknown values as other.
platform_typestring- The platform's own product label, lowercase (reel, short, story, live, tweet, …).
titlestring- Headline (YouTube title, Reddit title); null on caption-only platforms.
textstring- Caption, description or body as plain text.
languagestring- BCP-47 language of text, when the platform states it.
Live data from 33 platforms.
What teams build with the X (Twitter) Post API.
Track a post's views, likes and reposts
Turn pasted links into text, author and media
Download a post's video or photos
Read poll votes and X Article text
Trace quotes and replies to the original post
Good to know before you build.
- metrics.comment_count is replies and metrics.save_count is bookmarks. metrics.share_count is always null, because X shows no such count.
- metrics.view_count is null on older posts, which X has no view count for.
- text is the whole post, with X's t.co links written out and without the trailing link to its own media or quoted post.
- A repost (platform_type repost) keeps its own id, author and published_at, but its text, media and metrics are the reposted post's, also in platform_specific.x.reposted_post, because X counts nothing on a repost itself.
- On an X Article, title and text are the article's and platform_type is article.
- platform_type is post, reply, quote, repost or article. platform_specific.x.source is null on most recent posts, because X no longer says which client posted them.
- When X's API fails, the post comes from its page, with conversation_id, source and the quoted post's save_count null.
- When the page fails too, the post comes from the embed widget's API: repost, quote, view and save counts are null, a long post's text is cut and listed in approximate_fields, and a repost returns upstream_error, because that API returns the reposted post in its place.
- Deleted posts and posts from suspended accounts return not_found. Posts from protected accounts and age-restricted posts return private_or_unavailable.
1 credit per request. Failed requests are free.
Buy credits once and spend them on any endpoint, on any platform.
| Source | Price per successful request | Per 1,000 requests |
|---|---|---|
| Any source | 1 credit | $0.75–$1.97 |
- Cache hits are free.
- Credits from packs and top-ups never expire.
- A monthly plan's credits are spent first, and what is left of them ends at each renewal.
- Prices exclude tax: sales tax is added at checkout where it applies.
- Outside the US, plans and packs are sold to businesses: checkout asks for your VAT, GST or other business tax ID.
Questions about the X (Twitter) Post API.
More in the API reference.
Can I download the video from a tweet?
How much does the X (Twitter) Post API cost?
Try it on a link you already have.
Paste it in the playground, or copy the request into your code.
- No credit card
- Failed requests are free
- Works on all 218 endpoints