Get started

Make your first request in a minute with one API key, then read the response and handle its errors.

SocialTap is the social media scraper API for developers and AI agents. Send a public link, a handle or a search and get live data back in one schema. Failed requests are free.

  1. Get an API key

    Sign up and copy your key from the dashboard. Every new account starts with 100 free credits, no card needed, so you can try every endpoint before you pay.

    Get your key and 100 free credits

    Send your API key in the x-api-key header with every request. Keep it out of source control, for example in an environment variable:

    Shell
    export SOCIALTAP_API_KEY="your-api-key"
  2. Make a request

    Endpoints are plain HTTPS GET requests with query parameters. This calls the TikTok Profile API with its documented example input:

    cURL
    curl 'https://socialtap.dev/v1/tiktok/profile?handle=nasa' \
      -H "x-api-key: $SOCIALTAP_API_KEY"
    TypeScript SDK
    import { SocialTap } from "socialtap";
    
    const client = new SocialTap(); // apiKey defaults to process.env.SOCIALTAP_API_KEY
    
    const res = await client.tiktok.profile.get({ handle: "nasa" });
    console.log(res.data);
    console.log(res.credits.charged, res.meta.request_id);
    Node.js (fetch)
    const params = new URLSearchParams({
      handle: "nasa",
    });
    const res = await fetch(`https://socialtap.dev/v1/tiktok/profile?${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);
    Python (requests)
    import os
    
    import requests
    
    res = requests.get(
        "https://socialtap.dev/v1/tiktok/profile",
        params={"handle": "nasa"},
        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"])
    Go (net/http)
    package main
    
    import (
    	"encoding/json"
    	"fmt"
    	"log"
    	"net/http"
    	"net/url"
    	"os"
    )
    
    func main() {
    	q := url.Values{}
    	q.Set("handle", "nasa")
    	req, err := http.NewRequest("GET", "https://socialtap.dev/v1/tiktok/profile?"+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"])
    }
    PHP
    <?php
    
    $query = http_build_query([
        'handle' => 'nasa',
    ]);
    $ch = curl_init('https://socialtap.dev/v1/tiktok/profile?' . $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;
    Ruby
    require "json"
    require "net/http"
    
    uri = URI("https://socialtap.dev/v1/tiktok/profile")
    uri.query = URI.encode_www_form('handle' => 'nasa')
    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"]
    Java
    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 = "handle=" + encode("nasa");
            HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create("https://socialtap.dev/v1/tiktok/profile?" + 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);
        }
    }
    .NET
    using System.Text.Json;
    
    using var http = new HttpClient();
    http.DefaultRequestHeaders.Add("x-api-key", Environment.GetEnvironmentVariable("SOCIALTAP_API_KEY"));
    
    var query = string.Join("&",
        "handle=" + Uri.EscapeDataString("nasa"));
    var res = await http.GetAsync("https://socialtap.dev/v1/tiktok/profile?" + 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"));
    Rust
    // 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/tiktok/profile")
            .query(&[("handle", "nasa")])
            .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(())
    }
    • The TypeScript tab uses the TypeScript SDK. Install it first: npm install socialtap. The package is an ES module: load it with import, not require.
    • The Go and Python tabs are plain HTTP, with nothing to install: their SDKs are not published yet.
  3. Read the response

    A successful response is a JSON object with two keys: data, in the same schema on every platform, and meta, which says how the request was served. List endpoints add a third key with what you need to fetch the next page.

    200 OK
    {
      "data": {
        "approximate_fields": [],
        "avatar_url": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/e5143212a59c09a008bf50f487d54d1f~tplv-tiktokx-cropcenter:1080:1080.jpeg?dr=9640&refresh_token=29840d4e&x-expires=4070908800&x-signature=SCRUBBED&t=4d5b0474&ps=13740610&shp=a5d48078&shcp=81f88b70&idc=useast5",
        "banner_url": null,
        "bio": "Making the seemingly impossible, possible.✨",
        "category": null,
        "country_code": "US",
        "created_at": "2026-07-20T15:55:49Z",
        "display_name": "NASA",
        "external_links": [
          {
            "title": null,
            "url": "https://science.nasa.gov/feature/artemis-ii-preliminary-lunar-science-report"
          }
        ],
        "id": "7664638705177150477",
        "is_private": false,
        "is_verified": true,
        "location": null,
        "metrics": {
          "follower_count": 2152843,
          "following_count": 23,
          "post_count": 62,
          "total_like_count": 11486661,
          "total_view_count": null
        },
        "platform": "tiktok",
        "platform_specific": {
          "tiktok": {
            "display_name_changed_at": "2026-07-20T15:58:00Z",
            "friend_count": 17,
            "is_business_account": false,
            "is_shop_seller": false,
            "language": "en",
            "liked_post_count": null,
            "sec_uid": "MS4wLjABAAAAU9BRVzC8oCaegVnia8IbqWhPb_-dbU7s00Y3wS1_Nx8g5RUaYvyXrpejgjdxTwd6"
          }
        },
        "type": "organization",
        "unknown_fields": [],
        "url": "https://www.tiktok.com/@nasa",
        "username": "nasa"
      },
      "meta": {
        "request_id": "req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f",
        "platform": "tiktok",
        "source": "page",
        "is_cached": false,
        "credits_charged": 1,
        "latency_ms": 1098,
        "fetched_at": "2026-10-25T12:00:00Z",
        "input": {
          "kind": "profile",
          "id": "7664638705177150477",
          "url": "https://www.tiktok.com/@nasa"
        },
        "credits_remaining": 9999,
        "client_request_id": null,
        "warnings": []
      }
    }
    FieldTypeDescription
    meta.request_idstringUnique request ID; include it when contacting support.
    meta.platformstringPlatform ID of the endpoint (youtube, tiktok, …).
    meta.sourcestringWhich upstream strategy produced the data.
    meta.is_cachedbooleanTrue when served from the cache (free); see max_age.
    meta.credits_chargedintegerCredits charged for this request (0 for errors and cache hits).
    meta.latency_msintegerTime SocialTap spent on the request, in milliseconds.
    meta.fetched_atstring (date-time)When the data was fetched from the platform, RFC 3339 UTC (older than the request for cache hits).
    meta.inputobject or nullWhat the request's url or handle resolved to: kind, platform ID and canonical URL, so you can confirm what was fetched; null for searches.
    meta.input.kindstringWhat the input points to (profile, video, post, …). New values may be added; treat unknown values as other.
    meta.input.idstring or nullPlatform-native ID of the item; null when it is only known after the fetch and the request failed.
    meta.input.urlstring or nullCanonical URL of the item; null when unknown.
    meta.credits_remaininginteger or nullAccount balance after this request (the x-credits-remaining header); for a busy account an estimate that may not yet subtract concurrent requests (about a second of usage), never below the real balance; null when SocialTap could not read it.
    meta.client_request_idstring or nullYour x-client-request-id, echoed; null when you sent none.
    meta.warningsobject[]Things to act on that did not fail the request: a deprecated endpoint, partial data, items a list's walk leaves out. [] when none.
    meta.warnings[].codestringMachine-readable warning code (see the warnings list). New codes may be added.
    meta.warnings[].messagestringWhat to do about it.
    meta.warnings[].doc_urlstringWhere to read more (the changelog entry, the endpoint's docs).
    meta.warnings[].fieldsstring[]partial_data: the output fields (catalog paths such as metrics.view_count) that are null or [] because part of the data could not be fetched; absent for other codes.

Errors

A failed request returns a non-2xx status and an error object with a stable code:

400 response
{
  "error": {
    "code": "invalid_input",
    "message": "unknown parameter \"username\"; did you mean \"handle\"? handle takes a username or a profile link. This endpoint accepts: handle, max_age, fields.",
    "request_id": "req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f",
    "status": 400,
    "retryable": false,
    "param": "username",
    "remediation": "Fix the parameter named in param as the message says. For a link of the wrong kind, call an endpoint listed in details.endpoints, when there is one.",
    "doc_url": "https://socialtap.dev/docs/errors/#invalid_input",
    "details": {
      "accepted_params": [
        "handle",
        "max_age",
        "fields"
      ],
      "suggestion": "handle"
    },
    "charged": false
  }
}

Branch on code, never on message. The error reference lists every code and whether it is safe to retry.

Next steps

All endpoints

Amazon

Apple Music

Bluesky

Facebook

Facebook Ad Library

Facebook Marketplace

Facebook Events

Instagram

Kick

Komi

Kwai

Lnk.Bio

LinkedIn

Linkme

Linktree

Pillar

Pinterest

Reddit

Rumble

Snapchat

SoundCloud

Spotify

Telegram

Threads

TikTok

TikTok Ad Library

TikTok Shop

Truth Social

Twitch

X

YouTube

Last updated