# SocialTap: full documentation > Public social media data. One API, one schema. SocialTap returns public social media data from the major platforms, normalized into one JSON schema per resource. One API key, one credit balance, and you only pay for requests that succeed. Available now: ads on Facebook; comments on Rumble, TikTok and YouTube; posts on Facebook, Instagram, Rumble, TikTok and YouTube; profiles on Instagram, Rumble, Threads, TikTok and YouTube; transcripts on Facebook, Instagram, Rumble, TikTok and YouTube. Every SocialTap docs page and endpoint reference in one Markdown file, generated from the product catalog. The short index is https://staging-fly.socialtap.dev/llms.txt. **Instructions for AI agents** 1. **Authenticate.** Send the API key in the `x-api-key` header of every request to `https://staging-fly.socialtap.dev`. Read it from an environment variable such as `SOCIALTAP_API_KEY`, never hard-code it, and call the API from server-side code only. Without a key, ask the user for one (keys: https://staging-fly.socialtap.dev/signup/); never invent one. 2. **No key yet?** `POST https://staging-fly.socialtap.dev/v1/agent_signup` with `{"email": ""}` returns a test key that works at once and a live key that stays pending until your user confirms the emailed link (it spends nothing until then). Ask the user for their email address first, build with the test key, and tell them to confirm. Details: [account and keys](https://staging-fly.socialtap.dev/docs/account/index.md). 3. **Pick an endpoint.** Every endpoint is `GET https://staging-fly.socialtap.dev/v1//` with query parameters; the endpoint list ([all endpoints](https://staging-fly.socialtap.dev/apis/index.md), or [catalog.json](https://staging-fly.socialtap.dev/catalog.json) for machines) gives each one's parameters and price per successful request. Read the endpoint's Markdown docs page before writing code, and don't rely on memory: endpoints and parameters change. Every endpoint of one kind of data (ads, comments, posts, profiles and transcripts) returns the same schema on every platform; `null` means unknown, never 0 or "". 4. **Read the response.** A success is HTTP 200 with `{data, meta}`: `meta.credits_charged` is what the request cost, `meta.credits_remaining` the balance left, `meta.request_id` identifies the request for support. 5. **Paginate.** List endpoints return `{data, page, meta}`. While `page.has_more` is true, repeat the same request with `cursor` set to `page.next_cursor`, keeping every other parameter. An empty page with `page.has_more` true is not the end. Cursors expire (`page.next_cursor_expires_at`): don't store them. Leave `limit` out: a smaller page costs the same. 6. **Handle errors.** Errors are non-2xx responses with `{"error": {code, message, request_id, status, retryable, param, remediation, doc_url, details, charged}}`. Branch on `error.code`, never on `error.message`. Retry only when `error.retryable` is true, with exponential backoff, first waiting the seconds in the `retry-after` header (also `error.details.retry_after`) when it is sent; otherwise do what `error.remediation` says. `insufficient_credits` means the balance is too low: stop and tell the user. Every code is documented at `https://staging-fly.socialtap.dev/docs/errors/#` (`error.doc_url`). Failed requests cost nothing. 7. **Spend credits deliberately.** Each endpoint states its price per successful request (per page for lists); fetch only what the task needs. Add `max_age=` to accept a stored response up to that old: cache hits are free. 8. **Test first.** Keys starting with `st_test_` are test keys: they answer every endpoint from recorded responses, free and deterministic. Build and test the integration with one, then switch to a live key. Magic inputs make an endpoint return any error code it can return ([test mode](https://staging-fly.socialtap.dev/docs/test-mode/index.md)), so test your error handling too. 9. **Many items or long lists.** Use background jobs, `POST /v1/jobs`, instead of a loop of single requests: one job runs many requests and walks whole lists. 10. **Notifications.** Instead of polling, register a webhook endpoint with `POST /v1/webhook_endpoints`: deliveries are signed (Standard Webhooks); verify the signature on the raw body before trusting one. 11. **Retry POSTs safely.** Send a new `idempotency-key` (a UUID) with every POST and reuse it when you retry: the work runs once, and a retry answers with the current state. 12. **Balance.** `GET /v1/account` returns the balance, free. 13. **MCP.** Agents with MCP can use the remote server at `https://staging-fly.socialtap.dev/mcp` (Streamable HTTP, the same API key as `authorization: Bearer ` or in the `x-api-key` header), with the tools `get_post`, `get_profile`, `get_transcript`, `list_comments`, `list_posts`, `search`, `find_endpoint`, `search_docs`, `read_doc`, `get_account`, `create_job`, `get_job` and `list_job_results`; see [the agent guide](https://staging-fly.socialtap.dev/docs/agents/index.md). 14. **Verify.** Before building on an endpoint, run its example request (on its docs page) and check that `data` is not empty. Report the endpoint, `meta.credits_charged` and any `error.code` you saw. --- Source: https://staging-fly.socialtap.dev/docs/ # Quickstart Get started Make your first request in a minute: one API key, one HTTP call, one JSON shape for every platform. 1. ## Get an API key Every request is authenticated with an API key in the `x-api-key` header. To try SocialTap right now, use this public test key: Keep keys out of source control, for example in an environment variable: Shell: ```shell export SOCIALTAP_API_KEY="st_test_docs_mZkmkl5JiSWvVgL4v3DLolbtSsjxXm11" ``` 2. ## Make a request Endpoints are plain HTTPS `GET` requests with query parameters. This calls the [TikTok Profile API](https://staging-fly.socialtap.dev/docs/reference/tiktok/profile/index.md) with its documented example input: cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/tiktok/profile' \ --data-urlencode 'handle=https://www.tiktok.com/@nasa' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 3. ## Read the response A successful response is a JSON object with two keys: `data`, the item in its resource's [shared schema](https://staging-fly.socialtap.dev/resources/index.md), and `meta`, which describes how the request was served. [List endpoints](https://staging-fly.socialtap.dev/docs/pagination/index.md) add a third key with what you need to fetch the next page. *(Omitted: another section of this file has it.)* | Field | Type | Description | | --- | --- | --- | | `meta.request_id` | `string` | Unique request ID; include it when contacting support. | | `meta.platform` | `string` | Platform ID of the endpoint (youtube, tiktok, …). | | `meta.source` | `string` | Which upstream strategy produced the data. | | `meta.is_cached` | `boolean` | True when served from the cache (free); see max_age. | | `meta.credits_charged` | `integer` | Credits charged for this request (0 for errors and cache hits). | | `meta.latency_ms` | `integer` | Time SocialTap spent on the request, in milliseconds. | | `meta.fetched_at` | `string` (date-time) | When the data was fetched from the platform, RFC 3339 UTC (older than the request for cache hits). | | `meta.input` | `object` or `null` | What 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.kind` | `string` | What the input points to (profile, video, post, …). New values may be added; treat unknown values as other. | | `meta.input.id` | `string` or `null` | Platform-native ID of the item; null when it is only known after the fetch and the request failed. | | `meta.input.url` | `string` or `null` | Canonical URL of the item; null when unknown. | | `meta.credits_remaining` | `integer` or `null` | Account 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_id` | `string` or `null` | Your x-client-request-id, echoed; null when you sent none. | | `meta.warnings` | `object[]` | Things to act on that did not fail the request: a deprecated endpoint, partial data. \[\] when none. | | `meta.warnings[].code` | `string` | Machine-readable warning code (see the warnings list). New codes may be added. | | `meta.warnings[].message` | `string` | What to do about it. | | `meta.warnings[].doc_url` | `string` | Where to read more (the changelog entry, the endpoint's docs). | | `meta.warnings[].fields` | `string[]` | 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. | | `meta.mode` | `string` or `null` | live, or test for a test key's answer: a recorded response (fixture), free and deterministic; null when SocialTap could not tell. New values may be added.
One of `live`, `test` | > [!TIP] > > Test keys are free and deterministic, so build and test your whole integration with one. Switch to a live key (`st_live_…`) when you want live data: only successful requests are charged. ## Errors A failed request returns a non-2xx status and an `error` object with a stable `code`: *(Omitted: another section of this file has it.)* Branch on `code`, never on `message`. The [error reference](https://staging-fly.socialtap.dev/docs/errors/index.md) lists every code and whether it is safe to retry. ## Next steps ## All endpoints ### [Facebook](https://staging-fly.socialtap.dev/apis/facebook/index.md) - [Facebook Post API](https://staging-fly.socialtap.dev/docs/reference/facebook/post/index.md) `GET /v1/facebook/post` - [Facebook Ad Library Search API](https://staging-fly.socialtap.dev/docs/reference/facebook/search_ads/index.md) `GET /v1/facebook/search_ads` - [Facebook Video Transcript API](https://staging-fly.socialtap.dev/docs/reference/facebook/transcript/index.md) `GET /v1/facebook/transcript` ### [Instagram](https://staging-fly.socialtap.dev/apis/instagram/index.md) - [Instagram Post API](https://staging-fly.socialtap.dev/docs/reference/instagram/post/index.md) `GET /v1/instagram/post` - [Instagram Posts API](https://staging-fly.socialtap.dev/docs/reference/instagram/posts/index.md) `GET /v1/instagram/posts` - [Instagram Profile API](https://staging-fly.socialtap.dev/docs/reference/instagram/profile/index.md) `GET /v1/instagram/profile` - [Instagram Reels Transcript API](https://staging-fly.socialtap.dev/docs/reference/instagram/transcript/index.md) `GET /v1/instagram/transcript` ### [Rumble](https://staging-fly.socialtap.dev/apis/rumble/index.md) - [Rumble Comments API](https://staging-fly.socialtap.dev/docs/reference/rumble/comments/index.md) `GET /v1/rumble/comments` - [Rumble Video API](https://staging-fly.socialtap.dev/docs/reference/rumble/post/index.md) `GET /v1/rumble/post` - [Rumble Channel Videos API](https://staging-fly.socialtap.dev/docs/reference/rumble/posts/index.md) `GET /v1/rumble/posts` - [Rumble Channel API](https://staging-fly.socialtap.dev/docs/reference/rumble/profile/index.md) `GET /v1/rumble/profile` - [Rumble Search API](https://staging-fly.socialtap.dev/docs/reference/rumble/search_posts/index.md) `GET /v1/rumble/search_posts` - [Rumble Transcript API](https://staging-fly.socialtap.dev/docs/reference/rumble/transcript/index.md) `GET /v1/rumble/transcript` ### [Threads](https://staging-fly.socialtap.dev/apis/threads/index.md) - [Threads Profile API](https://staging-fly.socialtap.dev/docs/reference/threads/profile/index.md) `GET /v1/threads/profile` ### [TikTok](https://staging-fly.socialtap.dev/apis/tiktok/index.md) - [TikTok Comments API](https://staging-fly.socialtap.dev/docs/reference/tiktok/comments/index.md) `GET /v1/tiktok/comments` - [TikTok Post API](https://staging-fly.socialtap.dev/docs/reference/tiktok/post/index.md) `GET /v1/tiktok/post` - [TikTok Posts API](https://staging-fly.socialtap.dev/docs/reference/tiktok/posts/index.md) `GET /v1/tiktok/posts` - [TikTok Profile API](https://staging-fly.socialtap.dev/docs/reference/tiktok/profile/index.md) `GET /v1/tiktok/profile` - [TikTok Transcript API](https://staging-fly.socialtap.dev/docs/reference/tiktok/transcript/index.md) `GET /v1/tiktok/transcript` ### [YouTube](https://staging-fly.socialtap.dev/apis/youtube/index.md) - [YouTube Comments API](https://staging-fly.socialtap.dev/docs/reference/youtube/comments/index.md) `GET /v1/youtube/comments` - [YouTube Video API](https://staging-fly.socialtap.dev/docs/reference/youtube/post/index.md) `GET /v1/youtube/post` - [YouTube Channel Videos API](https://staging-fly.socialtap.dev/docs/reference/youtube/posts/index.md) `GET /v1/youtube/posts` - [YouTube Channel API](https://staging-fly.socialtap.dev/docs/reference/youtube/profile/index.md) `GET /v1/youtube/profile` - [YouTube Transcript API](https://staging-fly.socialtap.dev/docs/reference/youtube/transcript/index.md) `GET /v1/youtube/transcript` --- Source: https://staging-fly.socialtap.dev/docs/authentication/ # Authentication Get started Authenticate every request with your API key in a header. One key works for every endpoint and platform. Send your API key in the `x-api-key` header of every request: Shell: ```shell curl -H "x-api-key: $SOCIALTAP_API_KEY" "https://staging-fly.socialtap.dev/v1/tiktok/profile?..." ``` `authorization: Bearer ` works too (what most HTTP and MCP clients send); when both are sent, `x-api-key` wins. > [!WARNING] > > The key is a secret. Call SocialTap from your server or backend jobs, never from code that ships to browsers or apps, and rotate the key if it leaks. ## Keys, scopes and modes Get a key on the [signup page](https://staging-fly.socialtap.dev/signup/); AI agents can sign up through the API (see [account and API keys](https://staging-fly.socialtap.dev/docs/account/index.md)). Keys starting with `st_live_` are live: real requests, billed. Keys starting with `st_test_` are test keys: every endpoint answers from recorded responses, free (see [test mode](https://staging-fly.socialtap.dev/docs/test-mode/index.md)). Each key has scopes (what it may call) and can have a spend limit, an expiry and an IP allowlist; a key that may not make a request gets [`forbidden`](https://staging-fly.socialtap.dev/docs/errors/index.md#forbidden) (HTTP 403) with the reason. Manage keys in the [dashboard](https://staging-fly.socialtap.dev/dashboard/) or with the API keys routes. ## Request headers | Header | Type | Description | | --- | --- | --- | | `x-api-key` | `string` | Your API key. | | `authorization` | `string` | Bearer \: the same key as x-api-key, in the form most HTTP and MCP clients send (x-api-key wins when both are sent). | | `x-client-request-id` | `string` | Your own ID for the request (1–128 printable ASCII characters): echoed back and logged with ours, so support can find a request whose response never reached you. | | `traceparent` | `string` | W3C Trace Context of your trace: recorded with the request (its trace ID) so a trace can be matched to ours; SocialTap starts its own trace. | | `idempotency-key` | `string` | On any POST: your unique key for the request (1–255 printable ASCII characters; a UUID is ideal), kept 24 hours. Retrying with the same key and body never repeats the work: the answer is the first request's result, in its current state. | | `prefer` | `string` | RFC 7240. respond-async on any endpoint request runs it as a one-request job: the answer is 202 with the job (Location: its URL) instead of the result. | | `cache-control` | `string` | max-age=N accepts a cached response up to N seconds old, like the max_age parameter (max_age wins when both are sent). Other directives are ignored. | ## Response headers | Header | Type | Description | | --- | --- | --- | | `x-request-id` | `string` | Unique request ID; include it when contacting support. | | `x-credits-charged` | `int` | Credits charged for this request (0 for errors and cache hits). | | `x-credits-remaining` | `int` | Account balance after this request; for a busy account an estimate that may not yet subtract concurrent requests (about a second of usage), never below the real balance. | | `retry-after` | `int` | Seconds to wait before retrying; sent only with the codes whose retry_after is set in the error codes list (never on credit errors). | | `allow` | `string` | Methods the endpoint accepts (method_not_allowed only). | | `age` | `int` | Seconds since the data was fetched (cache hits only). | | `x-client-request-id` | `string` | Your x-client-request-id, echoed (also meta.client_request_id). | | `ratelimit-policy` | `string` | The account's runaway-protection ceiling as an IETF RateLimit-Policy field, as enforced: a token bucket of q requests (twice the ceiling in requests per second, at least 10) that refills in w seconds: "account";q=\;w=\. Not a plan limit. | | `ratelimit` | `string` | What is left of the ceiling (IETF RateLimit field): "account";r=\;t=\. | | `deprecation` | `string` | RFC 9745: the endpoint is deprecated, as of this date (@\; may be in the future). Also in meta.warnings. | | `sunset` | `string` | RFC 8594: when a deprecated endpoint stops answering (HTTP-date); from then on it answers endpoint_retired. | | `link` | `string` | RFC 8288 links: rel="next" is the next page of a list (while page.has_more), the same request with cursor set; rel="deprecation" is a deprecated endpoint's notice. | | `location` | `string` | On 202 answers: the job's URL (GET it, free, for its status). | | `preference-applied` | `string` | RFC 7240: respond-async when Prefer: respond-async turned the request into a job (202). | | `idempotent-replayed` | `bool` | true when the answer replays an earlier request with the same Idempotency-Key (it shows the resource's current state). | `x-credits-charged` and `x-credits-remaining` are on every response to an authenticated request, successful or not: errors show a charge of 0 and your unchanged balance. A request rejected for its key has neither, because there is no account to report on. ## Missing or invalid keys A request without a key, or with an unknown or revoked one, fails with [`unauthorized`](https://staging-fly.socialtap.dev/docs/errors/index.md#unauthorized) (HTTP 401), carries no credit headers and costs nothing. ## Rate limits Plans do not limit request rate: you pay for credits (see [Pricing](https://staging-fly.socialtap.dev/pricing/index.md)), and you can send requests as fast and as many in parallel as your balance covers. Each account has a high safety ceiling on requests per second that only stops runaway clients, such as a retry loop without backoff. Above it, requests fail with [`rate_limited`](https://staging-fly.socialtap.dev/docs/errors/index.md#rate_limited) (HTTP 429) and cost nothing; wait the number of seconds in the `retry-after` header, then retry. If your sustained traffic needs more, ask [support](https://staging-fly.socialtap.dev/docs/index.md) and the ceiling is raised for your account. --- Source: https://staging-fly.socialtap.dev/docs/credits/ # Credits and caching Get started How requests are priced, reserved and charged, how to read what you paid, and how to get free cache hits. SocialTap is prepaid: you buy credits, and each successful request spends some of them. One balance covers every endpoint on every platform. - Failed requests are free: errors cost 0 credits. - Cache hits are free: responses served from the cache cost 0 credits. - Credits never expire. - One credit balance for every endpoint and platform. ## How a request is charged 1. **Reserve.** Before calling the platform, SocialTap checks that your balance covers the endpoint's minimum price and sets that minimum aside, nothing more. If the balance is below the minimum, the request fails immediately with [`insufficient_credits`](https://staging-fly.socialtap.dev/docs/errors/index.md#insufficient_credits). 2. **Fetch.** Endpoints try their sources cheapest first and stop at the first one that works. The source that produced the result is reported as `meta.source` (see sources). Right before a source that costs more than what is set aside (a price that grows with the size of the item), SocialTap sets aside more of your balance: the exact price when the size of the item is known in advance, otherwise as much as your balance allows, and it then processes only as much of the item as that pays for. A source your balance can't pay for is skipped. 3. **Charge.** The price of the result is charged and the rest released. If the result would cost more than your balance covers, the request fails with [`insufficient_credits`](https://staging-fly.socialtap.dev/docs/errors/index.md#insufficient_credits) instead and costs nothing. A failed request releases everything. A request that succeeds after you hang up (a timeout, a closed connection) is still charged. Its result is cached: retry with `max_age` to get it as a free cache hit. What you paid is in `meta.credits_charged` and in the `x-credits-charged` header. `x-credits-remaining` is your account balance right after this request was charged; other requests of yours still in flight may lower it further, so with requests in parallel treat it as an upper bound. Both headers are on every response to an authenticated request, errors included (with a charge of 0); a [`unauthorized`](https://staging-fly.socialtap.dev/docs/errors/index.md#unauthorized) response has neither. ### Reservations A request starts only if your balance covers the endpoint's minimum, and only that minimum is set aside when it starts. More is set aside just before costlier work, and only what that work needs. Requests running in parallel therefore don't each lock up the most they could cost: your balance has to cover their minimums, plus the actual price of whatever costlier work they reach. Prices that grow with the size of the item have a ceiling, because items above a size limit are refused; that ceiling is the most one request can cost. | Endpoint | Balance needed to start | Most one request can cost | | --- | --- | --- | | [Facebook Post API](https://staging-fly.socialtap.dev/docs/reference/facebook/post/index.md) | 1 credit | 1 credit | | [Facebook Ad Library Search API](https://staging-fly.socialtap.dev/docs/reference/facebook/search_ads/index.md) | 1 credit | 1 credit | | [Facebook Video Transcript API](https://staging-fly.socialtap.dev/docs/reference/facebook/transcript/index.md) | 1 credit | 122 credits | | [Instagram Post API](https://staging-fly.socialtap.dev/docs/reference/instagram/post/index.md) | 1 credit | 1 credit | | [Instagram Posts API](https://staging-fly.socialtap.dev/docs/reference/instagram/posts/index.md) | 1 credit | 1 credit | | [Instagram Profile API](https://staging-fly.socialtap.dev/docs/reference/instagram/profile/index.md) | 1 credit | 1 credit | | [Instagram Reels Transcript API](https://staging-fly.socialtap.dev/docs/reference/instagram/transcript/index.md) | 1 credit | 122 credits | | [Rumble Comments API](https://staging-fly.socialtap.dev/docs/reference/rumble/comments/index.md) | 1 credit | 1 credit | | [Rumble Video API](https://staging-fly.socialtap.dev/docs/reference/rumble/post/index.md) | 1 credit | 1 credit | | [Rumble Channel Videos API](https://staging-fly.socialtap.dev/docs/reference/rumble/posts/index.md) | 1 credit | 1 credit | | [Rumble Channel API](https://staging-fly.socialtap.dev/docs/reference/rumble/profile/index.md) | 1 credit | 1 credit | | [Rumble Search API](https://staging-fly.socialtap.dev/docs/reference/rumble/search_posts/index.md) | 1 credit | 1 credit | | [Rumble Transcript API](https://staging-fly.socialtap.dev/docs/reference/rumble/transcript/index.md) | 1 credit | 122 credits | | [Threads Profile API](https://staging-fly.socialtap.dev/docs/reference/threads/profile/index.md) | 1 credit | 1 credit | | [TikTok Comments API](https://staging-fly.socialtap.dev/docs/reference/tiktok/comments/index.md) | 1 credit | 1 credit | | [TikTok Post API](https://staging-fly.socialtap.dev/docs/reference/tiktok/post/index.md) | 1 credit | 1 credit | | [TikTok Posts API](https://staging-fly.socialtap.dev/docs/reference/tiktok/posts/index.md) | 1 credit | 1 credit | | [TikTok Profile API](https://staging-fly.socialtap.dev/docs/reference/tiktok/profile/index.md) | 1 credit | 1 credit | | [TikTok Transcript API](https://staging-fly.socialtap.dev/docs/reference/tiktok/transcript/index.md) | 1 credit | 122 credits | | [YouTube Comments API](https://staging-fly.socialtap.dev/docs/reference/youtube/comments/index.md) | 1 credit | 1 credit | | [YouTube Video API](https://staging-fly.socialtap.dev/docs/reference/youtube/post/index.md) | 1 credit | 1 credit | | [YouTube Channel Videos API](https://staging-fly.socialtap.dev/docs/reference/youtube/posts/index.md) | 1 credit | 1 credit | | [YouTube Channel API](https://staging-fly.socialtap.dev/docs/reference/youtube/profile/index.md) | 1 credit | 1 credit | | [YouTube Transcript API](https://staging-fly.socialtap.dev/docs/reference/youtube/transcript/index.md) | 1 credit | 122 credits | ## Prices A price is either flat ("1 credit") or a base plus a per-unit amount, for sources whose cost grows with the size of the item; the unit is part of the price. | Endpoint | Source | Price per successful request | Per 1,000 requests | | --- | --- | --- | --- | | [Facebook Post API](https://staging-fly.socialtap.dev/apis/facebook/post/index.md) | Public page `page` | 1 credit | $0.75–$1.45 | | [Facebook Ad Library Search API](https://staging-fly.socialtap.dev/apis/facebook/search_ads/index.md) | Platform API `api` | 1 credit | $0.75–$1.45 | | [Facebook Video Transcript API](https://staging-fly.socialtap.dev/apis/facebook/transcript/index.md) | Captions `captions` | 1 credit | $0.75–$1.45 | | [Facebook Video Transcript API](https://staging-fly.socialtap.dev/apis/facebook/transcript/index.md) | Speech-to-text `asr` | 2 credits + 2 per started minute of media
up to 122 credits (60 started minutes of media) | $1.50–$2.90 + $1.50–$2.90 per started minute of media | | [Instagram Post API](https://staging-fly.socialtap.dev/apis/instagram/post/index.md) | Public page `page` | 1 credit | $0.75–$1.45 | | [Instagram Posts API](https://staging-fly.socialtap.dev/apis/instagram/posts/index.md) | Any source | 1 credit | $0.75–$1.45 | | [Instagram Profile API](https://staging-fly.socialtap.dev/apis/instagram/profile/index.md) | Public page `page` | 1 credit | $0.75–$1.45 | | [Instagram Reels Transcript API](https://staging-fly.socialtap.dev/apis/instagram/transcript/index.md) | Captions `captions` | 1 credit | $0.75–$1.45 | | [Instagram Reels Transcript API](https://staging-fly.socialtap.dev/apis/instagram/transcript/index.md) | Speech-to-text `asr` | 2 credits + 2 per started minute of media
up to 122 credits (60 started minutes of media) | $1.50–$2.90 + $1.50–$2.90 per started minute of media | | [Rumble Comments API](https://staging-fly.socialtap.dev/apis/rumble/comments/index.md) | Platform API `api` | 1 credit | $0.75–$1.45 | | [Rumble Video API](https://staging-fly.socialtap.dev/apis/rumble/post/index.md) | Public page `page` | 1 credit | $0.75–$1.45 | | [Rumble Channel Videos API](https://staging-fly.socialtap.dev/apis/rumble/posts/index.md) | Any source | 1 credit | $0.75–$1.45 | | [Rumble Channel API](https://staging-fly.socialtap.dev/apis/rumble/profile/index.md) | Public page `page` | 1 credit | $0.75–$1.45 | | [Rumble Search API](https://staging-fly.socialtap.dev/apis/rumble/search_posts/index.md) | Public page `page` | 1 credit | $0.75–$1.45 | | [Rumble Transcript API](https://staging-fly.socialtap.dev/apis/rumble/transcript/index.md) | Captions `captions` | 1 credit | $0.75–$1.45 | | [Rumble Transcript API](https://staging-fly.socialtap.dev/apis/rumble/transcript/index.md) | Speech-to-text `asr` | 2 credits + 2 per started minute of media
up to 122 credits (60 started minutes of media) | $1.50–$2.90 + $1.50–$2.90 per started minute of media | | [Threads Profile API](https://staging-fly.socialtap.dev/apis/threads/profile/index.md) | Platform API `api` | 1 credit | $0.75–$1.45 | | [Threads Profile API](https://staging-fly.socialtap.dev/apis/threads/profile/index.md) | Public page `page` | 1 credit | $0.75–$1.45 | | [TikTok Comments API](https://staging-fly.socialtap.dev/apis/tiktok/comments/index.md) | Platform API `api` | 1 credit | $0.75–$1.45 | | [TikTok Post API](https://staging-fly.socialtap.dev/apis/tiktok/post/index.md) | Public page `page` | 1 credit | $0.75–$1.45 | | [TikTok Posts API](https://staging-fly.socialtap.dev/apis/tiktok/posts/index.md) | Any source | 1 credit | $0.75–$1.45 | | [TikTok Profile API](https://staging-fly.socialtap.dev/apis/tiktok/profile/index.md) | Public page `page` | 1 credit | $0.75–$1.45 | | [TikTok Transcript API](https://staging-fly.socialtap.dev/apis/tiktok/transcript/index.md) | Captions `captions` | 1 credit | $0.75–$1.45 | | [TikTok Transcript API](https://staging-fly.socialtap.dev/apis/tiktok/transcript/index.md) | Speech-to-text `asr` | 2 credits + 2 per started minute of media
up to 122 credits (60 started minutes of media) | $1.50–$2.90 + $1.50–$2.90 per started minute of media | | [YouTube Comments API](https://staging-fly.socialtap.dev/apis/youtube/comments/index.md) | Platform API `api` | 1 credit | $0.75–$1.45 | | [YouTube Video API](https://staging-fly.socialtap.dev/apis/youtube/post/index.md) | Platform API `api` | 1 credit | $0.75–$1.45 | | [YouTube Video API](https://staging-fly.socialtap.dev/apis/youtube/post/index.md) | Public page `page` | 1 credit | $0.75–$1.45 | | [YouTube Channel Videos API](https://staging-fly.socialtap.dev/apis/youtube/posts/index.md) | Any source | 1 credit | $0.75–$1.45 | | [YouTube Channel API](https://staging-fly.socialtap.dev/apis/youtube/profile/index.md) | Any source | 1 credit | $0.75–$1.45 | | [YouTube Transcript API](https://staging-fly.socialtap.dev/apis/youtube/transcript/index.md) | Captions `captions` | 1 credit | $0.75–$1.45 | | [YouTube Transcript API](https://staging-fly.socialtap.dev/apis/youtube/transcript/index.md) | Speech-to-text `asr` | 2 credits + 2 per started minute of media
up to 122 credits (60 started minutes of media) | $1.50–$2.90 + $1.50–$2.90 per started minute of media | ### Sources | meta.source | Source | What it means | | --- | --- | --- | | `captions` | Captions | The platform's own caption track (uploaded or auto-generated). | | `asr` | Speech-to-text | Transcribed by SocialTap from the media's audio when the platform has no captions. | | `api` | Platform API | The platform's public, logged-out API. | | `page` | Public page | Data embedded in the platform's public web page. | ## Packs | Credits | Price | Per 1,000 credits | | --- | --- | --- | | 20,000 | $29 | $1.45 | | 80,000 | $99 | $1.24 | | 400,000 | $399 | $1.00 | | 2,000,000 | $1,499 | $0.75 | Buy a pack in the [dashboard](https://staging-fly.socialtap.dev/dashboard/), or with `POST /v1/billing/checkout`: it answers a Stripe Checkout URL to open and pay; the credits are added when the payment completes. Read your balance with `GET /v1/account`. ## Caching Every endpoint accepts these options: | Parameter | Type | Description | | --- | --- | --- | | `max_age` | `integer`
min 0 | Accept a cached response up to this many seconds old. Cache hits cost 0 credits. Omit to always fetch fresh. The Cache-Control: max-age=N request header is equivalent; max_age wins when both are sent. | | `fields` | `string` | Return only these fields of each item: comma-separated paths in the schema's notation (id,text,metrics.view_count,segments\[\].text; a parent path keeps the whole object). Unknown paths are rejected. Applied after the cache, so it changes neither the price nor cache hits; page and meta are always returned whole. | With `max_age=N`, SocialTap may answer from a stored response that is at most N seconds old. Cached responses have `meta.is_cached` set to `true` and are free. > [!TIP] > > Send `max_age` whenever slightly stale data is fine, for example when several of your users ask for the same item: every cache hit is free. --- Source: https://staging-fly.socialtap.dev/docs/test-mode/ # Test mode Get started Test keys answer every endpoint from recorded responses, free and deterministic, and magic inputs trigger every error code, so you can build and test an integration without spending credits. A key that starts with `st_test_` is a **test key**. It works like a live key, with three differences: 1. **Recorded answers.** Every endpoint answers from its recorded responses (the same recordings the docs examples come from), never from the platform. Responses have the live schema; `meta.mode` is `test`. 2. **Free and deterministic.** Nothing is charged: `meta.credits_charged` is 0, and the balance a test key sees is always 10,000 credits. The same request always gets the same answer. 3. **No platform traffic.** Test requests are not metered in usage reports. Get one with the [signup page](https://staging-fly.socialtap.dev/signup/) (every new account has one) or, as an AI agent, from the agent signup in [account and API keys](https://staging-fly.socialtap.dev/docs/account/index.md). > [!TIP] > > No account yet? The [quickstart](https://staging-fly.socialtap.dev/docs/index.md) has a public test key that answers every endpoint the same way, so you can try every request on this page right now. ## Which answer a request gets - A **recorded input**: each endpoint's page lists the inputs it has recordings for. Sending one returns that recording, success or error. - A **magic input** (below): returns that error code. - **Anything else** live mode would accept: returns the endpoint's docs example, with a `test_default_example` entry in `meta.warnings` naming your input, so a test never passes on another item's data unnoticed. Requests are validated as in live mode: a missing or out-of-range parameter, a malformed date or a link of another kind or platform fails with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input), with the same `param` and message. (A link on the endpoint's own platform in a form no recording classifies gets the docs example and the warning; live mode fetches it.) Unknown parameters, `fields` and deprecation work as in live mode. Lists are paged over the recorded items: cursors start with `t1.`, expire like the endpoint's live cursors and continue only the request that issued them; any other cursor is [`invalid_cursor`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_cursor). ## Magic inputs Send one of these values as the endpoint's identifying parameter to get the error code, with the status, body, `details` and (where the code has one) the `retry-after` header that live mode sends. Each endpoint accepts the magic inputs of the codes it can return; its page lists them. | Error code | Status | `handle` | `query` | `url` | | --- | --- | --- | --- | --- | | [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input) | 400 | `socialtap_test_invalid_input` | `socialtap_test_invalid_input` | `https://socialtap.dev/test/invalid_input` | | [`invalid_cursor`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_cursor) | 400 | `socialtap_test_invalid_cursor` | `socialtap_test_invalid_cursor` | `https://socialtap.dev/test/invalid_cursor` | | [`insufficient_credits`](https://staging-fly.socialtap.dev/docs/errors/index.md#insufficient_credits) | 402 | `socialtap_test_insufficient_credits` | `socialtap_test_insufficient_credits` | `https://socialtap.dev/test/insufficient_credits` | | [`spend_limit_reached`](https://staging-fly.socialtap.dev/docs/errors/index.md#spend_limit_reached) | 402 | `socialtap_test_spend_limit_reached` | `socialtap_test_spend_limit_reached` | `https://socialtap.dev/test/spend_limit_reached` | | [`rate_limited`](https://staging-fly.socialtap.dev/docs/errors/index.md#rate_limited) | 429 | `socialtap_test_rate_limited` | `socialtap_test_rate_limited` | `https://socialtap.dev/test/rate_limited` | | [`not_found`](https://staging-fly.socialtap.dev/docs/errors/index.md#not_found) | 404 | `socialtap_test_not_found` | `socialtap_test_not_found` | `https://socialtap.dev/test/not_found` | | [`private_or_unavailable`](https://staging-fly.socialtap.dev/docs/errors/index.md#private_or_unavailable) | 403 | `socialtap_test_private_or_unavailable` | `socialtap_test_private_or_unavailable` | `https://socialtap.dev/test/private_or_unavailable` | | [`no_transcript`](https://staging-fly.socialtap.dev/docs/errors/index.md#no_transcript) | 404 | `socialtap_test_no_transcript` | `socialtap_test_no_transcript` | `https://socialtap.dev/test/no_transcript` | | [`upstream_blocked`](https://staging-fly.socialtap.dev/docs/errors/index.md#upstream_blocked) | 503 | `socialtap_test_upstream_blocked` | `socialtap_test_upstream_blocked` | `https://socialtap.dev/test/upstream_blocked` | | [`upstream_error`](https://staging-fly.socialtap.dev/docs/errors/index.md#upstream_error) | 502 | `socialtap_test_upstream_error` | `socialtap_test_upstream_error` | `https://socialtap.dev/test/upstream_error` | | [`overloaded`](https://staging-fly.socialtap.dev/docs/errors/index.md#overloaded) | 503 | `socialtap_test_overloaded` | `socialtap_test_overloaded` | `https://socialtap.dev/test/overloaded` | | [`internal`](https://staging-fly.socialtap.dev/docs/errors/index.md#internal) | 500 | `socialtap_test_internal` | `socialtap_test_internal` | `https://socialtap.dev/test/internal` | Errors about the key itself ([`unauthorized`](https://staging-fly.socialtap.dev/docs/errors/index.md#unauthorized), [`forbidden`](https://staging-fly.socialtap.dev/docs/errors/index.md#forbidden)) are reached the same way as with a live key: send no key, or a key without the scope. ## Account routes in test mode | Routes | In test mode | | --- | --- | | `GET /v1/account` | A test key sees the fixed test balance. | | `PATCH /v1/account` | A test key's change is validated and answered but not stored: test mode never changes the live account. | | `POST /v1/api_keys`
`GET /v1/api_keys/{id}`
`GET /v1/api_keys`
`DELETE /v1/api_keys/{id}`
`PATCH /v1/api_keys/{id}` | A test key sees and manages test keys only. | | `POST /v1/billing/checkout` | A test key gets a placeholder session (no Stripe call, nothing to pay). | | `GET /v1/credit_transactions`
`GET /v1/usage` | A test key gets an empty report: test requests are not metered. | | `GET /v1/events/{id}`
`GET /v1/events`
`POST /v1/events/{id}/redeliver` | A test key sees test events only (a test job's job.\* events and webhook.test); credits.\* and api_key.\* events are live only. | | `POST /v1/jobs/{id}/cancel`
`POST /v1/jobs`
`GET /v1/jobs/{id}`
`GET /v1/jobs`
`GET /v1/jobs/{id}/results` | A test key's job runs every item in test mode: each gets its endpoint's test-mode answer (recorded responses, magic inputs), free; nothing is held or charged. A test key sees test jobs only. | | `POST /v1/webhook_endpoints`
`DELETE /v1/webhook_endpoints/{id}`
`GET /v1/webhook_endpoints/{id}/deliveries`
`GET /v1/webhook_endpoints/{id}`
`GET /v1/webhook_endpoints`
`POST /v1/webhook_endpoints/{id}/rotate_secret`
`POST /v1/webhook_endpoints/{id}/test`
`PATCH /v1/webhook_endpoints/{id}` | A test key manages the account's test webhook endpoints, separate from live ones; they receive test events only (a test job's job.\* events and webhook.test), signed and retried like live ones. | --- Source: https://staging-fly.socialtap.dev/docs/requests/ # Requests Using the API How every endpoint takes its input, the parameter names shared by all of them, and how unknown or mistyped parameters are answered. Every endpoint follows the same conventions, so once you have called one you can call them all. ## One identifying parameter Each endpoint takes exactly one parameter that says what to fetch, named for what you have in hand: - **`url`** for an item and an item's lists (a video's transcript, a post's comments): a canonical link, a short or share link, a mobile link or the platform's own ID. - **`handle`** for a profile and a profile's lists: a profile link, `@handle`, a username or the platform's own ID. - **`query`** for searches (endpoints named `search_…`). Each endpoint page lists the kinds of link its parameter takes. A link of another kind, or from another platform, fails with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input) at no charge, and the error names the endpoint that takes it: `error.details` says what the link is (`url_kind`, `url_platform`) and which endpoints take it (`endpoints`). The links SocialTap recognizes: | Platform | Kind of link | Example | | --- | --- | --- | | Facebook | video | `https://www.facebook.com/reel/1803909017420449` | | | video | `https://fb.watch/abcDEF123/` | | | video | `https://www.facebook.com/share/v/1AbCdEfGh/` | | | hashtag | `https://www.facebook.com/hashtag/cooking` | | | post | `https://www.facebook.com/NASA/posts/pfbid02abc` | | | post | `https://www.facebook.com/share/p/1AbCdEfGh/` | | | post | `https://www.facebook.com/1515533333772646` | | | profile | `https://www.facebook.com/NASA` | | Instagram | video | `https://www.instagram.com/reel/De1S3KpCXf0/` | | | post | `https://www.instagram.com/p/C0AbCdEfGhI/` | | | hashtag | `https://www.instagram.com/explore/tags/cooking/` | | | profile | `https://www.instagram.com/natgeo/` | | | profile | `https://www.instagram.com/_u/natgeo` | | | profile | `https://www.instagram.com/stories/natgeo/` | | Rumble | video | `https://rumble.com/v79xhhm-discovery-why-glenn-wants-israel-to-sue-the-new-york-times.html` | | | video | `https://rumble.com/v79xhhm` | | | video | `https://rumble.com/shorts/v7hgv02` | | | video | `https://rumble.com/share/v79xhhm` | | | video | `https://rumble.com/embed/v77qthe/` | | | profile | `https://rumble.com/c/Bongino` | | | profile | `https://rumble.com/user/vivafrei` | | | profile | `https://rumble.com/Bongino` | | Threads | post | `https://www.threads.com/@zuck/post/DeZtp-TkQHh` | | | profile | `https://www.threads.com/@zuck` | | TikTok | video | `https://www.tiktok.com/@scout2015/video/6718335390845095173` | | | post | `https://www.tiktok.com/@nasa/photo/7310000000000000000` | | | hashtag | `https://www.tiktok.com/tag/cooking` | | | profile | `https://www.tiktok.com/@scout2015` | | | profile | `https://www.tiktok.com/share/user/6745191554350760966` | | | video | `https://vm.tiktok.com/ZMhdWxvaW/` | | | video | `https://www.tiktok.com/t/ZSgAbqM38/` | | YouTube | video | `https://www.youtube.com/watch?v=dQw4w9WgXcQ` | | | video | `https://www.youtube.com/shorts/dQw4w9WgXcQ` | | | video | `https://youtu.be/dQw4w9WgXcQ` | | | playlist | `https://www.youtube.com/playlist?list=PLFgquLnL59alCl_2TQvOiD5Vgm1hCaGSI` | | | hashtag | `https://www.youtube.com/hashtag/cooking` | | | profile | `https://www.youtube.com/@RickAstleyYT` | | | profile | `https://www.youtube.com/channel/UCuAXFkgsw1L7xaCfnd5JJOw` | | | profile | `https://www.youtube.com/c/RickAstleyYT` | | | profile | `https://www.youtube.com/user/RickAstleyVEVO` | | | profile | `https://www.youtube.com/pewdiepie` | The response says what your input resolved to in meta.input: the kind of item, its ID on the platform and its canonical URL. A short link shows the item it pointed to, so you can check you fetched what you meant. ## Shared parameters Parameters that mean the same thing on every endpoint have one name, one format and one validation. An endpoint offers the ones that apply to it (its page lists them), and never under another name: the last column lists names other APIs use that are answered with a suggestion instead. | Parameter | Type | Values | Meaning | Not | | --- | --- | --- | --- | --- | | `lang` | `string`
bcp47 | — | Preferred language: a BCP-47 tag such as en or pt-BR (case-insensitive; _ is read as -). | `hl`, `lang_code`, `language`, `language_code`, `locale` | | `country` | `string`
iso3166-1-alpha-2 | — | Region: an ISO 3166-1 alpha-2 country code such as US or BR (case-insensitive). | `country_code`, `geo`, `gl`, `market`, `region` | | `sort` | `string` | `relevance`, `newest`, `oldest`, `popular`
each endpoint offers a subset | Order of the results. | `order`, `order_by`, `orderby`, `sort_by`, `sortby` | | `status` | `string` | `active`, `inactive`, `all`
each endpoint offers a subset | Only items that are still running (active), only items that stopped (inactive), or both (all). | `active`, `active_status`, `ad_active_status`, `state` | | `since` | `string`
date-time | — | Only items at or after this time: RFC 3339 (2026-10-01T12:00:00Z) or a date (2026-10-01, midnight UTC). On newest-first lists, paging stops when it is reached, so later pages are not fetched or charged.
Only on lists ordered by time. | `after`, `date_from`, `from`, `from_date`, `min_date`, `start`, `start_date`, `start_time` | | `until` | `string`
date-time | — | Only items before this time (exclusive): RFC 3339 or a date (midnight UTC).
Only on lists ordered by time. | `before`, `date_to`, `end`, `end_date`, `end_time`, `max_date`, `to`, `to_date` | Every endpoint also accepts the common parameters (max_age, fields; see [Credits and caching](https://staging-fly.socialtap.dev/docs/credits/index.md#param-max_age)), and list endpoints limit and cursor (see [Pagination](https://staging-fly.socialtap.dev/docs/pagination/index.md)). ## Unknown parameters are rejected A parameter the endpoint does not accept fails with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input), free, before anything is fetched. A typo can't silently change nothing, and junk parameters can't make a request look new to the cache. The error names the parameter in `error.param`, the one you most likely meant in `details.suggestion` (`username` is answered with `handle`, `lnag` with `lang`), and every accepted parameter in `details.accepted_params`. ## Selecting fields `fields` returns only the fields you name, to cut payloads and tokens: comma-separated paths in the notation of the resource's [schema](https://staging-fly.socialtap.dev/resources/index.md) (`id,text`, `metrics.view_count`, `segments[].text`; a parent path such as `metrics` keeps the whole object). It applies to the item, or to each item of a list page; `page` and `meta` always come back whole. Fields you leave out are absent, so `null` still only ever means unknown. An unknown path fails with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input) and lists every valid path in `details.fields`. Selection happens after the cache: it changes neither the price nor whether a request is a cache hit, and requests that differ only in `fields` share one cached result. ## Freshness Responses are fetched fresh unless you opt into the cache with max_age (see [Credits and caching](https://staging-fly.socialtap.dev/docs/credits/index.md#param-max_age)). The standard `cache-control: max-age=N` request header works too; when you send both, max_age wins. Values above the longest a result is kept are reduced to it. ## Request IDs and tracing Every response carries our `x-request-id`; quote it when you contact [support](https://staging-fly.socialtap.dev/docs/index.md). You can also send your own ID for the request in `x-client-request-id` (1 to 128 printable ASCII characters): it is echoed back in the same header and in meta.client_request_id, and logged with ours, so a request whose response never reached you can still be found. A malformed one fails with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input) before anything runs. A W3C `traceparent` is accepted too: its trace ID is recorded with the request so your trace can be matched to ours (SocialTap starts its own trace; a malformed `traceparent` is ignored). ## Rate limit headers Plans don't limit request rate, but every account has a high runaway-protection ceiling (see [Rate limits](https://staging-fly.socialtap.dev/docs/authentication/index.md#rate-limits)). Every response states it in the standard IETF fields: `ratelimit-policy` is the ceiling as it is enforced: a bucket of `q` requests that refills in `w` seconds (`"account";q=2000;w=2` for a ceiling of 1,000 requests per second, which admits a burst of 2,000 at once), and `ratelimit` what is left of it (`"account";r=1997;t=1`: requests you may send at once now, and seconds until the bucket is full again). Above it, requests fail with [`rate_limited`](https://staging-fly.socialtap.dev/docs/errors/index.md#rate_limited) and a `retry-after`. ## Warnings meta.warnings lists things to act on that did not fail the request, such as an endpoint that will be retired. It is always present, and empty when there is nothing to say. Each warning has a `code`, a `message` and a `doc_url`: | Warning | Meaning | | --- | --- | | `deprecated_endpoint` | The endpoint is deprecated: it stops answering on its sunset date, after which it returns endpoint_retired. The message names the date and the replacement. | | `test_default_example` | Test mode only: no recorded answer exists for the input you sent, so the answer is the endpoint's docs example (meta.input names the item it is about), not the item you named. The input was validated as a live request would be; a live key fetches the item itself. | | `partial_data` | Part of the data could not be fetched (a secondary request to the platform failed) while the rest is valid: the fields listed in fields are null (or \[\]) although the platform has them. The request was charged as usual; repeat it later for the full data. | --- Source: https://staging-fly.socialtap.dev/docs/errors/ # Errors Using the API Every error code, its HTTP status, whether a later retry can succeed, and what to do about it. Errors return a non-2xx status and a JSON body with one `error` object. Everything a program or an agent needs to react is in the body, so a client that only sees the JSON can still decide what to do: 400 response: ```json {"error":{"code":"invalid_input","message":"unknown parameter \"username\"; did you mean \"handle\"? This en…","request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","status":400,"retryable":false,"param":"username","remediation":"Fix the parameter named in param (the message says how); for…","doc_url":"https://staging-fly.socialtap.dev/docs/errors/#invalid_input","details":{"accepted_params":["handle","max_age"],"suggestion":"handle"},"charged":false}} ``` > [!IMPORTANT] > > Branch on `error.code`: its values are stable. `error.message` is for people, and its wording may change. Every field is always present: | Field | Type | Description | | --- | --- | --- | | `error.code` | `string` | Machine-readable error code (see the error codes list); stable. | | `error.message` | `string` | What went wrong and how to fix it, for humans and agents; wording may change. | | `error.request_id` | `string` | Unique request ID; include it when contacting support. | | `error.status` | `integer` | The HTTP status of the response, repeated for clients that only see the body. | | `error.retryable` | `boolean` | True when repeating the same request later may succeed (after details.retry_after when present). | | `error.param` | `string` or `null` | The query parameter or header the error is about; null when it is not about one input. | | `error.remediation` | `string` | What to do about this code, one sentence. | | `error.doc_url` | `string` | URL of this code's documentation. | | `error.details` | `object` | The code's dynamic values (the error codes list documents each code's keys); {} when none. | | `error.charged` | `boolean` | Whether the request was charged: always false, failed requests are free. | - **`error.param`** names the query parameter or header the error is about, so you can fix the call without parsing the message. - **`error.details`** holds the code's dynamic values: the parameter you most likely meant, the endpoint a link belongs to, the credits a request needed. The table below lists each code's keys. Keys are only ever added. - **`error.charged`** is always false: failed requests are free (see [Credits](https://staging-fly.socialtap.dev/docs/credits/index.md)), so a retry costs nothing. ## Error codes **Retry** says whether sending the same request again later can get a different answer. **What to do** is the recommended reaction. Codes marked **Reserved** are documented ahead of the feature that returns them; nothing returns them yet. | Code | HTTP | Retry | Meaning | What to do | details | | --- | --- | --- | --- | --- | --- | | `invalid_input` | 400 | No | The request parameters are missing or malformed, or the URL is not a supported link for this endpoint. | Fix the parameter named in param (the message says how); for a link of the wrong kind, call the endpoint the message names. | `suggestion` `string`: For an unknown parameter: the accepted parameter it most likely means; absent when nothing is close.`accepted_params` `string[]`: For an unknown parameter: every query parameter this endpoint accepts.`url_kind` `string`: For a link of the wrong kind: what the link points to (profile, video, post, …).`url_platform` `string`: For a link of the wrong kind: the platform the link belongs to.`accepted_url_kinds` `string[]`: For a link of the wrong kind: the kinds of link this endpoint takes.`endpoints` `string[]`: For a link of the wrong kind: IDs of the endpoints that take it; empty when none does yet.`fields` `string[]`: For an unknown fields path: every path fields accepts on this endpoint. | | `invalid_cursor` | 400 | No | The cursor is malformed, expired, belongs to another endpoint, or the other parameters differ from the request that issued it. | Repeat the original request without cursor to start the list again. | `reason` `string`: Why: malformed, expired, wrong_endpoint, params_changed or pin_lost (the upstream exit a cursor-bound list was pinned to is gone; new values may be added). | | `unauthorized` | 401 | No | The x-api-key header is missing, unknown or revoked (or, on the dashboard's routes, the session cookie is missing or expired). | Send a valid API key in the x-api-key header; without one, get a key at details.signup_url (AI agents: POST details.agent_signup_url). | `signup_url` `string`: Where a human gets an API key (self-serve signup).`agent_signup_url` `string`: POST an email address here to get a test key at once and a live key once the human confirms. | | `forbidden` | 403 | No | The API key is valid but may not make this request: its account is suspended, its scopes do not include the route, it is waiting for its owner's email confirmation, it is past its expiry, or the request came from an IP its allowlist does not include. A running job stops the same way when its account is suspended or the key that created it is revoked or expires. It is about the key, never the item (a private item is private_or_unavailable, also 403): branch on the code. | Do what details.reason says: if suspended, contact [support](https://staging-fly.socialtap.dev/docs/index.md); otherwise use a key whose scopes cover the route (details.required_scope), have the owner confirm the emailed link, create a new key, or call from an allowed IP. | `reason` `string`: Why: suspended (an operator suspended the account), scope (the key lacks details.required_scope), key_pending (an agent signup's live key before its owner confirms), key_expired, key_revoked (a job's creating key was revoked while it ran), ip_not_allowed, test_mode (a test key asked for something live, such as a live key) or csrf (a dashboard request from another site). New values may be added.`required_scope` `string`: For reason scope: the scope the route needs (endpoints, jobs, account:read or admin). | | `insufficient_credits` | 402 | No | The account does not have enough credits for this request. | Buy credits (a human pays: details.purchase_url, or details.checkout makes a checkout link), then repeat the request; do not retry automatically. | `credits_required` `integer`: Credits this request needed at the point it stopped (to start, or to finish a longer item).`credits_available` `integer`: Credits the account could spend at that moment (in-flight requests hold part of the balance).`max_credits` `integer`: For a job's item: the job's max_credits, when that cap (not the balance) stopped the item.`purchase_url` `string`: Where a human buys credits (the dashboard); absent when a job's max_credits, not the balance, stopped it.`checkout` `object`: The API call that makes a checkout link for a human to pay: {method, path (an absolute URL), packs (the pack IDs its body's pack takes)}. | | `spend_limit_reached` | 402 | No | The API key reached its spend limit for the current period; the account may still have credits. | Wait until details.resets_at or raise the key's spend limit (PATCH /v1/api_keys/{id}); do not retry automatically. | `api_key_id` `string`: The key whose limit was reached.`spend_limit` `integer`: The limit: most credits the key may spend per interval.`interval` `string`: day or month (UTC calendar).`used` `integer`: Credits the key spent this interval, plus what its requests in flight hold.`credits_required` `integer`: Credits this request needed.`resets_at` `string`: When the interval ends and the limit is free again, RFC 3339 UTC. | | `rate_limited` | 429 | Yes, later | The account exceeded its runaway-protection ceiling: plans do not limit request rate, but a high per-account safety ceiling stops broken client loops. Support raises it on request. Without an API key (signup, the MCP server's tool listing and find_endpoint), requests are limited per IP address instead. | Wait for the number of seconds in the retry-after header, then retry; ask [support](https://staging-fly.socialtap.dev/docs/index.md) to raise the ceiling if your sustained traffic needs more (calls without an API key: use one). | `retry_after` `integer`: Seconds to wait before retrying; equals the retry-after header. | | `method_not_allowed` | 405 | No | The endpoint exists but does not accept this HTTP method. | Use the method in the allow header (GET for every data endpoint). | — | | `endpoint_retired` | 410 | No | The endpoint passed its sunset date and no longer answers. | Call the replacement endpoint (details.replacement); the changelog describes the migration. | `sunset_at` `string`: When the endpoint stopped answering, RFC 3339 UTC.`replacement` `string`: ID of the endpoint to use instead; absent when there is none. | | `idempotency_key_reused` | 422 | No | The Idempotency-Key was already used with a different request (another route, or another body). | Send a new Idempotency-Key for a different request; reuse a key only to retry the identical request. | — | | `idempotency_key_in_progress` | 409 | Yes, later | A request with this Idempotency-Key is still running. | Retry after the number of seconds in the retry-after header to get that request's result. | `retry_after` `integer`: Seconds to wait before retrying; equals the retry-after header. | | `job_not_found` | 404 | No | No job with this ID exists for the account: unknown, or past its retention. | Check the job ID; finished jobs are kept for a limited time. | — | | `unknown_endpoint` | 404 | No | No endpoint or route has this path: a typo, a platform that does not have this endpoint yet, or a path that is not part of the API. Nothing was fetched or charged. | Call the path in details.suggestion when it is set (it is the one the path most likely means), or pick one of details.endpoints; do not retry the same path. | `suggestion` `string`: The endpoint path this one most likely means; absent when nothing is close.`endpoints` `string[]`: Every public data endpoint's path. | | `not_found` | 404 | No | The item does not exist or was deleted. | Check the URL or ID; do not retry. | — | | `private_or_unavailable` | 403 | No | The item exists but is private, age- or region-gated, or requires a login to view. It shares 403 with forbidden (which is about the key): branch on the code. | Only public items can be fetched; do not retry. | — | | `no_transcript` | 404 | No | The media exists but has no transcript: no caption track and no speech to transcribe. | Do not retry; try another language with lang if the platform offers one. | — | | `upstream_blocked` | 503 | Yes, later | The platform refused every attempt. | Retry later with backoff; failed requests are free. | — | | `upstream_error` | 502 | Yes, later | The platform failed or returned something unexpected. | Retry with backoff; failed requests are free. | — | | `overloaded` | 503 | Yes, later | SocialTap was momentarily at capacity and did not start the request: nothing was fetched or charged. | Retry after the number of seconds in the retry-after header; failed requests are free. | `retry_after` `integer`: Seconds to wait before retrying; equals the retry-after header. | | `internal` | 500 | Yes, later | Unexpected SocialTap error. | Retry with backoff; contact [support](https://staging-fly.socialtap.dev/docs/index.md) with the request_id if it persists. | — | ## Retrying - **Retry: yes.** Retry with exponential backoff and a cap on attempts. For [`rate_limited`](https://staging-fly.socialtap.dev/docs/errors/index.md#rate_limited), wait at least the number of seconds in the `retry-after` header (also in `error.details`) first, then retry. It is not a plan limit but the per-account safety ceiling against runaway clients (see [Rate limits](https://staging-fly.socialtap.dev/docs/authentication/index.md#rate-limits)). - **Retry: no.** The code describes your request or the item, and the same request gets the same answer on every attempt: fix the input, top up your balance, or treat the item as unavailable, as the table says. - `retry-after` is only ever sent with codes you may retry. Credit errors such as [`insufficient_credits`](https://staging-fly.socialtap.dev/docs/errors/index.md#insufficient_credits) never carry it: buy credits, then repeat the request. --- Source: https://staging-fly.socialtap.dev/docs/pagination/ # Pagination Using the API How list endpoints return items in pages, how to fetch the next page with a cursor, and how pages are priced. Some endpoints return a list of items, a page at a time. Every list endpoint pages the same way, so you write the loop once. ## The response A successful list response has three keys: `data`, an array of items in the resource's [shared schema](https://staging-fly.socialtap.dev/resources/index.md); `page`, which says whether more items exist and how to get them; and `meta`, as on every response: Shape of a list response: ```json {"data":["…"],"page":{"has_more":true,"next_cursor":"…","next_cursor_expires_at":"…","end_reason":"…"},"meta":{"request_id":"…","platform":"…","source":"…","is_cached":true,"credits_charged":0,"latency_ms":0,"fetched_at":"…","input":"…","credits_remaining":0,"client_request_id":"…","warnings":"…","mode":"…"}} ``` *(Omitted: another section of this file has it.)* | Field | Type | Description | | --- | --- | --- | | `page.has_more` | `boolean` | True when another page exists; then next_cursor is set. An empty page with has_more true is not the end. | | `page.next_cursor` | `string` or `null` | Pass as cursor, with the same other parameters, to get the next page; null at the end. | | `page.next_cursor_expires_at` | `string` (date-time) or `null` | When next_cursor stops working, RFC 3339 UTC; null at the end. Don't store cursors. | | `page.end_reason` | `string` or `null` | Why there are no more pages; null while has_more is true. end: the list ended, or reached since/until. platform_limit: the platform stops serving further pages although the list goes on. max_pages: SocialTap's cap on pages in one walk. New values may be added. A short or empty page is not the end: only has_more false is.
One of `end`, `platform_limit`, `max_pages` | There are no totals: platforms don't report them reliably, and counting would cost extra requests. ## Parameters Every list endpoint accepts these on top of its own parameters and the [common options](https://staging-fly.socialtap.dev/docs/credits/index.md#caching): | Parameter | Type | Description | | --- | --- | --- | | `limit` | `integer`
min 1 | Items per page, 1 to the endpoint's page size (its default and maximum; larger values are reduced to it). Omit it: every page costs the same, so a smaller limit means more pages to pay for. | | `cursor` | `string` | page.next_cursor from the previous response. Send the same other parameters; omit to start from the first page. | ## Getting the next page 1. Send the first request without `cursor`. 2. If `page.has_more` is `true`, send **the same request again** with `cursor` set to `page.next_cursor` from the previous response. 3. Stop when `page.has_more` is `false`. Keep every other parameter the same: only `limit` and the common options such as `max_age` may change between pages. > [!WARNING] > > **A short or empty page is not the end.** A page can hold fewer items than `limit`, or none, and still have `page.has_more` set to `true`: keep going. Only `page.has_more` `false` ends the list. Every list page with a next page also carries the next request ready to send, in the standard `link` header (`rel="next"`): your request with the cursor filled in. Generic HTTP clients can follow it. ## Why a list ended When `page.has_more` is `false`, `page.end_reason` says why, so fewer items than you expected is never a mystery: - `end`: the list really ended, or it reached the time window you asked for with `since`/`until`. - `platform_limit`: the platform stops serving further pages although the list goes on (many platforms cap how far logged-out visitors can scroll). Repeating the walk later will not get further. - `max_pages`: the walk reached SocialTap's cap on pages per list. ## Time windows List endpoints ordered by time accept `since` (inclusive) and `until` (exclusive), as an RFC 3339 time or a date (see [Requests](https://staging-fly.socialtap.dev/docs/requests/index.md#param-since)). On a newest-first list, paging stops as soon as an item older than `since` appears: the last page has `page.has_more` `false` with `page.end_reason` `end`, and the older pages are neither fetched nor charged. `until` filters out newer items; the pages that hold them are still read, so they are charged. JavaScript: ```javascript const items = []; let cursor = null; do { const params = new URLSearchParams({ ...yourParams, ...(cursor ? { cursor: cursor } : {}) }); const res = await fetch(`https://staging-fly.socialtap.dev/v1//?${params}`, { headers: { "x-api-key": process.env.SOCIALTAP_API_KEY }, }); if (!res.ok) throw new Error((await res.json()).error.code); const body = await res.json(); items.push(...body.data); cursor = body.page.next_cursor; } while (cursor); ``` ## Cursors Cursors are opaque and short-lived: don't parse, build or store them. Each one stops working at `page.next_cursor_expires_at`. A cursor that is malformed, expired, from another endpoint, or sent with different parameters fails with [`invalid_cursor`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_cursor) (HTTP 400) and costs nothing; repeat the original request without a cursor to start again. ## Order and page size Each list endpoint states the order of its items and its page size: the default and the largest `limit` it accepts (larger values are reduced). One request reads one page from the platform, so a list of N items takes about N ÷ page size requests when you leave `limit` out. | Endpoint | Order | Page size | Cursor lifetime | Price per page | | --- | --- | --- | --- | --- | | [Facebook Ad Library Search API](https://staging-fly.socialtap.dev/docs/reference/facebook/search_ads/index.md) | most impressions first | up to 10 | 1 hour | 1 credit | | [Instagram Posts API](https://staging-fly.socialtap.dev/docs/reference/instagram/posts/index.md) | newest first | up to 12 | 24 hours | 1 credit | | [Rumble Comments API](https://staging-fly.socialtap.dev/docs/reference/rumble/comments/index.md) | newest first | up to 500 | 24 hours | 1 credit | | [Rumble Channel Videos API](https://staging-fly.socialtap.dev/docs/reference/rumble/posts/index.md) | Rumble's upload order (newest uploads first, not strictly by published_at) | up to 25 | 1 hour | 1 credit | | [Rumble Search API](https://staging-fly.socialtap.dev/docs/reference/rumble/search_posts/index.md) | Rumble's relevance order | up to 36 | 1 hour | 1 credit | | [TikTok Comments API](https://staging-fly.socialtap.dev/docs/reference/tiktok/comments/index.md) | TikTok's comment ranking (the order its app shows) | up to 50 | 24 hours | 1 credit | | [TikTok Posts API](https://staging-fly.socialtap.dev/docs/reference/tiktok/posts/index.md) | newest first | up to 15 | 24 hours | 1 credit | | [YouTube Comments API](https://staging-fly.socialtap.dev/docs/reference/youtube/comments/index.md) | newest first | up to 20 | 1 hour | 1 credit | | [YouTube Channel Videos API](https://staging-fly.socialtap.dev/docs/reference/youtube/posts/index.md) | newest first | up to 48 | 1 hour | 1 credit | ## Pricing A list endpoint's price is **per page**: every successful page costs the same, however many items it holds and whatever `limit` you send (a smaller limit lowers neither the price of a page nor the walk's: it costs more pages, below). Empty pages are charged like any other successful page. Failed requests, including [`invalid_cursor`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_cursor), are free, and so are pages served from the cache. See [Credits and caching](https://staging-fly.socialtap.dev/docs/credits/index.md). ### A smaller limit costs more A `limit` below the page size does not split one charge into parts. The request returns the first `limit` items of a platform page, and the next request, with its cursor, serves the rest of that same platform page: it is fetched again and charged as a page each time. Walking a page in parts therefore costs one page per part, and because the platform page is read again for every part, items can shift between those requests if the platform adds or removes items in the meantime, so an item may repeat or be skipped. **Omitting `limit` is the cheapest way to walk a list:** every request returns a full page, and each platform page is read and charged once. Use a smaller `limit` only when you need fewer items than a page holds and will stop there. ## Errors On top of the errors every endpoint can return, list endpoints have these: | Code | HTTP | Retry | Meaning | What to do | | --- | --- | --- | --- | --- | | `invalid_cursor` | 400 | No | The cursor is malformed, expired, belongs to another endpoint, or the other parameters differ from the request that issued it. | Repeat the original request without cursor to start the list again. | A rejected cursor costs nothing; repeat the original request without `cursor` to start again. --- Source: https://staging-fly.socialtap.dev/docs/idempotency/ # Idempotency Using the API Retry any POST safely with an Idempotency-Key: the same key and body never repeat the work, and a replay shows the current state. A request whose answer never arrives may or may not have run. For reads that is harmless: retry it, and set `max_age` to the seconds since the first attempt so the retry is served free from the cache. For requests that create something, such as a job, send an `idempotency-key` header and retry with the same key: the work is done once, whatever the number of attempts. ## How it works - Every `POST` accepts the header. Use a new random value for each new request (a UUID is ideal), up to 255 printable ASCII characters, and reuse it only to retry that same request. - Keys belong to your account and to one route, and are kept 1 day. - The first request with a key runs normally. Once it has succeeded, a retry with the same key and the same body doesn't run again: it answers with the first request's status and the resource's **current** state (a job created then is shown as it is now), with the header `idempotent-replayed: true`. Routes that show a secret once (a new webhook endpoint, a rotated secret) answer exactly what they answered the first time. - The body is compared after parsing, so key order and whitespace don't matter. | Situation | Answer | | --- | --- | | The same key with another body, or on another route | [`idempotency_key_reused`](https://staging-fly.socialtap.dev/docs/errors/index.md#idempotency_key_reused) (422): send a new key | | The same key while its first request is still running | [`idempotency_key_in_progress`](https://staging-fly.socialtap.dev/docs/errors/index.md#idempotency_key_in_progress) (409): retry after the `retry-after` seconds (after 2 minutes a retry resumes it) | | A key whose first request was refused or failed | Nothing is kept: retry with the same key once the request is fixed | | A key whose first request could not confirm what it did ([`internal`](https://staging-fly.socialtap.dev/docs/errors/index.md#internal) says so) | Retry it: you get what it created, never a second one | Send a new key with every `POST` and reuse it, with the same body, whenever you retry that request. --- Source: https://staging-fly.socialtap.dev/docs/agents/ # Build with AI agents Agents and accounts Everything a coding agent needs to integrate the API correctly on the first try: a prompt to paste, the rules to follow, an Agent Skill and Markdown docs. SocialTap is built to be integrated by AI coding agents such as Claude Code, Cursor or Codex. Every docs page is also Markdown, the whole documentation is one file, and the rules below are generated from the same catalog as the API, so they are always current. ## Paste this into your agent Give your agent this prompt to integrate SocialTap into your project. It sends the agent to the docs first and ends with a real request, so you see it working: Prompt: ``` Integrate the SocialTap API (https://staging-fly.socialtap.dev) into this project. 1. Read https://staging-fly.socialtap.dev/llms.txt first, then the Markdown docs page of the endpoint you need (linked there). Use only endpoints and parameters documented there. 2. Send the API key in the x-api-key header, read from the SOCIALTAP_API_KEY environment variable. Never hard-code it; if it is not set, ask me for it. 3. Branch on error.code: retry only the retryable codes the docs list, with backoff. On list endpoints follow page.next_cursor while page.has_more is true. 4. Verify with one real request before building on it, for example: curl -G 'https://staging-fly.socialtap.dev/v1/tiktok/profile' \ --data-urlencode 'handle=https://www.tiktok.com/@nasa' \ -H "x-api-key: $SOCIALTAP_API_KEY" and check that data is not empty. 5. Tell me which endpoint you used, what meta.credits_charged was, and any error code you saw. ``` ## Instructions for AI agents The same rules open [llms.txt](https://staging-fly.socialtap.dev/llms.txt) and the Agent Skill: *(Omitted: another section of this file has it.)* ## Which endpoint | Data | Request | Price | Docs | | --- | --- | --- | --- | | Facebook · Post (beta) | `GET /v1/facebook/post` | 1 credit (page) per request | [docs](https://staging-fly.socialtap.dev/docs/reference/facebook/post/index.md) | | Facebook · Ads (beta) | `GET /v1/facebook/search_ads` | 1 credit (api) per page | [docs](https://staging-fly.socialtap.dev/docs/reference/facebook/search_ads/index.md) | | Facebook · Transcript (beta) | `GET /v1/facebook/transcript` | 1 credit (captions); 2 credits + 2 per started minute of media (asr) per request | [docs](https://staging-fly.socialtap.dev/docs/reference/facebook/transcript/index.md) | | Instagram · Post (beta) | `GET /v1/instagram/post` | 1 credit (page) per request | [docs](https://staging-fly.socialtap.dev/docs/reference/instagram/post/index.md) | | Instagram · Posts (beta) | `GET /v1/instagram/posts` | 1 credit per page | [docs](https://staging-fly.socialtap.dev/docs/reference/instagram/posts/index.md) | | Instagram · Profile (beta) | `GET /v1/instagram/profile` | 1 credit (page) per request | [docs](https://staging-fly.socialtap.dev/docs/reference/instagram/profile/index.md) | | Instagram · Transcript (beta) | `GET /v1/instagram/transcript` | 1 credit (captions); 2 credits + 2 per started minute of media (asr) per request | [docs](https://staging-fly.socialtap.dev/docs/reference/instagram/transcript/index.md) | | Rumble · Comments (beta) | `GET /v1/rumble/comments` | 1 credit (api) per page | [docs](https://staging-fly.socialtap.dev/docs/reference/rumble/comments/index.md) | | Rumble · Post (beta) | `GET /v1/rumble/post` | 1 credit (page) per request | [docs](https://staging-fly.socialtap.dev/docs/reference/rumble/post/index.md) | | Rumble · A Rumble channel's or user's videos, shorts and live streams (beta) | `GET /v1/rumble/posts` | 1 credit per page | [docs](https://staging-fly.socialtap.dev/docs/reference/rumble/posts/index.md) | | Rumble · Profile (beta) | `GET /v1/rumble/profile` | 1 credit (page) per request | [docs](https://staging-fly.socialtap.dev/docs/reference/rumble/profile/index.md) | | Rumble · Search Rumble videos, live streams and shorts by keyword (beta) | `GET /v1/rumble/search_posts` | 1 credit (page) per page | [docs](https://staging-fly.socialtap.dev/docs/reference/rumble/search_posts/index.md) | | Rumble · Transcript (beta) | `GET /v1/rumble/transcript` | 1 credit (captions); 2 credits + 2 per started minute of media (asr) per request | [docs](https://staging-fly.socialtap.dev/docs/reference/rumble/transcript/index.md) | | Threads · Profile (beta) | `GET /v1/threads/profile` | 1 credit (api); 1 credit (page) per request | [docs](https://staging-fly.socialtap.dev/docs/reference/threads/profile/index.md) | | TikTok · Comments (beta) | `GET /v1/tiktok/comments` | 1 credit (api) per page | [docs](https://staging-fly.socialtap.dev/docs/reference/tiktok/comments/index.md) | | TikTok · Post (beta) | `GET /v1/tiktok/post` | 1 credit (page) per request | [docs](https://staging-fly.socialtap.dev/docs/reference/tiktok/post/index.md) | | TikTok · Posts (beta) | `GET /v1/tiktok/posts` | 1 credit per page | [docs](https://staging-fly.socialtap.dev/docs/reference/tiktok/posts/index.md) | | TikTok · Profile (beta) | `GET /v1/tiktok/profile` | 1 credit (page) per request | [docs](https://staging-fly.socialtap.dev/docs/reference/tiktok/profile/index.md) | | TikTok · Transcript (beta) | `GET /v1/tiktok/transcript` | 1 credit (captions); 2 credits + 2 per started minute of media (asr) per request | [docs](https://staging-fly.socialtap.dev/docs/reference/tiktok/transcript/index.md) | | YouTube · Comments (beta) | `GET /v1/youtube/comments` | 1 credit (api) per page | [docs](https://staging-fly.socialtap.dev/docs/reference/youtube/comments/index.md) | | YouTube · Post (beta) | `GET /v1/youtube/post` | 1 credit (api); 1 credit (page) per request | [docs](https://staging-fly.socialtap.dev/docs/reference/youtube/post/index.md) | | YouTube · Posts (beta) | `GET /v1/youtube/posts` | 1 credit per page | [docs](https://staging-fly.socialtap.dev/docs/reference/youtube/posts/index.md) | | YouTube · Profile (beta) | `GET /v1/youtube/profile` | 1 credit per request | [docs](https://staging-fly.socialtap.dev/docs/reference/youtube/profile/index.md) | | YouTube · Transcript (beta) | `GET /v1/youtube/transcript` | 1 credit (captions); 2 credits + 2 per started minute of media (asr) per request | [docs](https://staging-fly.socialtap.dev/docs/reference/youtube/transcript/index.md) | ## Docs for agents - [llms.txt](https://staging-fly.socialtap.dev/llms.txt): the instructions above and a link to every page, as Markdown. - [llms-full.txt](https://staging-fly.socialtap.dev/llms-full.txt): every guide and endpoint reference in one Markdown file. - Every page has a Markdown twin: add `index.md` to its URL, or request the page with `Accept: text/markdown`. Each page also has "Copy page as Markdown" and "Open in Claude / ChatGPT" buttons. - The [API reference](https://staging-fly.socialtap.dev/docs/reference/index.md): an overview of every operation, and a page per operation, readable without JavaScript. - The MCP server's `search_docs` and `read_doc` tools: search these docs and read any page, free and without a key. - [openapi.json](https://staging-fly.socialtap.dev/openapi.json): OpenAPI 3.1 with examples, the exact errors of each operation and `x-socialtap-*` extensions for pricing, pagination and retries. ## Agent Skill *(Omitted: another section of this file has it.)* ## MCP server The remote MCP server at `https://staging-fly.socialtap.dev/mcp` (Streamable HTTP) takes your API key as `authorization: Bearer ` or in the `x-api-key` header, and each tool call costs what the same API request costs. Set `SOCIALTAP_API_KEY` in your environment, then add the server: Claude Code: ```shell claude mcp add --transport http socialtap https://staging-fly.socialtap.dev/mcp --header "authorization: Bearer $SOCIALTAP_API_KEY" ``` Its tools cover every endpoint, grouped by resource: pass `platform` and the link, handle or query. Results are concise JSON (`detail: "full"` returns every field), state `credits_charged` and `credits_remaining`, and are cut at about 20,000 tokens with a `next_cursor` to continue. Listing the tools and `find_endpoint` work without a key. | Tool | What it does | | --- | --- | | `get_post` | Get a public post on Facebook, Instagram, Rumble, TikTok or YouTube. | | `get_profile` | Get a public profile on Instagram, Rumble, Threads, TikTok or YouTube. | | `get_transcript` | Get the transcript of a public video or post on Facebook, Instagram, Rumble, TikTok or YouTube. | | `list_comments` | List the comments of a public post on Rumble, TikTok or YouTube, one page per call. | | `list_posts` | List the posts of a public profile on Instagram, Rumble, TikTok or YouTube, one page per call. | | `search` | Search public ads or posts on Facebook or Rumble by query. | | `find_endpoint` | Find which SocialTap tool and endpoint serves a need ("TikTok comments", "YouTube transcript") or a link: returns the best matches with the tool and arguments to call, the price, the parameters and an example result. | | `search_docs` | Search the SocialTap docs (guides, the API reference, error codes, schemas, pricing) for a question or term: returns the best-matching sections with their page, a snippet and the path to read with read_doc. | | `read_doc` | Read one SocialTap docs page as Markdown: a path from search_docs or llms.txt (/docs/pagination/), or a full docs URL. | | `get_account` | Get the account, balance and calling key. | | `create_job` | Run many requests (or one long one) as a job. | | `get_job` | Get a job (free to poll). | | `list_job_results` | List a job's results as they complete. | --- Source: https://staging-fly.socialtap.dev/docs/account/ # Account, API keys and signup Agents and accounts Sign up, manage API keys (scopes, spend limits, expiry, IP allowlists, live and test modes), read usage and the credit ledger, and get balance alerts. Everything about your account is an API route, free, and also available in the [dashboard](https://staging-fly.socialtap.dev/dashboard/). ## Sign up **People:** create your account on the [signup page](https://staging-fly.socialtap.dev/signup/) with Google, GitHub, or an email address and a password (we email you a link to verify the address first). Your first sign-in gives you **100 free credits** (once per account, no card) and two keys, a live key and a test key, shown once. **AI agents:** an agent without a key signs up with its human's email address: Shell: ```shell curl -X POST https://staging-fly.socialtap.dev/v1/agent_signup \ -H 'content-type: application/json' \ -d '{"email": "you@example.com", "name": "my-agent"}' ``` The answer has two keys. The **test key** works at once: every endpoint answers from recorded responses, free (see [test mode](https://staging-fly.socialtap.dev/docs/test-mode/index.md)). The **live key** is *pending*: it spends nothing and can only call `GET /v1/account` until the human opens the confirmation email and confirms, which activates it and grants the free credits. Agents never get spending power without a human. Poll `GET /v1/account` with the live key: `api_key.status` turns from `pending` to `active`. An address that already has an account gets no keys and is not refused: the answer is HTTP `202`: The address already has an account: no keys were made; its owner was emailed a sign-in link to create a key for you in the dashboard. Ask them to check their email. Signup is throttled per IP address and per email address, and disposable email domains are refused. ## Routes | Route | What it does | Auth | | --- | --- | --- | | `GET /v1/account`
Get the account, balance and calling key | The account's balance and plan, the low-balance alert threshold, and the calling key: its mode, scopes, spend limit (with what it used this interval) and expiry. Free; never cached. A pending agent key may call it (and nothing else) to learn when its owner confirmed. | key with account:read, or the session | | `PATCH /v1/account`
Update account settings | Sets low_balance_threshold: credits.low (an event and an email to the owner) fires once each time a request takes the balance from above it to at or below it; credits.depleted fires when the balance reaches 0. Free. | key with admin, or the session | | `POST /v1/agent_signup`
Sign up as an agent: a test key now, live on confirm | For AI agents: send your human's email address and get a test key that works at once (recorded responses, free) and a live key that stays pending until the human confirms by email, which grants the free credits. Agents never get spending power without a human. For an address that already has an account, no keys are made: the owner is emailed a sign-in link to create one in the dashboard, and the answer is 202 with the human signup's object (check your email). | none | | `POST /v1/api_keys`
Create an API key | Creates a key with the given scopes, spend limit, expiry and IP allowlist. The secret is in this answer only: store it now. A key never gets a scope its creator lacks. Free. | key with admin, or the session | | `GET /v1/api_keys/{id}`
Get an API key | One key and its spend this interval; never its secret. Free. | key with account:read, or the session | | `GET /v1/api_keys`
List API keys | The account's keys, newest first, revoked ones included; never their secrets. Free. | key with account:read, or the session | | `DELETE /v1/api_keys/{id}`
Revoke an API key | Revokes a key for good (a key may revoke itself). It stops working within a minute everywhere; revoking it again changes nothing. Free. | key with admin, or the session | | `PATCH /v1/api_keys/{id}`
Update an API key | Changes a key's name, scopes, spend limit, expiry or IP allowlist; absent fields keep their value. Takes effect within a minute everywhere. Free. | key with admin, or the session | | `GET /v1/credit_transactions`
List credit transactions (the ledger) | Every change to the balance, newest first, cursor-paged: grants (free credits), purchases, hourly usage roll-ups and adjustments. Per-request detail is in GET /v1/usage. Free. | key with account:read, or the session | | `POST /v1/docs_feedback`
Send feedback on a docs page | Records whether a docs page helped, with an optional comment: what every docs page's "Was this page helpful?" sends. Agents are welcome to report a page that was wrong, unclear or missing something (set \`agent\`). Free; limited per IP address. | none | | `GET /v1/security_events`
List sign-in and security events | The signed-in person's sign-ins, failed sign-ins, password, email, two-factor and session changes, newest first, with the network and browser each came from. Kept \`security_events_retention_s\`. Free. | dashboard session | | `POST /v1/session`
Start the dashboard: set up the account | The dashboard calls it once it is signed in (people sign in with Google, GitHub or an email address and password on the sign-in page). The first call of a new account returns its live and test keys (secrets shown only here); with agent_token, it approves that agent signup, activating the agent's pending live key and granting the free credits. An account an agent opened that its owner signs in to without approving the agent gets fresh keys, and the agent's are revoked. Free. | dashboard session | | `DELETE /v1/session`
Sign out | Ends the session and clears its cookie. | dashboard session | | `GET /v1/session`
Get the dashboard session | The signed-in person and account of the session cookie. | dashboard session | | `GET /v1/usage`
Usage report: requests and credits over time | Requests, successes, failures, cache hits and credits charged per time bucket, optionally grouped by endpoint, key and outcome. Buckets are listed oldest first, cursor-paged. Data is complete to within about 5 minutes; credits reconcile with the ledger. Free. | key with account:read, or the session | ## API keys Send the key in the `x-api-key` header. A key has: - a **mode**: live keys start with `st_live_` and make real, billed requests; test keys start with `st_test_` and answer from recorded responses, free. The mode is stored with the key; the prefix is only a hint for people. - **scopes**, what it may call. A new key gets every scope but `admin` unless you choose, and a key never gets a scope its creator lacks. Without the scope a route needs, the answer is [`forbidden`](https://staging-fly.socialtap.dev/docs/errors/index.md#forbidden) with `details.required_scope`. - an optional **spend limit**: at most that many credits per UTC day or month. Past it, requests fail with [`spend_limit_reached`](https://staging-fly.socialtap.dev/docs/errors/index.md#spend_limit_reached) (HTTP 402, never retried automatically) until `details.resets_at`, even when the account still has credits. Concurrent requests can never take a key past its limit. - an optional **expiry** and **IP allowlist** (addresses or CIDR ranges, IPv4 or IPv6). An expired key, or a call from another address, is refused with [`forbidden`](https://staging-fly.socialtap.dev/docs/errors/index.md#forbidden) (`details.reason` says which). The secret is shown once, when the key is created; the API only ever shows its prefix afterwards. Changes and revocations reach every server within a minute. | Scope | Routes that need it | | --- | --- | | `endpoints` | the routes of its own feature (see above) | | `jobs` | `POST /v1/jobs/{id}/cancel`, `POST /v1/jobs`, `GET /v1/jobs/{id}`, `GET /v1/jobs`, `GET /v1/jobs/{id}/results` | | `account:read` | `GET /v1/account`, `GET /v1/api_keys/{id}`, `GET /v1/api_keys`, `GET /v1/credit_transactions`, `GET /v1/events/{id}`, `GET /v1/events`, `GET /v1/security_events`, `POST /v1/session`, `DELETE /v1/session`, `GET /v1/session`, `GET /v1/usage`, `GET /v1/webhook_endpoints/{id}/deliveries`, `GET /v1/webhook_endpoints/{id}`, `GET /v1/webhook_endpoints` | | `admin` | `PATCH /v1/account`, `POST /v1/api_keys`, `DELETE /v1/api_keys/{id}`, `PATCH /v1/api_keys/{id}`, `POST /v1/events/{id}/redeliver`, `POST /v1/webhook_endpoints`, `DELETE /v1/webhook_endpoints/{id}`, `POST /v1/webhook_endpoints/{id}/rotate_secret`, `POST /v1/webhook_endpoints/{id}/test`, `PATCH /v1/webhook_endpoints/{id}` | ## Usage and the ledger `GET /v1/usage` reports requests, successes, failures, cache hits and credits per hour or day, grouped by endpoint, key or outcome. `GET /v1/credit_transactions` lists every change to the balance: free credits, purchases, hourly usage and adjustments, with the balance after each. ## Balance alerts Set `low_balance_threshold` with `PATCH /v1/account`. When a request takes the balance from above it to at or below it, the account gets a `credits.low` event and an email to its owner, once per crossing. `credits.depleted` fires when the balance reaches 0, and `api_key.spend_limit_reached` the first time in an interval that a key is refused for its spend limit. ## The dashboard session Signing in starts a dashboard session: a signed cookie with the `__Host-` prefix on this origin (`Secure`, `HttpOnly`, `SameSite=Lax`), checked against the server on every request, so signing out, a password reset or "sign out everywhere else" ends it at once. The session can read and manage the account and keys, never call data endpoints. Every change it makes must come from this origin (the API checks the request's `Origin`), so another site can neither read nor drive it. Creating or changing API keys needs a recent sign-in: after a while the dashboard asks for your password or a two-factor code again. Turn on two-factor authentication (an authenticator app, with one-time backup codes) in the security settings: every sign-in then asks for a code, with a password, Google or GitHub alike. The same page lists your sessions and a log of your sign-ins and security changes. --- Source: https://staging-fly.socialtap.dev/docs/jobs/ # Jobs and batches Jobs and events Run many requests, or one long list walk, in the background: same prices and caches as single requests, with results you can stream, download or receive by webhook. A job runs up to 1,000 endpoint requests for you in the background. Use one when a task needs many items ("these 300 videos"), a whole list ("every comment"), or a request that can take a while, such as a long transcript. Each item is exactly the request its endpoint answers synchronously: same parameters, same price, same caches, and its result is the same envelope, so the code that reads a single response reads a job's results too. Failed items cost nothing. | Route | What it does | Key scope | | --- | --- | --- | | `POST /v1/jobs` | Run many requests (or one long one) as a job | `jobs` | | `GET /v1/jobs` | List jobs, newest first | `jobs` | | `GET /v1/jobs/{id}` | Get a job (free to poll) | `jobs` | | `GET /v1/jobs/{id}/results` | List a job's results as they complete | `jobs` | | `GET /v1/jobs/{id}/results.jsonl` | Download every result of a job (JSON Lines) | none (signed URL) | | `POST /v1/jobs/{id}/cancel` | Cancel a job | `jobs` | ## Create a job Send the requests with `POST /v1/jobs`. Each one names an endpoint by its ID (shown as "Endpoint ID" on each endpoint in the [API reference](https://staging-fly.socialtap.dev/docs/reference/index.md), `x-socialtap-endpoint` in OpenAPI; not the operationId or the path) and its parameters, as you would send them to the endpoint, plus your own `custom_id`: Shell: ```shell curl -X POST 'https://staging-fly.socialtap.dev/v1/jobs?wait=60' \ -H "x-api-key: $SOCIALTAP_API_KEY" \ -H "idempotency-key: $(uuidgen)" \ -H 'content-type: application/json' \ --data '{ "requests": [ { "custom_id": "row-1", "endpoint": "tiktok.profile", "params": { "handle": "https://www.tiktok.com/@nasa" } } ] }' ``` | Field | Type | Description | | --- | --- | --- | | `requests` | `object[]` | The requests to run, 1 to 1000; custom_id must be unique within the job. | | `requests[].custom_id` | `string` | Your ID for the item (letters, digits, _ and -), echoed on its results. | | `requests[].endpoint` | `string` | The endpoint ID: Endpoint ID on its reference page, x-socialtap-endpoint in OpenAPI (youtube.transcript; not its operationId or path). | | `requests[].params` | `object` | The endpoint's query params, as you would send them to it (strings, numbers or booleans). | | `requests[].max_pages?` | `integer` or `null` | List endpoints only: walk up to this many pages with the cursor (default 1). Each page is a result and costs a page's price. | | `max_age?` | `integer` or `null` | Accept cached results up to this many seconds old for every item (free cache hits), like the endpoints' max_age. | | `max_credits?` | `integer` or `null` | Most the job may spend: a cap, never held up front (each page reserves as a synchronous request does). Default: the sum of every item's maximum price (times its max_pages). Items that would go over it are canceled. | | `webhook_url?` | `string` or `null` | Also deliver this job's job.\* events to this URL: the URL of one of the account's webhook endpoints (its secret signs them). | | `metadata?` | `object` | Up to 16 string pairs of your own, returned with the job and in its events. | Every item is checked before anything runs, as its synchronous request would be: an unknown endpoint (with a did-you-mean) or parameter, a missing or out-of-range parameter, a malformed date, a link of the wrong kind or platform, a duplicate `custom_id` or a list option on a single-item endpoint fails the whole request with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input), and its `param` names the field, such as `requests[3].params.url`. Nothing is charged. **Synchronous batches.** With `wait` (up to 1 minute), the request waits for the job to end. If it ends in time, the answer is `200` with the job and the first page of its results in `results` (continue from `results_page.next_cursor`). Otherwise it is `202` with the job as it is, and its URL in the `location` header. **Any endpoint, in the background.** A single endpoint request with the header `Prefer: respond-async` becomes a one-request job: the answer is `202` with the job instead of the result. Its one item's `custom_id` is `request`. It takes an `idempotency-key` like a create: repeating the request with the same key answers the same job. ## Walk whole lists On a list endpoint, `max_pages` (up to 1,000 per item) walks the list for you with its cursor: every page is one result with its `page_index`, priced as one page. The walk stops at the end of the list, at `max_pages`, or when the job's credits run out. A job holds at most 10,000 pages in total. ## The job Poll `GET /v1/jobs/{id}`: it is free, and `wait` long-polls until the job ends. Or subscribe a [webhook](https://staging-fly.socialtap.dev/docs/webhooks/index.md) to `job.completed`. | Field | Type | Description | | --- | --- | --- | | `object` | `string` | Always job.
One of `job` | | `id` | `string` | The job ID (job_…). | | `status` | `string` | queued: nothing started yet. running. canceling: canceled, running items finishing. completed: every item ended (check request_counts: there is no job-level failure). canceled. expired: the deadline passed; unfinished items expired.
One of `queued`, `running`, `canceling`, `completed`, `canceled`, `expired` | | `request_counts` | `object` | | | `request_counts.total` | `integer` | | | `request_counts.queued` | `integer` | Not started. | | `request_counts.running` | `integer` | Started (fetching, waiting for a retry or walking pages). | | `request_counts.succeeded` | `integer` | | | `request_counts.failed` | `integer` | | | `request_counts.canceled` | `integer` | | | `request_counts.expired` | `integer` | | | `credits` | `object` | | | `credits.max` | `integer` | max_credits: the most the job may spend. | | `credits.reserved` | `integer` | Credits its running pages may still use (0 once the job ends). | | `credits.charged` | `integer` | Credits charged so far, each page once (items' prices; failed items are free). | | `max_age` | `integer` or `null` | The job's max_age; null when it accepts no cached results. | | `metadata` | `object` | Your metadata; {} when none. | | `webhook_url` | `string` or `null` | The job's own webhook URL; null when none. | | `created_at` | `string` (date-time) | | | `started_at` | `string` (date-time) or `null` | When the first item started; null before. | | `finished_at` | `string` (date-time) or `null` | When the job ended; null while it runs. | | `cancel_requested_at` | `string` (date-time) or `null` | | | `expires_at` | `string` (date-time) | The deadline: items not ended by then expire. | | `results_url` | `string` or `null` | Every result as JSON Lines (one JobResult per line), signed: works without the API key until results_url_expires_at. Null until the job ends and after results_expire_at. | | `results_url_expires_at` | `string` (date-time) or `null` | | | `results_expire_at` | `string` (date-time) or `null` | When results are deleted; null while the job runs. | A job is `queued` until its first item starts, `running` while items run, and `completed` once every item has ended: there is no failed job, items fail one by one, so check `request_counts`. A canceled job is `canceling` until its running items finish, then `canceled`. A job that reaches `expires_at` (its deadline, 1 day after creation) ends `expired`: items that had not started expire, running ones finish (a list walk stops after its page). ## Results `GET /v1/jobs/{id}/results` lists results in the order they completed, cursor-paged, while the job runs and after. Keep the last `page.next_cursor` to read only what arrived since. Each result is one page of one item: | Field | Type | Description | | --- | --- | --- | | `object` | `string` | Always job_result.
One of `job_result` | | `custom_id` | `string` | | | `endpoint` | `string` | | | `page_index` | `integer` | 0 for a single-item endpoint and a list's first page; n for the list's page n+1. | | `status` | `string` | The page's outcome; an item's status is its last result's. canceled and expired results carry no result (error says why when it is not the job's cancel or deadline).
One of `succeeded`, `failed`, `canceled`, `expired` | | `status_code` | `integer` or `null` | The HTTP status the synchronous request answered; null when it did not run. | | `result` | `object` or `null` | Exactly the synchronous success envelope; null unless succeeded. | | `result.data` | `any` | The endpoint's data: its resource, or a list page's items. | | `result.page?` | `object` or `null` | List endpoints only. | | `result.meta` | `object` | | | `error` | `object` or `null` | Exactly the synchronous error object; null on success and for the job's own cancel or deadline. | | `completed_at` | `string` (date-time) | | - `result` is exactly the synchronous response's envelope (`data`, `meta`, and `page` on list endpoints). - `error` is exactly the synchronous error object: branch on `error.code` as you would for a single request. - An item that ended without a fetched page (canceled, expired) has one result with that status and no `result`. - `?status=failed` lists the failures, to resubmit them in a new job. Once the job ends, `results_url` is a signed link to every result as JSON Lines (one result per line), valid 1 hour and usable without an API key: hand it to a download tool or another service. Get the job again for a fresh link. A `job.*` event's `results_url` is the same signed link. Results are kept 7 days after the job ends and job records 30 days; after that the job answers [`job_not_found`](https://staging-fly.socialtap.dev/docs/errors/index.md#job_not_found). ## Credits 1. **Reserve, as a single request does.** Each page reserves its endpoint's minimum when it runs and grows to its price as it goes, so a job starts with what one page needs. `max_credits` is a cap, not a deposit: by default the most the items can cost (each item's maximum price, times its `max_pages`); lower it to spend less. If the balance can't pay a first page, creating the job fails with [`insufficient_credits`](https://staging-fly.socialtap.dev/docs/errors/index.md#insufficient_credits) and nothing runs ([`spend_limit_reached`](https://staging-fly.socialtap.dev/docs/errors/index.md#spend_limit_reached) when the key's spend limit is what can't). 2. **Charge.** Each page is charged its own price once as it ends, exactly like a single request (even if it had to be rerun): cache hits and failures are free. A page that the remaining budget can't pay for does not run: its item is `canceled` with an [`insufficient_credits`](https://staging-fly.socialtap.dev/docs/errors/index.md#insufficient_credits) error whose `details.max_credits` says the job's cap stopped it, or [`spend_limit_reached`](https://staging-fly.socialtap.dev/docs/errors/index.md#spend_limit_reached) when the key's spend limit did. Pages count against the key that created the job, exactly as its own requests do. 3. **Release.** What a page reserved and did not use goes back to your balance when it ends. `credits.reserved` is what running pages may still use, `credits.charged` what the job cost. ## Keys and test mode Creating a job needs a key with the `jobs` scope, and `endpoints` too: a job's items are endpoint requests. A test key's jobs run every item in [test mode](https://staging-fly.socialtap.dev/docs/test-mode/index.md): each result is exactly the recorded answer the endpoint gives that test key, free, and nothing is set aside or charged. Test jobs are separate from live ones: a test key lists and reads its own, a live key never sees them. ## Cancel `POST /v1/jobs/{id}/cancel` stops a job: items that haven't started are `canceled` (free), items already running finish and are charged. Canceling a job that has ended changes nothing. ## Limits and fairness A job runs up to 10 items at a time, and all of an account's jobs together up to 25, so a large job never crowds out your own synchronous requests. When SocialTap is busy, a job's items wait for capacity instead of failing; an item whose platform fails is retried up to 3 times before it fails. The unfinished jobs of an account hold at most 50,000 pages; above that, creating a job answers [`rate_limited`](https://staging-fly.socialtap.dev/docs/errors/index.md#rate_limited): wait for running jobs to end. ## Retry safely Send an `idempotency-key` with every create: if the connection drops, repeat the request with the same key and body and you get the job the first request created, never a second one, even after an [`internal`](https://staging-fly.socialtap.dev/docs/errors/index.md#internal). See [idempotency](https://staging-fly.socialtap.dev/docs/idempotency/index.md). --- Source: https://staging-fly.socialtap.dev/docs/webhooks/ # Webhooks Jobs and events Receive signed notifications when jobs end or credits run low: Standard Webhooks signatures, retries for days, delivery logs and secret rotation. Instead of polling, register an HTTPS endpoint and SocialTap sends it an [event](https://staging-fly.socialtap.dev/docs/events/index.md) when something happens: a job ends, your credits run low. Deliveries follow [Standard Webhooks](https://www.standardwebhooks.com/) exactly, so its verification libraries work too. | Route | What it does | Key scope | | --- | --- | --- | | `POST /v1/webhook_endpoints` | Register a webhook endpoint | `admin` | | `GET /v1/webhook_endpoints` | List webhook endpoints | `account:read` | | `GET /v1/webhook_endpoints/{id}` | Get a webhook endpoint | `account:read` | | `PATCH /v1/webhook_endpoints/{id}` | Update a webhook endpoint | `admin` | | `DELETE /v1/webhook_endpoints/{id}` | Delete a webhook endpoint | `admin` | | `POST /v1/webhook_endpoints/{id}/rotate_secret` | Rotate a webhook endpoint's signing secret | `admin` | | `POST /v1/webhook_endpoints/{id}/test` | Send a webhook.test event to an endpoint | `admin` | | `GET /v1/webhook_endpoints/{id}/deliveries` | List an endpoint's delivery attempts | `account:read` | ## Register an endpoint `POST /v1/webhook_endpoints` takes the `url` and the `events` it receives: event types, a family such as `job.*`, or `*` for all. The answer is the only time you see its signing `secret` (`whsec_…`): store it with your other secrets. An account has at most 16 endpoints. | Field | Type | Description | | --- | --- | --- | | `object` | `string` | Always webhook_endpoint.
One of `webhook_endpoint` | | `id` | `string` | we_… | | `url` | `string` | https URL deliveries are POSTed to. | | `events` | `string[]` | Event types it receives: types, prefix wildcards (job.\*) or \* for all. | | `description` | `string` or `null` | | | `status` | `string` | One of `enabled`, `disabled` | | `disabled_reason` | `string` or `null` | Why it is disabled: gone (it answered 410), failing (deliveries failed for 5 days), user. Null while enabled.
One of `gone`, `failing`, `user` | | `secret?` | `string` or `null` | The signing secret (whsec_…): only in the create and rotate_secret answers. Store it. | | `previous_secret_expires_at` | `string` (date-time) or `null` | Until then the previous secret signs too; null when none. | | `created_at` | `string` (date-time) | | | `updated_at` | `string` (date-time) | | The URL must use `https`, on port 443 or a port above 1023, without credentials, and its host must resolve only to public addresses: private, loopback, link-local and cloud metadata addresses are refused when you register it and checked again before every delivery. Redirects are not followed: a `3xx` answer is a failed delivery, so register the final URL. A job can also notify one of these endpoints that its subscriptions don't cover: set the job's `webhook_url` to the endpoint's URL. ## What a delivery looks like Each delivery is a `POST` of the event as JSON, with these headers: | Header | Type | Description | | --- | --- | --- | | `webhook-id` | `string` | The event ID (evt_…), the same on every retry: deduplicate deliveries on it. | | `webhook-timestamp` | `int` | When this attempt was signed, in Unix seconds: reject deliveries outside your tolerance (replays). | | `webhook-signature` | `string` | Space-separated v1,SIGNATURE entries, each the base64 HMAC-SHA256 of webhook-id, webhook-timestamp and the raw body joined by dots, with one active secret (two during a rotation): accept the delivery when one matches. | The body is small, a summary and links, never a job's results: fetch those from the links when you need them. ## Verify every delivery Compute the HMAC-SHA256 of `webhook-id.webhook-timestamp.` with the secret's bytes (the base64 after `whsec_`), and accept the delivery only when one of the `v1,` entries of `webhook-signature` matches, compared in constant time, and the timestamp is within 5 minutes of your clock. Use the raw body as it arrived: parsing and re-serializing JSON changes the bytes. TypeScript: ```typescript import { createHmac, timingSafeEqual } from "node:crypto"; /** The verified event, or null: pass the RAW body, before any JSON parsing. */ export function verify(rawBody: string, headers: Record, secret = process.env.SOCIALTAP_WEBHOOK_SECRET!): unknown { const id = headers["webhook-id"], ts = headers["webhook-timestamp"], sigs = headers["webhook-signature"]; if (!id || !ts || !sigs || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return null; const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64"); const want = createHmac("sha256", key).update(`${id}.${ts}.${rawBody}`).digest(); const ok = sigs.split(" ").some((s) => { const [version, b64] = s.split(",", 2); const got = Buffer.from(b64 ?? "", "base64"); return version === "v1" && got.length === want.length && timingSafeEqual(got, want); }); return ok ? JSON.parse(rawBody) : null; // deduplicate on its id } ``` Any Standard Webhooks library verifies these deliveries too. > [!IMPORTANT] > > Answer with any `2xx` quickly (within 15 seconds) and do the work afterwards. Deliveries can repeat and arrive out of order: deduplicate on the event ID (`webhook-id`), the same on every retry. ## Retries A delivery that doesn't get a `2xx` within 15 seconds is retried on this schedule, each delay varied by a little so retries don't arrive in lockstep: | Attempt | After the previous one | Since the event | | --- | --- | --- | | 1 | immediately | 0 | | 2 | 5 seconds | about 5 seconds | | 3 | 5 minutes | about 305 seconds | | 4 | 30 minutes | about 2105 seconds | | 5 | 2 hours | about 155 minutes | | 6 | 5 hours | about 455 minutes | | 7 | 10 hours | about 1055 minutes | | 8 | 14 hours | about 32 hours | | 9 | 20 hours | about 52 hours | | 10 | 1 day | about 76 hours | On `429`, `502` and `504`, a longer `retry-after` from your server is respected. A `410 Gone` answer disables the endpoint at once; an endpoint whose deliveries keep failing for 5 days is disabled too. A disabled endpoint's `disabled_reason` says why; set its `status` back to `enabled` with `PATCH /v1/webhook_endpoints/{id}` once it works. ## Delivery logs and redelivery `GET /v1/webhook_endpoints/{id}/deliveries` lists every delivery to an endpoint with its last status code, duration, the start of your server's answer and when the next attempt is due: | Field | Type | Description | | --- | --- | --- | | `object` | `string` | Always webhook_delivery.
One of `webhook_delivery` | | `id` | `string` | whd_… | | `event_id` | `string` | | | `event_type` | `string` | | | `webhook_endpoint_id` | `string` | | | `status` | `string` | pending: an attempt is due (next_attempt_at). failed: every attempt failed, or the endpoint was disabled or deleted.
One of `pending`, `succeeded`, `failed` | | `attempts` | `integer` | Attempts made so far. | | `status_code` | `integer` or `null` | The last attempt's HTTP status; null when it got none (timeout, refused, blocked URL). | | `duration_ms` | `integer` or `null` | The last attempt's duration. | | `response_excerpt` | `string` or `null` | The start of the last response body. | | `error` | `string` or `null` | Why the last attempt failed when there was no response. | | `created_at` | `string` (date-time) | | | `delivered_at` | `string` (date-time) or `null` | | | `next_attempt_at` | `string` (date-time) or `null` | | `POST /v1/events/{id}/redeliver` sends an event again (with the same `webhook-id`), to one endpoint or to every endpoint subscribed to it. `POST /v1/webhook_endpoints/{id}/test` sends a `webhook.test` event to one endpoint, to check your handler end to end. ## Rotate a secret `POST /v1/webhook_endpoints/{id}/rotate_secret` issues a new secret, shown once. For `previous_secret_expires_in` seconds (default and maximum 1 day) every delivery carries two signatures, one per secret, so you can deploy the new secret without dropping a delivery. Verify against both during the switch. ## Keys and test mode Reading webhook endpoints and their deliveries needs the `account:read` scope; creating, changing, deleting, rotating, testing and redelivering need `admin`. A test key manages a separate set of test endpoints: they receive test events only (a test job's `job.*` events and test deliveries), signed and retried like live ones, and live endpoints never receive them. --- Source: https://staging-fly.socialtap.dev/docs/events/ # Events Jobs and events Every event type, its data, and how to list and replay events: what webhooks deliver, kept for a month. An event records something that happened in your account. Every event is kept for 30 days and listed by `GET /v1/events`, whether or not a webhook endpoint received it, and each one is delivered to the [webhook endpoints](https://staging-fly.socialtap.dev/docs/webhooks/index.md) subscribed to its type. | Route | What it does | Key scope | | --- | --- | --- | | `GET /v1/events` | List events, newest first | `account:read` | | `GET /v1/events/{id}` | Get an event | `account:read` | | `POST /v1/events/{id}/redeliver` | Deliver an event again | `admin` | ## The event object The body of a webhook delivery is the event object, exactly as `GET /v1/events/{id}` returns it: | Field | Type | Description | | --- | --- | --- | | `object` | `string` | Always event.
One of `event` | | `id` | `string` | The event ID (evt_…): the webhook-id header of every delivery, the same on retries. Deduplicate on it. | | `type` | `string` | The event type (event types table); new types may be added. | | `timestamp` | `string` (date-time) | When it happened. | | `data` | `any` | The type's data object (small: a summary plus links). | ## Event types | Type | When | data | | --- | --- | --- | | `job.completed` | A job ended with every item ended (check request_counts: items fail individually). | `JobEventData` | | `job.canceled` | A canceled job ended: its running items finished. | `JobEventData` | | `job.expired` | A job reached its deadline; its unfinished items expired. | `JobEventData` | | `credits.low` | A balance change took the balance from above the account's low_balance_threshold to at or below it (once per crossing; the owner is emailed too). | `CreditsLowData` | | `credits.depleted` | The balance reached 0 (once, until credits are added). | `CreditsDepletedData` | | `api_key.spend_limit_reached` | An API key's request (or job) was refused for its spend limit, the first time in the limit's interval. | `SpendLimitReachedData` | | `webhook.test` | Sent by POST /v1/webhook_endpoints/{id}/test to that endpoint only. | `WebhookTestData` | New types may be added; ignore the ones you don't handle. Subscribe an endpoint to a family with a wildcard (`job.*`) or to everything with `*`, and filter `GET /v1/events` the same way with its `type` parameter. ## Job events `job.completed`, `job.canceled` and `job.expired` carry the job's summary and links, never its results: *(Omitted: another section of this file has it.)* `url` is the job (GET it with your API key); `results_url` is the job's own signed JSON Lines link to every result, which works without the key until it expires (GET `url` for a fresh one). ## Account events The balance and spend events come from the account itself (live keys only: test requests never touch the balance), and `webhook.test` from a test delivery. Their data: ### `credits.low` | Field | Type | Description | | --- | --- | --- | | `balance` | `integer` | The balance after the change that crossed the threshold. | | `low_balance_threshold` | `integer` | The account's low_balance_threshold at the time. | ### `credits.depleted` | Field | Type | Description | | --- | --- | --- | | `balance` | `integer` | The balance: 0. | ### `api_key.spend_limit_reached` | Field | Type | Description | | --- | --- | --- | | `api_key_id` | `string` | The key (key_…) whose spend limit was reached. | | `spend_limit` | `object` | Its spend limit. | | `spend_limit.amount` | `integer` | Most credits the key may spend per interval. | | `spend_limit.interval` | `string` | Calendar day or month, UTC.
One of `day`, `month` | | `used` | `integer` | Credits the key had used this interval (spent plus held by its requests in flight). | | `resets_at` | `string` (date-time) | Start of the next interval, RFC 3339 UTC: the key can spend again then. | ### `webhook.test` | Field | Type | Description | | --- | --- | --- | | `webhook_endpoint_id` | `string` | | --- Source: https://staging-fly.socialtap.dev/docs/reference/facebook/post/ # Facebook Post API Facebook Beta Returns a public Facebook post, photo, video or reel: its text with hashtags and mentions, the author with their verification badge, the publish time, every photo of an album and the video's file, thumbnail, size and duration, the shared link card or reshared post, and the exact reaction, comment, share and view counts with the reactions by type. Accepts post, permalink, photo, reel, watch, video, fb.watch and share links or a numeric ID. GET `/v1/facebook/post` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/facebook/post' \ --data-urlencode 'url=https://www.facebook.com/reel/4075684206061398/' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":{"approximate_fields":[],"author":{"avatar_url":"https://scontent-atl3-2.xx.fbcdn.net/v/t39.30808-1/243095782…","display_name":"NASA - National Aeronautics and Space Administration","id":"100044561550831","is_verified":true,"url":"https://www.facebook.com/NASA","username":"NASA"},"coauthors":[],"duration_ms":39381,"hashtags":[],"id":"1670768087751894","is_nsfw":null,"is_pinned":null,"is_sponsored":null,"language":null,"media":[{"alt_text":null,"duration_ms":39381,"expires_at":"2026-10-25T18:00:00Z","height":2160,"thumbnail_url":"https://scontent-atl3-2.xx.fbcdn.net/v/t15.5256-10/877658791…","type":"video","url":"https://video-atl3-1.xx.fbcdn.net/o1/v/t2/f2/m366/AQMS1Kmor5…","width":3840}],"mentions":["CanadianSpaceAgency","ISS"],"metrics":{"comment_count":55,"like_count":574,"quote_count":null,"repost_count":null,"save_count":null,"share_count":null,"view_count":52713},"platform":"facebook","platform_specific":{"facebook":{"feedback_id":"ZmVlZGJhY2s6MTY3MDc2ODA4Nzc1MTg5NA==","link":null,"reactions":[{"count":476,"type":"like"},{"count":70,"type":"love"}],"shared_post":null,"video_id":"4075684206061398"}},"platform_type":"reel","published_at":"2026-10-29T11:48:37Z","text":"NASA astronaut Anil Menon and Canadian Space Agency astronau…","thumbnail_url":"https://scontent-atl3-2.xx.fbcdn.net/v/t15.5256-10/877658791…","title":null,"type":"video","unknown_fields":[],"url":"https://www.facebook.com/reel/4075684206061398/"},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"facebook","source":"page","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"video","id":"1670768087751894","url":"https://www.facebook.com/reel/4075684206061398/"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - Counts are exact, as Facebook's page states them. view_count is set on videos and reels; share_count on posts and photos (Facebook's video page shows none logged out). > - Media URLs are Facebook CDN links that expire: media\[\].expires_at says when; download what you need before then. > - Posts that are private, limited to friends or a group, or deleted return private_or_unavailable: logged out, Facebook answers them all alike. They are not charged. > - If the post's page is unavailable, the answer comes from Facebook's embedded post: counts as displayed (large ones rounded, see approximate_fields), and no view count, reactions by type, feedback ID or reshared post, nor a video's file and length (partial_data warning). > - Past and current live broadcasts are platform_type live, with their title. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `url` required | `string`
max 2,048 chars | Facebook post, photo, video or reel URL: /\/posts/\, permalink.php and story.php links, /photo/?fbid= and /\/photos/… links, reel, watch, video.php and /\/videos/… links, fb.watch and /share/p\|v\|r/ share links (m., mbasic. and web. hosts too), or a numeric post, photo or video ID. | `https://www.facebook.com/reel/4075684206061398/` | `url` takes Facebook post and video links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). ## Response ### 200 `{data, meta}`. `data` is a [post](https://staging-fly.socialtap.dev/resources/post/index.md) object; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). Fields only Facebook has are under `platform_specific.facebook`. Fields: the [post schema](https://staging-fly.socialtap.dev/resources/post/index.md), with `platform_specific.facebook`. not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pricing | Source | Price per successful request | Per 1,000 requests | | --- | --- | --- | | Public page `page` | 1 credit | $0.75–$1.45 | Endpoint ID `facebook.post` · operationId `facebook.post.get` · [Product overview](https://staging-fly.socialtap.dev/apis/facebook/post/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/facebook/search_ads/ # Facebook Ad Library Search API Facebook Beta Searches Meta's public Ad Library by keyword and returns the matching ads that ran on Facebook, Instagram, Messenger, Threads and the Audience Network: the advertiser's page, the days the ad first and last ran, whether it is still active, where it ran, and each creative's headline, text, link, call to action, images and videos. Political and social-issue ads also carry the disclosed payer and Meta's impressions, reach and spend ranges. Filter by country, language, active status and the day an ad first ran; results come in the Ad Library's own order, most impressions first. GET `/v1/facebook/search_ads` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/facebook/search_ads' \ --data-urlencode 'country=US' \ --data-urlencode 'query=coffee' \ --data-urlencode 'status=all' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":[{"advertiser":{"avatar_url":"https://scontent-atl3-2.xx.fbcdn.net/v/t39.35426-6/739782028…","display_name":"American Health Support Community","id":"966175593248497","is_verified":null,"url":"https://www.facebook.com/61588018263996","username":null},"approximate_fields":["first_shown_at"],"audience_size":null,"country_codes":[],"creatives":[{"body":"My aunt called me a creep for putting a camera in Nana’s liv…","cta_text":"Learn more","link_description":null,"link_url":"https://shop.pipitea.com/hbt/kd/sp-nm","media":[{"alt_text":null,"duration_ms":null,"expires_at":"2026-10-25T18:00:00Z","height":null,"thumbnail_url":"https://scontent-atl3-1.xx.fbcdn.net/v/t39.35426-6/742001725…","type":"image","url":"https://scontent-atl3-1.xx.fbcdn.net/v/t39.35426-6/742501415…","width":null}],"title":"Natural Relief in Weeks"}],"first_shown_at":"2026-07-18T07:00:00Z","id":"2619351891814847","impressions":null,"is_active":true,"is_political":false,"languages":[],"last_shown_at":null,"paid_by":null,"platform":"facebook","platform_specific":{},"publisher_platforms":["facebook","instagram"],"reach":null,"spend":null,"type":"image","unknown_fields":[],"url":"https://www.facebook.com/ads/library/?id=2619351891814847"},{"advertiser":{"avatar_url":"https://scontent-atl3-1.xx.fbcdn.net/v/t39.35426-6/712623693…","display_name":"American Health Support Community","id":"966175593248497","is_verified":null,"url":"https://www.facebook.com/61588018263996","username":null},"approximate_fields":["first_shown_at"],"audience_size":null,"country_codes":[],"creatives":[{"body":"I almost looked past the woman who saved my husband's life. …","cta_text":"Learn more","link_description":"\"I've tried everything to support my kidney function natural…","link_url":"https://shop.pipitea.com/hbt/kd/sp","media":[{"alt_text":null,"duration_ms":null,"expires_at":"2026-10-25T18:00:00Z","height":null,"thumbnail_url":"https://scontent-atl3-2.xx.fbcdn.net/v/t39.35426-6/711729685…","type":"image","url":"https://scontent-atl3-1.xx.fbcdn.net/v/t39.35426-6/713729791…","width":null}],"title":"Natural Relief in Weeks"}],"first_shown_at":"2026-06-01T07:00:00Z","id":"1923645134954720","impressions":null,"is_active":true,"is_political":false,"languages":[],"last_shown_at":null,"paid_by":null,"platform":"facebook","platform_specific":{},"publisher_platforms":["facebook","instagram"],"reach":null,"spend":null,"type":"image","unknown_fields":[],"url":"https://www.facebook.com/ads/library/?id=1923645134954720"}],"page":{"has_more":true,"next_cursor":"c1.EXAMPLE_CURSOR","next_cursor_expires_at":"2026-10-26T12:00:00Z","end_reason":null},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"facebook","source":"api","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":null,"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - Payer, impressions, reach and spend are disclosed only for political and social-issue ads; they are null for other ads. > - Dates are whole days (Meta reports the day an ad first and last ran, US Pacific time), so they are listed in approximate_fields. > - since and until return the ads that ran during the window (first ran before until, and still running or last ran at or after since), whole days as Meta reports them; a filtered page can hold fewer ads than the page size, and that is not the end of the list. > - Ads that share one creative are grouped by Meta; each ad of a group is returned as its own item. > - Languages, targeting and EU reach by country are not part of the search results; those fields are empty or null. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `query` required | `string`
max 200 chars | Keywords matched against the ad's text, in any order. Wrap the query in double quotes ("cold brew") to match an exact phrase. | `coffee` | | `country` optional | `string`
iso3166-1-alpha-2 | Region: an ISO 3166-1 alpha-2 country code such as US or BR (case-insensitive). Only ads delivered in this country. All countries when omitted. | `US` | | `lang` optional | `string`
bcp47 | Preferred language: a BCP-47 tag such as en or pt-BR (case-insensitive; _ is read as -). Only ads whose text Meta detects as this language (the primary subtag counts: pt-BR searches pt). All languages when omitted. | `en` | | `status` optional | `string` | Only items that are still running (active), only items that stopped (inactive), or both (all). Default: all.
One of `active`, `inactive`, `all` | `active` | | `since` optional | `string`
date-time | Only items at or after this time: RFC 3339 (2026-10-01T12:00:00Z) or a date (2026-10-01, midnight UTC). On newest-first lists, paging stops when it is reached, so later pages are not fetched or charged. Ads count when they ran during the window: an ad that last ran (last_shown_at) before since is left out; active ads ran until today. | `2026-10-01` | | `until` optional | `string`
date-time | Only items before this time (exclusive): RFC 3339 or a date (midnight UTC). Ads count when they ran during the window: an ad that first ran (first_shown_at) at or after until is left out. | `2026-10-08` | `limit` and `cursor` page through the list: see Pagination. ## Response ### 200 `{data, page, meta}`. `data` is an array of up to 10 [ad](https://staging-fly.socialtap.dev/resources/ad/index.md) objects, most impressions first; `page` says how to get the next page; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). The fields are the shared ad schema, the same on every platform. Fields: the [ad schema](https://staging-fly.socialtap.dev/resources/ad/index.md). not_found is item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `invalid_cursor` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pagination Items come in pages, **most impressions first**. - **Order**: Most impressions first - **Page size**: Up to 10 ads (the default; `limit` lowers it) - **Cursor lifetime**: 1 hour; don't store cursors - **Price**: Per page, 1 credit [Pagination guide](https://staging-fly.socialtap.dev/docs/pagination/index.md) ## Pricing | Source | Price per successful page | Per 1,000 pages | | --- | --- | --- | | Platform API `api` | 1 credit | $0.75–$1.45 | Endpoint ID `facebook.search_ads` · operationId `facebook.search_ads.list` · [Product overview](https://staging-fly.socialtap.dev/apis/facebook/search_ads/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/facebook/transcript/ # Facebook Video Transcript API Facebook Beta Returns the timed transcript of a public Facebook video or reel: the uploaded or auto-generated caption track when Facebook has one, otherwise speech-to-text on the video's audio. Accepts reel, watch, page video, video.php, fb.watch and share links, or a numeric video ID. GET `/v1/facebook/transcript` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/facebook/transcript' \ --data-urlencode 'url=https://www.facebook.com/reel/1803909017420449/' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":{"available_languages":["en-US"],"duration_ms":59861,"id":"1803909017420449","is_auto_generated":false,"language":"en-US","platform":"facebook","platform_specific":{},"segments":[{"end_ms":4000,"start_ms":33,"text":"But the standard that we should continuously aspire to exceed,"},{"end_ms":7166,"start_ms":4000,"text":"which is why we are bringing that intensity, that discipline, that"}],"source":"captions","text":"But the standard that we should continuously aspire to excee…","url":"https://www.facebook.com/reel/1803909017420449/"},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"facebook","source":"captions","is_cached":false,"credits_charged":1,"latency_ms":1517,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"video","id":"1803909017420449","url":"https://www.facebook.com/reel/1803909017420449/"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - Videos that Facebook only serves to logged-in users return private_or_unavailable. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `url` required | `string`
max 2,048 chars | Facebook video URL (reel, watch, page video, video.php, m./mbasic./web. hosts, fb.watch and share/v\|r links) or a numeric video ID. | `https://www.facebook.com/reel/1803909017420449` | `url` takes Facebook video links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). ## Response ### 200 `{data, meta}`. `data` is a [transcript](https://staging-fly.socialtap.dev/resources/transcript/index.md) object; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). The fields are the shared transcript schema, the same on every platform. Fields: the [transcript schema](https://staging-fly.socialtap.dev/resources/transcript/index.md). not_found, private_or_unavailable and no_transcript are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `no_transcript` 404, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pricing | Source | Price per successful request | Per 1,000 requests | | --- | --- | --- | | Captions `captions` | 1 credit | $0.75–$1.45 | | Speech-to-text `asr` | 2 credits + 2 per started minute of media
up to 122 credits (60 started minutes of media) | $1.50–$2.90 + $1.50–$2.90 per started minute of media | Example: a result from Speech-to-text `asr` with 3 started minutes of media costs 2 + 2 × 3 = **8 credits**. Endpoint ID `facebook.transcript` · operationId `facebook.transcript.get` · [Product overview](https://staging-fly.socialtap.dev/apis/facebook/transcript/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/instagram/post/ # Instagram Post API Instagram Beta Returns a public Instagram post, reel or carousel: the caption with its hashtags and mentions, the author and any collaborators, the publish time, every carousel item's image or video URL with its size, duration and alt text, and the like, comment and (for single videos) view counts. Accepts post, reel, tv, instagr.am and share links or a raw shortcode. GET `/v1/instagram/post` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/instagram/post' \ --data-urlencode 'url=https://www.instagram.com/reel/De1S3KpCXf0/' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":{"approximate_fields":[],"author":{"avatar_url":"https://scontent-atl3-2.cdninstagram.com/v/t51.82787-19/6835…","display_name":"National Geographic","id":"787132","is_verified":true,"url":"https://www.instagram.com/natgeo/","username":"natgeo"},"coauthors":[{"avatar_url":"https://scontent-atl3-1.cdninstagram.com/v/t51.2885-19/29349…","display_name":"National Geographic Society","id":"4757225830","is_verified":true,"url":"https://www.instagram.com/natgeosociety/","username":"natgeosociety"},{"avatar_url":"https://scontent-atl3-2.cdninstagram.com/v/t51.2885-19/11419…","display_name":"Joel Sartore- Photo Ark","id":"225429667","is_verified":true,"url":"https://www.instagram.com/joelsartore/","username":"joelsartore"}],"duration_ms":23744,"hashtags":["PhotoArk"],"id":"De1S3KpCXf0","is_nsfw":null,"is_pinned":null,"is_sponsored":null,"language":null,"media":[{"alt_text":"Video by National Geographic on October 23, 2026. May be an …","duration_ms":23744,"expires_at":"2026-10-25T18:00:00Z","height":1920,"thumbnail_url":"https://scontent-atl3-2.cdninstagram.com/v/t51.82787-15/8625…","type":"video","url":"https://scontent-atl3-1.cdninstagram.com/o1/v/t2/f2/m86/AQO0…","width":1080}],"mentions":["joelsartore","natgeoanimals"],"metrics":{"comment_count":238,"like_count":68253,"quote_count":null,"repost_count":null,"save_count":null,"share_count":null,"view_count":759188},"platform":"instagram","platform_specific":{"instagram":{"is_ai_generated":null}},"platform_type":"reel","published_at":"2026-10-23T10:01:35Z","text":"Warning—looking at nudibranchs may cause excessive awe. @joe…","thumbnail_url":"https://scontent-atl3-2.cdninstagram.com/v/t51.82787-15/8625…","title":null,"type":"video","unknown_fields":[],"url":"https://www.instagram.com/reel/De1S3KpCXf0/"},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"instagram","source":"page","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"post","id":"De1S3KpCXf0","url":"https://www.instagram.com/reel/De1S3KpCXf0/"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - Media URLs are Instagram CDN links that expire: media\[\].expires_at says when; download what you need before then. > - view_count is set for single videos and reels only (Instagram shows no view count for photos and carousels). like_count and view_count are null when the owner hides them. When Instagram's embed page, the only logged-out source of the view count, fails or omits it, the post is still returned with view_count null and a partial_data warning in meta.warnings. > - Instagram does not expose share or save counts, pinned state, sponsorship labels or the caption's language to logged-out visitors: those fields are null. > - Private, age-restricted and deleted posts return private_or_unavailable or not_found and are not charged. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `url` required | `string`
max 2,048 chars | Instagram post, reel or carousel URL (instagram.com/p/…, /reel/…, /reels/…, /tv/…, /\/p/…, m.instagram.com, instagr.am, instagram.com/share/… links) or a raw shortcode. Tracking params and img_index are ignored: the whole post is returned. | `https://www.instagram.com/reel/De1S3KpCXf0/` | `url` takes Instagram post and video links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). ## Response ### 200 `{data, meta}`. `data` is a [post](https://staging-fly.socialtap.dev/resources/post/index.md) object; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). Fields only Instagram has are under `platform_specific.instagram`. Fields: the [post schema](https://staging-fly.socialtap.dev/resources/post/index.md), with `platform_specific.instagram`. not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pricing | Source | Price per successful request | Per 1,000 requests | | --- | --- | --- | | Public page `page` | 1 credit | $0.75–$1.45 | Endpoint ID `instagram.post` · operationId `instagram.post.get` · [Product overview](https://staging-fly.socialtap.dev/apis/instagram/post/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/instagram/posts/ # Instagram Posts API Instagram Beta Returns a public Instagram account's posts, reels and carousels as they appear on its profile grid, newest first, page by page: caption with hashtags and mentions, publish time, author and collaborators, every carousel item's image or video with its size and duration, tagged accounts, pinned posts, and like, comment and (for videos) view counts. Accepts a username or any profile link. GET `/v1/instagram/posts` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/instagram/posts' \ --data-urlencode 'handle=nasa' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":[{"approximate_fields":[],"author":{"avatar_url":"https://scontent-atl3-2.cdninstagram.com/v/t51.2885-19/29090…","display_name":"NASA","id":"528817151","is_verified":true,"url":"https://www.instagram.com/nasa/","username":"nasa"},"coauthors":[{"avatar_url":"https://scontent-atl3-2.cdninstagram.com/v/t51.2885-19/93880…","display_name":"NASA Solar System Exploration","id":"1611722079","is_verified":true,"url":"https://www.instagram.com/nasasolarsystem/","username":"nasasolarsystem"},{"avatar_url":"https://scontent-atl3-2.cdninstagram.com/v/t51.82787-19/6700…","display_name":"NASA Artemis","id":"1104426670","is_verified":true,"url":"https://www.instagram.com/nasaartemis/","username":"nasaartemis"}],"duration_ms":43767,"hashtags":["NASA","ArtemisII"],"id":"DeTUk4sDSp-","is_nsfw":null,"is_pinned":true,"is_sponsored":false,"language":null,"media":[{"alt_text":null,"duration_ms":43767,"expires_at":"2026-10-25T18:00:00Z","height":1908,"thumbnail_url":"https://scontent-atl3-2.cdninstagram.com/v/t51.82787-15/8421…","type":"video","url":"https://scontent-atl3-2.cdninstagram.com/o1/v/t2/f2/m86/AQNU…","width":1074}],"mentions":["nasaartemis","nasasolarsystem"],"metrics":{"comment_count":7628,"like_count":755597,"quote_count":null,"repost_count":null,"save_count":null,"share_count":null,"view_count":11075076},"platform":"instagram","platform_specific":{"instagram":{"is_ai_generated":false}},"platform_type":"reel","published_at":"2026-10-10T15:15:02Z","text":"POV: you're watching the Earth set\n\nWe just released hundred…","thumbnail_url":"https://scontent-atl3-2.cdninstagram.com/v/t51.82787-15/8421…","title":null,"type":"video","unknown_fields":[],"url":"https://www.instagram.com/reel/DeTUk4sDSp-/"},{"approximate_fields":[],"author":{"avatar_url":"https://scontent-atl3-2.cdninstagram.com/v/t51.2885-19/43202…","display_name":"NASA Webb Telescope","id":"549313808","is_verified":true,"url":"https://www.instagram.com/nasawebb/","username":"nasawebb"},"coauthors":[{"avatar_url":"https://scontent-atl3-2.cdninstagram.com/v/t51.2885-19/43778…","display_name":"NASA Universe","id":"65495240044","is_verified":true,"url":"https://www.instagram.com/nasauniverse/","username":"nasauniverse"},{"avatar_url":"https://scontent-atl3-2.cdninstagram.com/v/t51.2885-19/29090…","display_name":"NASA","id":"528817151","is_verified":true,"url":"https://www.instagram.com/nasa/","username":"nasa"}],"duration_ms":null,"hashtags":[],"id":"DeJ28eTxo4s","is_nsfw":null,"is_pinned":true,"is_sponsored":false,"language":null,"media":[{"alt_text":null,"duration_ms":null,"expires_at":"2026-10-25T18:00:00Z","height":1872,"thumbnail_url":"https://scontent-atl3-1.cdninstagram.com/v/t51.82787-15/8388…","type":"image","url":"https://scontent-atl3-1.cdninstagram.com/v/t51.82787-15/8388…","width":2000}],"mentions":["NASAWebb","nasa"],"metrics":{"comment_count":1027,"like_count":290040,"quote_count":null,"repost_count":null,"save_count":null,"share_count":null,"view_count":null},"platform":"instagram","platform_specific":{"instagram":{"is_ai_generated":false}},"platform_type":"post","published_at":"2026-10-06T13:10:11Z","text":"Brewing up new stars 🪄\n \n@NASAWebb’s unique view of the ste…","thumbnail_url":"https://scontent-atl3-1.cdninstagram.com/v/t51.82787-15/8388…","title":null,"type":"image","unknown_fields":[],"url":"https://www.instagram.com/p/DeJ28eTxo4s/"}],"page":{"has_more":true,"next_cursor":"c1.EXAMPLE_CURSOR","next_cursor_expires_at":"2026-10-26T12:00:00Z","end_reason":null},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"instagram","source":"api","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"profile","id":"528817151","url":"https://www.instagram.com/nasa/"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - Pinned posts lead the first page, whatever their age. Instagram does not mark pins for logged-out visitors: is_pinned is inferred from the order, and null for a leading post the order cannot tell. > - view_count is set for videos; like_count and view_count are null when the owner hides them. If Instagram's view counts fail on a later page, it is returned without them and with a partial_data warning. > - Media URLs are Instagram CDN links that expire: media\[\].expires_at says when. > - Share and save counts and the caption's language are not public logged out: those fields are null. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `handle` required | `string`
max 2,048 chars | Instagram username with or without the @, or a profile URL (instagram.com/nasa, m.instagram.com, instagr.am, a /reels/ or /tagged/ tab, an instagram.com/_u/ app link). Numeric user IDs are not accepted: Instagram does not resolve them logged out. | `nasa` | | `since` optional | `string`
date-time | Only items at or after this time: RFC 3339 (2026-10-01T12:00:00Z) or a date (2026-10-01, midnight UTC). On newest-first lists, paging stops when it is reached, so later pages are not fetched or charged. | `2026-10-01` | | `until` optional | `string`
date-time | Only items before this time (exclusive): RFC 3339 or a date (midnight UTC). | `2026-10-08` | `handle` takes Instagram profile links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). `limit` and `cursor` page through the list: see Pagination. ## Response ### 200 `{data, page, meta}`. `data` is an array of up to 12 [post](https://staging-fly.socialtap.dev/resources/post/index.md) objects, newest first; `page` says how to get the next page; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). Fields only Instagram has are under `platform_specific.instagram`. Fields: the [post schema](https://staging-fly.socialtap.dev/resources/post/index.md), with `platform_specific.instagram`. not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `invalid_cursor` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pagination Items come in pages, **newest first**. - **Order**: Newest first - **Page size**: Up to 12 posts (the default; `limit` lowers it) - **Cursor lifetime**: 24 hours; don't store cursors - **Price**: Per page, 1 credit [Pagination guide](https://staging-fly.socialtap.dev/docs/pagination/index.md) ## Pricing | Source | Price per successful page | Per 1,000 pages | | --- | --- | --- | | Any source | 1 credit | $0.75–$1.45 | Endpoint ID `instagram.posts` · operationId `instagram.posts.list` · [Product overview](https://staging-fly.socialtap.dev/apis/instagram/posts/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/instagram/profile/ # Instagram Profile API Instagram Beta Returns a public Instagram account: user ID, username, display name, bio, the links in its bio with their titles, profile picture, verification badge, whether the account is private, and exact follower, following and post counts, plus Instagram's own fields (Meta-wide ID, memorialized, live story, highlights, pronouns). Accepts a username with or without the @ or any instagram.com profile link. GET `/v1/instagram/profile` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/instagram/profile' \ --data-urlencode 'handle=https://www.instagram.com/natgeo/' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":{"approximate_fields":[],"avatar_url":"https://scontent-atl3-2.cdninstagram.com/v/t51.82787-19/6835…","banner_url":null,"bio":"Step into wonder and find your inner explorer with National Geographic 🌎","category":null,"country_code":null,"created_at":null,"display_name":"National Geographic","external_links":[{"title":null,"url":"http://visitstore.bio/natgeo"},{"title":"Subscribe Here!","url":"https://ngmdomsubs.nationalgeographic.com/servlet/OrdersGate…"}],"id":"787132","is_private":false,"is_verified":true,"location":null,"metrics":{"follower_count":268266804,"following_count":195,"post_count":32123,"total_like_count":null,"total_view_count":null},"platform":"instagram","platform_specific":{"instagram":{"fbid":"17841400573960012","is_highlights_shown":true,"is_memorialized":false,"is_story_live":true,"pronouns":[]}},"type":"unknown","unknown_fields":[],"url":"https://www.instagram.com/natgeo/","username":"natgeo"},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"instagram","source":"page","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"profile","id":"787132","url":"https://www.instagram.com/natgeo/"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - Private accounts return their profile with is_private true: name, bio, links and counts as Instagram shows them logged out. > - metrics.post_count is exact; on the rare answer where only a rounded count was available it is listed in approximate_fields. > - category, location, country_code, created_at, banner_url, metrics.total_like_count and metrics.total_view_count are always null and type is unknown: Instagram does not show them to logged-out visitors. Business email and phone are not returned. > - avatar_url is Instagram's 150-pixel profile picture, a CDN link that expires within days. > - When the profile page is unavailable the answer comes from Instagram's embed page: bio and following count are then null, and external_links is \[\] listed in unknown_fields. > - Unknown, deactivated and banned accounts answer not_found; restricted ones (age-restricted brands) private_or_unavailable. Numeric user IDs are not accepted: a bare number is read as a username. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `handle` required | `string`
max 2,048 chars | Instagram username with or without the @ (natgeo, @natgeo), or a profile link (instagram.com/natgeo, with or without a tab such as /reels/ or /tagged/, m.instagram.com, instagr.am, instagram.com/_u/natgeo, instagram.com/stories/natgeo/, with any query such as ?igsh=…). Usernames are case-insensitive. A bare number is a username: numeric user IDs are not accepted. | `natgeo` | `handle` takes Instagram profile links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). ## Response ### 200 `{data, meta}`. `data` is a [profile](https://staging-fly.socialtap.dev/resources/profile/index.md) object; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). Fields only Instagram has are under `platform_specific.instagram`. Fields: the [profile schema](https://staging-fly.socialtap.dev/resources/profile/index.md), with `platform_specific.instagram`. not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pricing | Source | Price per successful request | Per 1,000 requests | | --- | --- | --- | | Public page `page` | 1 credit | $0.75–$1.45 | Endpoint ID `instagram.profile` · operationId `instagram.profile.get` · [Product overview](https://staging-fly.socialtap.dev/apis/instagram/profile/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/instagram/transcript/ # Instagram Reels Transcript API Instagram Beta Returns the timed transcript of a public Instagram reel or video post. Instagram rarely exposes caption tracks to logged-out clients, so most transcripts are speech-to-text on the post's audio. Accepts reel, post, tv, instagr.am and share links or a raw shortcode; for carousels, img_index selects the item. GET `/v1/instagram/transcript` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/instagram/transcript' \ --data-urlencode 'url=https://www.instagram.com/p/De1S3KpCXf0/' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":{"available_languages":[],"duration_ms":23745,"id":"De1S3KpCXf0","is_auto_generated":true,"language":"en","platform":"instagram","platform_specific":{},"segments":[{"end_ms":3580,"start_ms":0,"text":"You are looking at nudibranchs. You ever seen anything that crazy looking?"},{"end_ms":9220,"start_ms":3800,"text":"Every color in the rainbow. These are marine snails basicall…"}],"source":"asr","text":"You are looking at nudibranchs. You ever seen anything that …","url":"https://www.instagram.com/reel/De1S3KpCXf0/"},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"instagram","source":"asr","is_cached":false,"credits_charged":4,"latency_ms":4125,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"video","id":"De1S3KpCXf0","url":"https://www.instagram.com/reel/De1S3KpCXf0/"},"credits_remaining":9996,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - Most results come from speech-to-text (meta.source = asr) and are priced per minute. > - Image posts return invalid_input; videos without speech return no_transcript. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `url` required | `string`
max 2,048 chars | Instagram reel/post URL (instagram.com/reel/…, /reels/…, /p/…, /tv/…, instagr.am, instagram.com/share/… links) or a raw shortcode. For carousel posts, ?img_index=N selects the item; default is the first video. | `https://www.instagram.com/reel/De1S3KpCXf0/` | `url` takes Instagram video and post links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). ## Response ### 200 `{data, meta}`. `data` is a [transcript](https://staging-fly.socialtap.dev/resources/transcript/index.md) object; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). The fields are the shared transcript schema, the same on every platform. Fields: the [transcript schema](https://staging-fly.socialtap.dev/resources/transcript/index.md). not_found, private_or_unavailable and no_transcript are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `no_transcript` 404, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pricing | Source | Price per successful request | Per 1,000 requests | | --- | --- | --- | | Captions `captions` | 1 credit | $0.75–$1.45 | | Speech-to-text `asr` | 2 credits + 2 per started minute of media
up to 122 credits (60 started minutes of media) | $1.50–$2.90 + $1.50–$2.90 per started minute of media | Example: a result from Speech-to-text `asr` with 3 started minutes of media costs 2 + 2 × 3 = **8 credits**. Endpoint ID `instagram.transcript` · operationId `instagram.transcript.get` · [Product overview](https://staging-fly.socialtap.dev/apis/instagram/transcript/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/rumble/comments/ # Rumble Comments API Rumble Beta Returns the public comments of a Rumble video, short or live stream, replies included, a page at a time: text, the commenter's username, name, avatar and badges, posting time, likes and dislikes, reply count, the comment each reply answers, a permalink, and whether the video's own channel wrote it. Sort newest first (the default, which also supports since and until) or in Rumble's Sort by likes order, and follow page.next_cursor for more. Accepts every Rumble video link form, embed links included, or a bare video ID. GET `/v1/rumble/comments` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/rumble/comments' \ --data-urlencode 'url=https://rumble.com/shorts/v7h29za' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":[{"approximate_fields":["published_at"],"author":{"avatar_url":"https://hugh.cdn.rumble.cloud/video/z0/e/b/M/g/ebMgr.asF.1-4qu5ac-tnkdxy.jpeg","display_name":"inshallakhan","id":null,"is_verified":false,"url":"https://rumble.com/user/inshallakhan","username":"user/inshallakhan"},"edited_at":null,"id":"620403472","is_by_post_author":false,"is_edited":null,"is_liked_by_author":null,"is_pinned":null,"metrics":{"like_count":0,"reply_count":0},"parent_id":null,"platform":"rumble","platform_specific":{"rumble":{"author_badges":[],"dislike_count":0}},"post_id":"v7h29za","published_at":"2026-10-23T03:31:00Z","text":"oh 😯😯","unknown_fields":[],"url":"https://rumble.com/shorts/v7h29za#comment-620403472"},{"approximate_fields":["published_at"],"author":{"avatar_url":"https://hugh.cdn.rumble.cloud/video/z0/C/E/l/b/CElbr.asF-4pzq1o-ti0cx0.jpeg","display_name":"CassandraB89","id":null,"is_verified":false,"url":"https://rumble.com/user/CassandraB89","username":"user/CassandraB89"},"edited_at":null,"id":"620356964","is_by_post_author":false,"is_edited":null,"is_liked_by_author":null,"is_pinned":null,"metrics":{"like_count":0,"reply_count":0},"parent_id":null,"platform":"rumble","platform_specific":{"rumble":{"author_badges":[],"dislike_count":0}},"post_id":"v7h29za","published_at":"2026-10-22T13:01:00Z","text":"🐈👌🏻🧡","unknown_fields":[],"url":"https://rumble.com/shorts/v7h29za#comment-620356964"}],"page":{"has_more":false,"next_cursor":null,"next_cursor_expires_at":null,"end_reason":"end"},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"rumble","source":"api","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"video","id":"v7h29za","url":"https://rumble.com/v7h29za"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - Replies are included: parent_id names the comment a reply answers. Sorted newest, comments and replies are interleaved by time; sorted popular, each comment is followed by its replies. > - Rumble shows comment times to the minute: published_at is exact to the minute and listed in approximate_fields. > - Rumble's comment count for a video (on its page and in rumble.post) includes comments it no longer lists, so a full walk can return fewer. > - metrics.reply_count is Rumble's own count of a comment's replies and can exceed the replies returned: Rumble counts replies it no longer lists. > - author.id is null: Rumble's comments name the commenter by username only (author.username, user/\, joins with rumble.profile). > - Videos whose comments Rumble shows only to signed-in viewers or buyers return private_or_unavailable. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `url` required | `string`
max 2,048 chars | Rumble video, short or live stream URL (rumble.com/v…-title.html, /shorts/v…, /share/v…, /embed/v…/) or a video ID (v…). | `https://rumble.com/v79xhhm-discovery-why-glenn-wants-israel-to-sue-the-new-york-times.html` | | `sort` optional | `string` | Order of the results. newest (default): every comment and reply, newest first. popular: Rumble's Sort by likes order of comments, each followed by its replies.
One of `newest`, `popular` | — | | `since` optional | `string`
date-time | Only items at or after this time: RFC 3339 (2026-10-01T12:00:00Z) or a date (2026-10-01, midnight UTC). On newest-first lists, paging stops when it is reached, so later pages are not fetched or charged. Compared with published_at, which Rumble shows to the minute. Replies count as items: on newest the walk stops at the first comment or reply older than since. | `2026-10-01` | | `until` optional | `string`
date-time | Only items before this time (exclusive): RFC 3339 or a date (midnight UTC). | `2026-10-08` | `url` takes Rumble video links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). `limit` and `cursor` page through the list: see Pagination. ## Response ### 200 `{data, page, meta}`. `data` is an array of up to 500 [comment](https://staging-fly.socialtap.dev/resources/comment/index.md) objects, newest first; `page` says how to get the next page; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). Fields only Rumble has are under `platform_specific.rumble`. Fields: the [comment schema](https://staging-fly.socialtap.dev/resources/comment/index.md), with `platform_specific.rumble`. not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `invalid_cursor` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pagination Items come in pages, **newest first**. - **Order**: `sort=newest` (the default): newest first; `sort=popular`: Rumble's Sort by likes order of comments, each followed by its replies - **Page size**: Up to 500 comments (the default; `limit` lowers it) - **Cursor lifetime**: 24 hours; don't store cursors - **Price**: Per page, 1 credit [Pagination guide](https://staging-fly.socialtap.dev/docs/pagination/index.md) ## Pricing | Source | Price per successful page | Per 1,000 pages | | --- | --- | --- | | Platform API `api` | 1 credit | $0.75–$1.45 | Endpoint ID `rumble.comments` · operationId `rumble.comments.list` · [Product overview](https://staging-fly.socialtap.dev/apis/rumble/comments/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/rumble/post/ # Rumble Video API Rumble Beta Returns a public Rumble video, short or live stream: title, full description, publish time, duration, thumbnail, the channel with its avatar, badge and follower count, views, likes and dislikes, Rumble's categories and tags, every video and audio file with its size, the caption tracks, and for live streams whether they are on air, when they started or are scheduled, and how many people are watching. Accepts every Rumble video link form, embed links included, or a bare video ID. GET `/v1/rumble/post` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/rumble/post' \ --data-urlencode 'url=https://rumble.com/v79xhhm-discovery-why-glenn-wants-israel-to-sue-the-new-york-times.html' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":{"approximate_fields":["platform_specific.rumble.author_follower_count"],"author":{"avatar_url":"https://hugh.cdn.rumble.cloud/video/z8/g/D/5/d/gD5da.baa.2-GGreenwald-rml416.png","display_name":"Glenn Greenwald","id":"_c1021766","is_verified":true,"url":"https://rumble.com/c/GGreenwald","username":"GGreenwald"},"coauthors":[],"duration_ms":1678000,"hashtags":[],"id":"v79xhhm","is_nsfw":null,"is_pinned":null,"is_sponsored":null,"language":null,"media":[{"alt_text":null,"duration_ms":1678000,"expires_at":null,"height":1080,"thumbnail_url":"https://hugh.cdn.rumble.cloud/video/fwe2/2f/s8/1/k/V/p/o/kVp…","type":"video","url":"https://rumble.com/hls-vod/vjXafkcCce8/playlist.m3u8","width":1920}],"mentions":[],"metrics":{"comment_count":22,"like_count":209,"quote_count":null,"repost_count":null,"save_count":null,"share_count":null,"view_count":25366},"platform":"rumble","platform_specific":{"rumble":{"author_follower_count":584000,"caption_tracks":[{"is_auto_generated":true,"label":"English (auto)","language":"en","url":"https://hugh.cdn.rumble.cloud/video/fwe2/2f/s8/11/k/V/p/o/kVpoA.fn8Si.1.vtt"}],"category":"News","concurrent_viewer_count":null,"dislike_count":4,"embed_id":"v77qthe","embed_url":"https://rumble.com/embed/v77qthe/","files":[{"bitrate_kbps":2142,"format":"tar","height":720,"kind":"video","size_bytes":449566720,"url":"https://hugh.cdn.rumble.cloud/video/fwe2/2f/s8/2/k/V/p/o/kVp…","width":1280},{"bitrate_kbps":1059,"format":"tar","height":480,"kind":"video","size_bytes":222300160,"url":"https://hugh.cdn.rumble.cloud/video/fwe2/2f/s8/2/k/V/p/o/kVp…","width":854}],"is_age_restricted":null,"is_live_now":false,"keywords":[],"live_started_at":null,"numeric_id":"436271810","scheduled_start_at":null,"secondary_category":"Politics"}},"platform_type":"video","published_at":"2026-05-16T11:31:27Z","text":"Follow Glenn Greenwald's writing and other journalism at Sub…","thumbnail_url":"https://hugh.cdn.rumble.cloud/video/fwe2/2f/s8/1/k/V/p/o/kVp…","title":"DISCOVERY: Why Glenn WANTS Israel to Sue The New York Times","type":"video","unknown_fields":[],"url":"https://rumble.com/v79xhhm-discovery-why-glenn-wants-israel-…"},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"rumble","source":"page","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"video","id":"v79xhhm","url":"https://rumble.com/v79xhhm-discovery-why-glenn-wants-israel-…"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - On a video's page Rumble cuts dislikes, comment and follower counts to three significant digits once they reach thousands (a cut label is a floor: the real count is at most one unit in its last digit higher; listed in approximate_fields); likes are made exact from the rating bar when the dislikes are exact; views, and every count of a short, are exact. > - metrics.comment_count on a video is the count its page shows, which can run below the total Rumble's own lists give for the same video; null when the page shows none. > - platform_specific.rumble.embed_id is the player's ID: /embed/ links use it, and it differs from id. > - platform_type is short, live (streams on air, scheduled or past) or video. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `url` required | `string`
max 2,048 chars | Rumble video, short or live stream URL (rumble.com/v…-title.html, /shorts/v…, /share/v…, /embed/v…/) or a video ID (v…). | `https://rumble.com/v79xhhm-discovery-why-glenn-wants-israel-to-sue-the-new-york-times.html` | `url` takes Rumble video links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). ## Response ### 200 `{data, meta}`. `data` is a [post](https://staging-fly.socialtap.dev/resources/post/index.md) object; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). Fields only Rumble has are under `platform_specific.rumble`. Fields: the [post schema](https://staging-fly.socialtap.dev/resources/post/index.md), with `platform_specific.rumble`. not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pricing | Source | Price per successful request | Per 1,000 requests | | --- | --- | --- | | Public page `page` | 1 credit | $0.75–$1.45 | Endpoint ID `rumble.post` · operationId `rumble.post.get` · [Product overview](https://staging-fly.socialtap.dev/apis/rumble/post/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/rumble/posts/ # Rumble Channel Videos API Rumble Beta Returns the public uploads of a Rumble channel or user a page at a time: its videos, its shorts or its live streams (on air now, scheduled and past), newest or most viewed first. Each carries its link, title, thumbnail, exact upload time and length, exact views, likes, dislikes, comments and reposts, the video file, the channel with its exact follower count and badge, and for streams whether they are on air, the viewer count and when they started or are due. Accepts a channel or user name, any channel or user link, or a channel's ID. GET `/v1/rumble/posts` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/rumble/posts' \ --data-urlencode 'handle=Bongino' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":[{"approximate_fields":[],"author":{"avatar_url":"https://hugh.cdn.rumble.cloud/video/z8/U/K/d/b/UKdba.baa.4-bongino-t8rqq7.jpeg","display_name":"The Dan Bongino Show","id":"_c276782","is_verified":true,"url":"https://rumble.com/c/bongino","username":"bongino"},"coauthors":[],"duration_ms":3233000,"hashtags":[],"id":"v7haadi","is_nsfw":false,"is_pinned":null,"is_sponsored":null,"language":null,"media":[{"alt_text":null,"duration_ms":3233000,"expires_at":null,"height":1080,"thumbnail_url":"https://hugh.cdn.rumble.cloud/video/fww1/25/s8/1/g/a/y/9/gay…","type":"video","url":"https://hugh.cdn.rumble.cloud/video/fww1/25/s8/2/g/a/y/9/gay9A.haa.mp4","width":1920}],"mentions":[],"metrics":{"comment_count":80,"like_count":1182,"quote_count":null,"repost_count":3,"save_count":null,"share_count":null,"view_count":207825},"platform":"rumble","platform_specific":{"rumble":{"author_follower_count":3673478,"caption_tracks":[],"category":null,"concurrent_viewer_count":null,"dislike_count":13,"embed_id":"v7f3xg0","embed_url":"https://rumble.com/embed/v7f3xg0/","files":[],"is_age_restricted":false,"is_live_now":false,"keywords":[],"live_started_at":null,"numeric_id":"448640784","scheduled_start_at":null,"secondary_category":null}},"platform_type":"video","published_at":"2026-10-24T11:01:00Z","text":null,"thumbnail_url":"https://hugh.cdn.rumble.cloud/video/fww1/25/s8/1/g/a/y/9/gay…","title":"Bongino Weekend Report - Week of 10/19/26","type":"video","unknown_fields":["hashtags","mentions"],"url":"https://rumble.com/v7haadi-bongino-weekend-report-week-of-101926.html"},{"approximate_fields":[],"author":{"avatar_url":"https://hugh.cdn.rumble.cloud/video/z8/U/K/d/b/UKdba.baa.4-bongino-t8rqq7.jpeg","display_name":"The Dan Bongino Show","id":"_c276782","is_verified":true,"url":"https://rumble.com/c/bongino","username":"bongino"},"coauthors":[],"duration_ms":3316000,"hashtags":[],"id":"v7gyjd4","is_nsfw":false,"is_pinned":null,"is_sponsored":null,"language":null,"media":[{"alt_text":null,"duration_ms":3316000,"expires_at":null,"height":1080,"thumbnail_url":"https://hugh.cdn.rumble.cloud/video/fww1/bd/s8/1/i/k/s/7/iks…","type":"video","url":"https://hugh.cdn.rumble.cloud/video/fww1/bd/s8/2/i/k/s/7/iks7A.haa.mp4","width":1920}],"mentions":[],"metrics":{"comment_count":119,"like_count":1170,"quote_count":null,"repost_count":2,"save_count":null,"share_count":null,"view_count":257009},"platform":"rumble","platform_specific":{"rumble":{"author_follower_count":3673478,"caption_tracks":[],"category":null,"concurrent_viewer_count":null,"dislike_count":25,"embed_id":"v7es6fm","embed_url":"https://rumble.com/embed/v7es6fm/","files":[],"is_age_restricted":false,"is_live_now":false,"keywords":[],"live_started_at":null,"numeric_id":"448092562","scheduled_start_at":null,"secondary_category":null}},"platform_type":"video","published_at":"2026-10-17T13:00:00Z","text":null,"thumbnail_url":"https://hugh.cdn.rumble.cloud/video/fww1/bd/s8/1/i/k/s/7/iks…","title":"Bongino Weekend Report - Week of 10/12/26","type":"video","unknown_fields":["hashtags","mentions"],"url":"https://rumble.com/v7gyjd4-bongino-weekend-report-week-of-101226.html"}],"page":{"has_more":true,"next_cursor":"c1.EXAMPLE_CURSOR","next_cursor_expires_at":"2026-10-26T12:00:00Z","end_reason":null},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"rumble","source":"page","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"profile","id":"_c276782","url":"https://rumble.com/c/bongino"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - tab=live lists Rumble's Livestreams tab: streams on air now (platform_specific.rumble.is_live_now, concurrent_viewer_count), scheduled ones (scheduled_start_at, no duration or views yet) and past ones. tab=videos holds the other uploads, tab=shorts the shorts. > - sort=newest is Rumble's upload order, newest first but not strictly by date: some older uploads sit among newer ones, so read published_at rather than the position. > - A user's tabs (user/\) list its own uploads and those of the channels it owns; each post's author says which account published it. > - List results carry no description: text is null and hashtags and mentions are listed in unknown_fields. Fetch the video (rumble.post) for those. > - A tab the account does not have (a channel without shorts) and an account without uploads answer an empty page. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `handle` required | `string`
max 2,048 chars | Rumble channel or user: a bare name (Bongino: a channel, else the user of that name), c/\ for a channel or user/\ for a user, any channel or user link with any tab after it (rumble.com/c/Bongino, rumble.com/c/Bongino/shorts, rumble.com/user/vivafrei/livestreams, the root rumble.com/Bongino link), or a channel's id as this API returns it (_c276782) or its c/c-\ name. A user's _u… id is refused: Rumble's public pages cannot look a user up by ID, so send its name. Names are case-insensitive. | `Bongino` | | `tab` optional | `string` | The tab to list: videos (default), shorts or live (Rumble's Livestreams tab: streams on air now, scheduled ones and past ones). A user's tabs include the videos of the channels it owns.
One of `videos`, `shorts`, `live` | `live` | | `sort` optional | `string` | Order of the results. newest (default): Rumble's upload order, newest uploads first (Rumble's own order, not strictly by published_at: older uploads can sit among newer ones). popular: Rumble's View count order (most viewed first).
One of `newest`, `popular` | — | | `since` optional | `string`
date-time | Only items at or after this time: RFC 3339 (2026-10-01T12:00:00Z) or a date (2026-10-01, midnight UTC). On newest-first lists, paging stops when it is reached, so later pages are not fetched or charged. Only posts published at or after this time. Neither sort is strictly by date, so since filters every page instead of ending the walk at the first older post: the walk ends after a few pages in a row with nothing in the window, and those pages are free. | `2026-10-01` | | `until` optional | `string`
date-time | Only items before this time (exclusive): RFC 3339 or a date (midnight UTC). Only posts published before this time; filters every page, like since. | `2026-10-08` | | `duration` optional | `string` | Only short videos (Rumble's Under 4 minutes) or long ones (Over 20 minutes); any length when omitted. Not with tab=shorts.
One of `short`, `long` | `long` | `handle` takes Rumble profile links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). `limit` and `cursor` page through the list: see Pagination. ## Response ### 200 `{data, page, meta}`. `data` is an array of up to 25 [post](https://staging-fly.socialtap.dev/resources/post/index.md) objects, Rumble's upload order (newest uploads first, not strictly by published_at); `page` says how to get the next page; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). Fields only Rumble has are under `platform_specific.rumble`. Fields: the [post schema](https://staging-fly.socialtap.dev/resources/post/index.md), with `platform_specific.rumble`. not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `invalid_cursor` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pagination Items come in pages, **Rumble's upload order (newest uploads first, not strictly by published_at)**. - **Order**: `sort=newest` (the default): Rumble's upload order (newest uploads first, not strictly by published_at); `sort=popular`: Rumble's View count order (most viewed first) - **Page size**: Up to 25 posts (the default; `limit` lowers it) - **Cursor lifetime**: 1 hour; don't store cursors - **Price**: Per page, 1 credit [Pagination guide](https://staging-fly.socialtap.dev/docs/pagination/index.md) ## Pricing | Source | Price per successful page | Per 1,000 pages | | --- | --- | --- | | Any source | 1 credit | $0.75–$1.45 | Endpoint ID `rumble.posts` · operationId `rumble.posts.list` · [Product overview](https://staging-fly.socialtap.dev/apis/rumble/posts/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/rumble/profile/ # Rumble Channel API Rumble Beta Returns a public Rumble channel or user: Rumble's own ID for it (the one Rumble's video data names the uploader by) and its name, display name, description, avatar, banner, verification badge, the social links it lists with their labels, its join date, follower count and exact video and lifetime view counts, and which kind of account it is with the ID of the user who owns it. Accepts a channel or user name, any channel or user link with any tab, or a channel's ID. GET `/v1/rumble/profile` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/rumble/profile' \ --data-urlencode 'handle=Bongino' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":{"approximate_fields":["metrics.follower_count","created_at"],"avatar_url":"https://hugh.cdn.rumble.cloud/video/z8/U/K/d/b/UKdba.baa.4-bongino-t8rqq7.jpeg","banner_url":"https://hugh.cdn.rumble.cloud/video/z8/U/K/d/b/UKdba.caa-bongino-tbhrxo.png","bio":"The official channel for The Dan Bongino Show®. A former Dep…","category":null,"country_code":null,"created_at":"2020-09-16T00:00:00Z","display_name":"The Dan Bongino Show","external_links":[{"title":"Twitter","url":"https://twitter.com/dbongino"},{"title":"Facebook","url":"https://facebook.com/danbongino"}],"id":"_c276782","is_private":false,"is_verified":true,"location":null,"metrics":{"follower_count":3670000,"following_count":null,"post_count":2136,"total_like_count":null,"total_view_count":389354965},"platform":"rumble","platform_specific":{"rumble":{"account_type":"channel","numeric_id":"276782","user_id":"_u169h4"}},"type":"unknown","unknown_fields":[],"url":"https://rumble.com/c/bongino","username":"bongino"},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"rumble","source":"page","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"profile","id":"_c276782","url":"https://rumble.com/c/bongino"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - Rumble has channels (rumble.com/c/\) and users (rumble.com/user/\, the uploader account that owns channels). A bare name is looked up as a channel first, then as a user; send c/\ or user/\ to choose. platform_specific.rumble.account_type says which one answered. > - id is Rumble's own reference: _c\ for a channel, _u\ for a user, the same string rumble.post and rumble.search_posts return as author.id. Channels and users are numbered separately (platform_specific.rumble.numeric_id), so only id is unambiguous. A channel's id is accepted as handle; a user's is not (send its name). > - username is a channel's name as Rumble spells it, or user/\ for a user (a bare name is looked up as a channel first), the same as author.username on rumble.post and rumble.search_posts, so it can be sent back as handle. > - metrics.follower_count is the count the page header shows: from a thousand up it is cut to three significant figures (a floor: it can be under the real count, never over; listed in approximate_fields). Video and view counts are exact. > - created_at is the start, in UTC, of the day Rumble shows as the join date (listed in approximate_fields): Rumble shows the day only. > - type is unknown, location, country_code and category are null, and metrics.following_count and metrics.total_like_count are null: Rumble does not show them. is_private is false: Rumble has no private accounts. > - Names that are no channel or user answer not_found. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `handle` required | `string`
max 2,048 chars | Rumble channel or user: a bare name (Bongino: a channel, else the user of that name), c/\ for a channel or user/\ for a user, any channel or user link with any tab after it (rumble.com/c/Bongino, rumble.com/c/Bongino/videos, rumble.com/user/vivafrei/about, the root rumble.com/Bongino link), or a channel's id as this API returns it (_c276782) or its c/c-\ name (c/c-276782, which every channel has). A user's _u… id is refused: Rumble's public pages cannot look a user up by ID, so send its name. Names are case-insensitive. | `Bongino` | `handle` takes Rumble profile links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). ## Response ### 200 `{data, meta}`. `data` is a [profile](https://staging-fly.socialtap.dev/resources/profile/index.md) object; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). Fields only Rumble has are under `platform_specific.rumble`. Fields: the [profile schema](https://staging-fly.socialtap.dev/resources/profile/index.md), with `platform_specific.rumble`. not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pricing | Source | Price per successful request | Per 1,000 requests | | --- | --- | --- | | Public page `page` | 1 credit | $0.75–$1.45 | Endpoint ID `rumble.profile` · operationId `rumble.profile.get` · [Product overview](https://staging-fly.socialtap.dev/apis/rumble/profile/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/rumble/search_posts/ # Rumble Search API Rumble Beta Searches Rumble's public videos and live streams, or its shorts, by keyword and returns them a page at a time: each video's link, title, thumbnail, exact upload time, real length, views, likes, dislikes and comments, its channel with the verification badge, and whether a stream is on air with its viewer count. Shorts also carry exact counts, the channel's follower count and the video file. Sort by relevance, newest or most viewed, and filter by upload date and length with Rumble's own filters. GET `/v1/rumble/search_posts` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/rumble/search_posts' \ --data-urlencode 'query=cats' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":[{"approximate_fields":[],"author":{"avatar_url":null,"display_name":"ViralHog","id":"_u7bbl","is_verified":false,"url":"https://rumble.com/user/ViralHog","username":"user/ViralHog"},"coauthors":[],"duration_ms":28000,"hashtags":[],"id":"v7hfhey","is_nsfw":null,"is_pinned":null,"is_sponsored":null,"language":null,"media":[{"alt_text":null,"duration_ms":28000,"expires_at":null,"height":null,"thumbnail_url":"https://hugh.cdn.rumble.cloud/video/fww1/39/s8/6/Q/l/t/-/Qlt-A.oq1b.jpg","type":"video","url":null,"width":null}],"mentions":[],"metrics":{"comment_count":null,"like_count":3,"quote_count":null,"repost_count":null,"save_count":null,"share_count":null,"view_count":254},"platform":"rumble","platform_specific":{"rumble":{"author_follower_count":null,"caption_tracks":[],"category":null,"concurrent_viewer_count":null,"dislike_count":0,"embed_id":null,"embed_url":null,"files":[],"is_age_restricted":null,"is_live_now":false,"keywords":[],"live_started_at":null,"numeric_id":null,"scheduled_start_at":null,"secondary_category":null}},"platform_type":"video","published_at":"2026-10-26T19:34:46Z","text":null,"thumbnail_url":"https://hugh.cdn.rumble.cloud/video/fww1/39/s8/6/Q/l/t/-/Qlt-A.oq1b.jpg","title":"Orange Cat Sits Like Human On Fuzzy Couch","type":"video","unknown_fields":["hashtags","mentions"],"url":"https://rumble.com/v7hfhey-orange-cat-sits-like-human-on-fuzzy-couch.html"},{"approximate_fields":[],"author":{"avatar_url":null,"display_name":"SereneChristine","id":"_urv80h","is_verified":false,"url":"https://rumble.com/user/SereneChristine","username":"user/SereneChristine"},"coauthors":[],"duration_ms":932000,"hashtags":[],"id":"v7hdue2","is_nsfw":null,"is_pinned":null,"is_sponsored":null,"language":null,"media":[{"alt_text":null,"duration_ms":932000,"expires_at":null,"height":null,"thumbnail_url":"https://hugh.cdn.rumble.cloud/video/fwe2/1e/s8/1/A/G/a/-/AGa…","type":"video","url":null,"width":null}],"mentions":[],"metrics":{"comment_count":null,"like_count":4,"quote_count":null,"repost_count":null,"save_count":null,"share_count":null,"view_count":400},"platform":"rumble","platform_specific":{"rumble":{"author_follower_count":null,"caption_tracks":[],"category":null,"concurrent_viewer_count":null,"dislike_count":1,"embed_id":null,"embed_url":null,"files":[],"is_age_restricted":null,"is_live_now":false,"keywords":[],"live_started_at":null,"numeric_id":null,"scheduled_start_at":null,"secondary_category":null}},"platform_type":"video","published_at":"2026-10-25T20:04:50Z","text":null,"thumbnail_url":"https://hugh.cdn.rumble.cloud/video/fwe2/1e/s8/1/A/G/a/-/AGa…","title":"The Book of Enoch Reveals Why Cats Were Sent to Earth — And …","type":"video","unknown_fields":["hashtags","mentions"],"url":"https://rumble.com/v7hdue2-the-book-of-enoch-reveals-why-cat…"}],"page":{"has_more":true,"next_cursor":"c1.EXAMPLE_CURSOR","next_cursor_expires_at":"2026-10-26T12:00:00Z","end_reason":null},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"rumble","source":"page","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":null,"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - Video results show Rumble's abbreviated counts (2.42K: cut, not rounded, to three significant digits, so a slight undercount): those values are in approximate_fields. A count a result does not show (no votes, no comments yet) is null. Shorts carry exact counts. > - Rumble serves a limited number of result pages per search; a walk that reaches it ends with end_reason platform_limit. > - Search results carry no description: text is null and hashtags and mentions are listed in unknown_fields. Fetch the video for those. > - Rumble's search matches Latin-script keywords: a query in Cyrillic, Arabic, Hebrew or CJK script finds nothing. Queries its firewall reads as SQL or script injection are refused with invalid_input. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `query` required | `string`
max 512 chars | Keywords, matched by Rumble's search against titles and channels. | `cats` | | `tab` optional | `string` | The search tab to list: videos (default; videos and live streams) or shorts.
One of `videos`, `shorts` | `shorts` | | `sort` optional | `string` | Order of the results. relevance (default): Rumble's best match. newest: newest uploads first. popular: Rumble's View count order (most viewed first).
One of `relevance`, `newest`, `popular` | — | | `since` optional | `string`
date-time | Only items at or after this time: RFC 3339 (2026-10-01T12:00:00Z) or a date (2026-10-01, midnight UTC). On newest-first lists, paging stops when it is reached, so later pages are not fetched or charged. Rumble's upload-date filter is applied when it covers since (the last day, week, month or year), so fewer pages are fetched. | `2026-10-01` | | `until` optional | `string`
date-time | Only items before this time (exclusive): RFC 3339 or a date (midnight UTC). | `2026-10-08` | | `duration` optional | `string` | Only short videos (Rumble's Under 4 minutes) or long ones (Over 20 minutes). Any length when omitted.
One of `short`, `long` | `long` | `limit` and `cursor` page through the list: see Pagination. ## Response ### 200 `{data, page, meta}`. `data` is an array of up to 36 [post](https://staging-fly.socialtap.dev/resources/post/index.md) objects, Rumble's relevance order; `page` says how to get the next page; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). Fields only Rumble has are under `platform_specific.rumble`. Fields: the [post schema](https://staging-fly.socialtap.dev/resources/post/index.md), with `platform_specific.rumble`. not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `invalid_cursor` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pagination Items come in pages, **Rumble's relevance order**. - **Order**: `sort=relevance` (the default): Rumble's relevance order; `sort=newest`: newest first; `sort=popular`: Rumble's View count order (most viewed first) - **Page size**: Up to 36 posts (the default; `limit` lowers it) - **Cursor lifetime**: 1 hour; don't store cursors - **Price**: Per page, 1 credit [Pagination guide](https://staging-fly.socialtap.dev/docs/pagination/index.md) ## Pricing | Source | Price per successful page | Per 1,000 pages | | --- | --- | --- | | Public page `page` | 1 credit | $0.75–$1.45 | Endpoint ID `rumble.search_posts` · operationId `rumble.search_posts.list` · [Product overview](https://staging-fly.socialtap.dev/apis/rumble/search_posts/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/rumble/transcript/ # Rumble Transcript API Rumble Beta Returns the timed transcript of a public Rumble video, short or past live stream: the uploader's caption track when there is one, otherwise Rumble's automatic captions, as segments with start and end times and the full text, in the language you ask for when Rumble has it. Each answer says which track it used, links its WebVTT file and lists every track the video has. Videos without captions are transcribed from their audio with speech-to-text. Accepts every Rumble video link form, embed links included, or a bare video ID. GET `/v1/rumble/transcript` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/rumble/transcript' \ --data-urlencode 'url=https://rumble.com/v79xhhm-discovery-why-glenn-wants-israel-to-sue-the-new-york-times.html' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":{"available_languages":["en"],"duration_ms":1678000,"id":"v79xhhm","is_auto_generated":true,"language":"en","platform":"rumble","platform_specific":{"rumble":{"caption_tracks":[{"is_auto_generated":true,"label":"English (auto)","language":"en","url":"https://hugh.cdn.rumble.cloud/video/fwe2/2f/s8/11/k/V/p/o/kVpoA.fn8Si.1.vtt"}],"caption_url":"https://hugh.cdn.rumble.cloud/video/fwe2/2f/s8/11/k/V/p/o/kVpoA.fn8Si.1.vtt","track_label":"English (auto)"}},"segments":[{"end_ms":11030,"start_ms":6830,"text":"Let's get into these questions, and we'll try and get through as many as we can."},{"end_ms":15630,"start_ms":11030,"text":"The first one is from Christi Marbet, who says there were a …"}],"source":"captions","text":"Let's get into these questions, and we'll try and get throug…","url":"https://rumble.com/v79xhhm-discovery-why-glenn-wants-israel-…"},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"rumble","source":"captions","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"video","id":"v79xhhm","url":"https://rumble.com/v79xhhm-discovery-why-glenn-wants-israel-…"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - A stream that is on air now, or has not started, has no transcript yet (no_transcript); Rumble captions it once it ends. > - is_auto_generated is true for Rumble's automatic captions and for speech-to-text; platform_specific.rumble.track_label is Rumble's own label for the track used. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `url` required | `string`
max 2,048 chars | Rumble video, short or live stream URL (rumble.com/v…-title.html, /shorts/v…, /share/v…, /embed/v…/) or a video ID (v…). | `https://rumble.com/v79xhhm-discovery-why-glenn-wants-israel-to-sue-the-new-york-times.html` | | `lang` optional | `string`
bcp47 | Preferred language: a BCP-47 tag such as en or pt-BR (case-insensitive; _ is read as -). Selects the caption track: an uploaded track in that language before Rumble's automatic one. When the video has no track in that language, the default track is returned (language says which). Default: an uploaded track in the spoken language, else Rumble's automatic captions. | `en` | `url` takes Rumble video links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). ## Response ### 200 `{data, meta}`. `data` is a [transcript](https://staging-fly.socialtap.dev/resources/transcript/index.md) object; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). Fields only Rumble has are under `platform_specific.rumble`. Fields: the [transcript schema](https://staging-fly.socialtap.dev/resources/transcript/index.md), with `platform_specific.rumble`. not_found, private_or_unavailable and no_transcript are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `no_transcript` 404, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pricing | Source | Price per successful request | Per 1,000 requests | | --- | --- | --- | | Captions `captions` | 1 credit | $0.75–$1.45 | | Speech-to-text `asr` | 2 credits + 2 per started minute of media
up to 122 credits (60 started minutes of media) | $1.50–$2.90 + $1.50–$2.90 per started minute of media | Example: a result from Speech-to-text `asr` with 3 started minutes of media costs 2 + 2 × 3 = **8 credits**. Endpoint ID `rumble.transcript` · operationId `rumble.transcript.get` · [Product overview](https://staging-fly.socialtap.dev/apis/rumble/transcript/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/threads/profile/ # Threads Profile API Threads Beta Returns a public Threads account: user ID, username, display name, bio, the links in its bio with their titles, the largest profile picture, verification badge, whether the account is private, and the exact follower count, plus the topic labels Threads shows on the profile and which bio links Threads marks verified. Accepts a username with or without the @, the account's numeric id, or any threads.com or threads.net profile link. GET `/v1/threads/profile` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/threads/profile' \ --data-urlencode 'handle=https://www.threads.com/@zuck' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":{"approximate_fields":[],"avatar_url":"https://scontent-atl3-1.cdninstagram.com/v/t51.82787-19/8253…","banner_url":null,"bio":"Mostly superintelligence and MMA takes","category":null,"country_code":null,"created_at":null,"display_name":"Mark Zuckerberg","external_links":[],"id":"63055343223","is_private":false,"is_verified":true,"location":null,"metrics":{"follower_count":5751366,"following_count":null,"post_count":null,"total_like_count":null,"total_view_count":null},"platform":"threads","platform_specific":{"threads":{"interest_tags":["AI Threads","UFC Threads"],"verified_link_urls":[]}},"type":"unknown","unknown_fields":[],"url":"https://www.threads.com/@zuck","username":"zuck"},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"threads","source":"api","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"profile","id":"63055343223","url":"https://www.threads.com/@zuck"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - Private accounts return their public header with is_private true. > - metrics.following_count, metrics.post_count, metrics.total_like_count, metrics.total_view_count, created_at, banner_url, category, location and country_code are null, and type is unknown: Threads does not show them to logged-out visitors. > - Whether an account exists only on Threads is not available logged out. > - avatar_url is a Threads CDN link and can expire. > - If Threads returns only part of a profile, is_private, external_links, interest_tags and verified_link_urls are left empty and the rest is returned, with a partial_data warning. > - An account Threads does not show to logged-out visitors answers not_found, the same as one that does not exist. A post link is refused. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `handle` required | `string`
max 2,048 chars | Threads username with or without the @ (zuck, @zuck), the account's numeric id, or a profile link (threads.com/@zuck, threads.net/@zuck, with or without a tab such as /replies, /reposts or /media, and with any query such as ?hl=es). Usernames are case-insensitive. A number of 5 or more digits is an account id; @ before it is a username. | `zuck` | `handle` takes Threads profile links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). ## Response ### 200 `{data, meta}`. `data` is a [profile](https://staging-fly.socialtap.dev/resources/profile/index.md) object; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). Fields only Threads has are under `platform_specific.threads`. Fields: the [profile schema](https://staging-fly.socialtap.dev/resources/profile/index.md), with `platform_specific.threads`. not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pricing | Source | Price per successful request | Per 1,000 requests | | --- | --- | --- | | Platform API `api` | 1 credit | $0.75–$1.45 | | Public page `page` | 1 credit | $0.75–$1.45 | Endpoint ID `threads.profile` · operationId `threads.profile.get` · [Product overview](https://staging-fly.socialtap.dev/apis/threads/profile/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/tiktok/comments/ # TikTok Comments API TikTok Beta Returns the public top-level comments of a TikTok video or photo post, a page at a time, in TikTok's own ranking: text, exact posting time, exact like and reply counts, the commenter's username, name, ID and verified badge, the comment's language, and whether it is pinned, liked by the creator or written by the creator. Follow page.next_cursor for more. Accepts tiktok.com video and photo links, mobile, embed and player URLs, vm./vt./t/ share links, or a numeric video ID. GET `/v1/tiktok/comments` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/tiktok/comments' \ --data-urlencode 'url=https://www.tiktok.com/@scout2015/video/6718335390845095173' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":[{"approximate_fields":[],"author":{"avatar_url":"https://p19-common-sign.tiktokcdn-us.com/tos-maliva-avt-0068…","display_name":"flamigoo","id":"6650133637952618502","is_verified":false,"url":"https://www.tiktok.com/@flamigo_kisses","username":"flamigo_kisses"},"edited_at":null,"id":"6718335906996502534","is_by_post_author":false,"is_edited":null,"is_liked_by_author":false,"is_pinned":false,"metrics":{"like_count":166,"reply_count":49},"parent_id":null,"platform":"tiktok","platform_specific":{"tiktok":{"language":null}},"post_id":"6718335390845095173","published_at":"2019-07-27T13:34:35Z","text":"ok ;)\nodoreta\nhave fun~\n(it's a Serbian name)","unknown_fields":[],"url":"https://m.tiktok.com/v/6718335390845095173.html?share_commen…"},{"approximate_fields":[],"author":{"avatar_url":"https://p19-common-sign.tiktokcdn-us.com/tos-maliva-avt-0068…","display_name":"vsco_room_xx","id":"6718326650415203333","is_verified":false,"url":"https://www.tiktok.com/@vsco_room_xx","username":"vsco_room_xx"},"edited_at":null,"id":"6722043930156417030","is_by_post_author":false,"is_edited":null,"is_liked_by_author":false,"is_pinned":false,"metrics":{"like_count":53,"reply_count":44},"parent_id":null,"platform":"tiktok","platform_specific":{"tiktok":{"language":null}},"post_id":"6718335390845095173","published_at":"2019-08-06T13:23:37Z","text":"srii good luck","unknown_fields":[],"url":"https://m.tiktok.com/v/6718335390845095173.html?share_commen…"}],"page":{"has_more":true,"next_cursor":"c1.EXAMPLE_CURSOR","next_cursor_expires_at":"2026-10-26T12:00:00Z","end_reason":null},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"tiktok","source":"api","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"video","id":"6718335390845095173","url":"https://www.tiktok.com/@/video/6718335390845095173"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - TikTok ranks comments itself and has no newest-first order, so since and until are not offered; published_at is exact for filtering on your side. > - Pages can hold fewer comments than limit while more follow, and TikTok repeats some comments across the first pages of a busy video: deduplicate by id. > - Replies are not included; metrics.reply_count says how many a comment has. > - Private videos and videos with comments turned off return private_or_unavailable; deleted videos return not_found, though TikTok keeps serving the comments of some deleted videos, which are then returned. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `url` required | `string`
max 2,048 chars | TikTok video or photo post URL (tiktok.com/@user/video/\, /@user/photo/\, m.tiktok.com/v/\.html, embed and player URLs, vm.tiktok.com/\, vt.tiktok.com/\, tiktok.com/t/\) or a numeric video ID. | `https://www.tiktok.com/@scout2015/video/6718335390845095173` | `url` takes TikTok video and post links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). `limit` and `cursor` page through the list: see Pagination. ## Response ### 200 `{data, page, meta}`. `data` is an array of up to 50 [comment](https://staging-fly.socialtap.dev/resources/comment/index.md) objects, TikTok's comment ranking (the order its app shows); `page` says how to get the next page; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). Fields only TikTok has are under `platform_specific.tiktok`. Fields: the [comment schema](https://staging-fly.socialtap.dev/resources/comment/index.md), with `platform_specific.tiktok`. not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `invalid_cursor` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pagination Items come in pages, **TikTok's comment ranking (the order its app shows)**. - **Order**: TikTok's comment ranking (the order its app shows) - **Page size**: Up to 50 comments (the default; `limit` lowers it) - **Cursor lifetime**: 24 hours; don't store cursors - **Price**: Per page, 1 credit [Pagination guide](https://staging-fly.socialtap.dev/docs/pagination/index.md) ## Pricing | Source | Price per successful page | Per 1,000 pages | | --- | --- | --- | | Platform API `api` | 1 credit | $0.75–$1.45 | Endpoint ID `tiktok.comments` · operationId `tiktok.comments.list` · [Product overview](https://staging-fly.socialtap.dev/apis/tiktok/comments/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/tiktok/post/ # TikTok Post API TikTok Beta Returns a public TikTok video or photo post: caption, hashtags, tagged accounts, author, publish time, a cookie-free video link or every slideshow photo, cover, sound, tagged place, ad and AI labels, and view, like, comment, share and save counts, from a post or share link or a numeric ID. GET `/v1/tiktok/post` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/tiktok/post' \ --data-urlencode 'url=https://www.tiktok.com/@scout2015/video/6718335390845095173' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":{"approximate_fields":["duration_ms","media[].duration_ms"],"author":{"avatar_url":"https://p16-common-sign.tiktokcdn-us.com/tos-useast8-avt-006…","display_name":"Scout, Suki & Stella","id":"53279706535428096","is_verified":true,"url":"https://www.tiktok.com/@scout2015","username":"scout2015"},"coauthors":[],"duration_ms":10000,"hashtags":["foryoupage","petsoftiktok"],"id":"6718335390845095173","is_nsfw":false,"is_pinned":null,"is_sponsored":null,"language":"en","media":[{"alt_text":null,"duration_ms":10000,"expires_at":"2026-10-25T18:00:00Z","height":1024,"thumbnail_url":"https://p19-common-sign.tiktokcdn-us.com/tos-maliva-p-0068/2…","type":"video","url":"https://v19.tiktokcdn-us.com/SCRUBBED/f2a52380/video/tos/use…","width":576}],"mentions":[],"metrics":{"comment_count":5716,"like_count":35200,"quote_count":null,"repost_count":null,"save_count":649,"share_count":1518,"view_count":159700},"platform":"tiktok","platform_specific":{"tiktok":{"author_sec_uid":"MS4wLjABAAAAPZWNdkF_cmVSPCvV0Y6MCsH29mlAOlMOX3ikzGvlfMm3K6OMZv-JrRImpEHxdIMI","country_code":"US","is_ad":false,"is_ai_generated":false,"location":null,"music":{"author_name":"tiff","cover_url":"https://p19-common-sign.tiktokcdn-us.com/tos-maliva-avt-0068…","duration_ms":10000,"id":"6689804660171082501","is_original":true,"play_url":"https://v19.tiktokcdn-us.com/SCRUBBED/f2a52380/video/tos/use…","title":"original sound"}}},"platform_type":"video","published_at":"2019-07-27T13:32:38Z","text":"Scramble up ur name & I’ll try to guess it😍❤️ #foryoupage #…","thumbnail_url":"https://p19-common-sign.tiktokcdn-us.com/tos-maliva-p-0068/2…","title":null,"type":"video","unknown_fields":["coauthors"],"url":"https://www.tiktok.com/@scout2015/video/6718335390845095173"},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"tiktok","source":"page","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"video","id":"6718335390845095173","url":"https://www.tiktok.com/@scout2015/video/6718335390845095173"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - TikTok rounds large like and view counts (see approximate_fields); comments, shares and saves are exact. > - If the video page fails, the embed page answers without saves, language, location, AI label and sound length (partial_data warning). > - is_sponsored marks TikTok ads only (else null): paid partnerships are not public. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `url` required | `string`
max 2,048 chars | TikTok video or photo post link (web, mobile, embed or share link) or a numeric post ID. | `https://www.tiktok.com/@niinoclock/video/7673108027645414678` | `url` takes TikTok video and post links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). ## Response ### 200 `{data, meta}`. `data` is a [post](https://staging-fly.socialtap.dev/resources/post/index.md) object; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). Fields only TikTok has are under `platform_specific.tiktok`. Fields: the [post schema](https://staging-fly.socialtap.dev/resources/post/index.md), with `platform_specific.tiktok`. not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pricing | Source | Price per successful request | Per 1,000 requests | | --- | --- | --- | | Public page `page` | 1 credit | $0.75–$1.45 | Endpoint ID `tiktok.post` · operationId `tiktok.post.get` · [Product overview](https://staging-fly.socialtap.dev/apis/tiktok/post/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/tiktok/posts/ # TikTok Posts API TikTok Beta Returns a TikTok account's public videos and photo posts, newest first, with caption, hashtags, mentions, publish time, duration, cover, photos and engagement counts. GET `/v1/tiktok/posts` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/tiktok/posts' \ --data-urlencode 'handle=@khaby.lame' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":[{"approximate_fields":["metrics.view_count","metrics.like_count"],"author":{"avatar_url":"https://p16-common-sign.tiktokcdn-us.com/tos-useast8-avt-006…","display_name":"Khabane lame","id":"127905465618821121","is_verified":true,"url":"https://www.tiktok.com/@khaby.lame","username":"khaby.lame"},"coauthors":[],"duration_ms":9000,"hashtags":["learnfromkhaby","comedy"],"id":"7697307919943060767","is_nsfw":null,"is_pinned":null,"is_sponsored":null,"language":"en","media":[{"alt_text":null,"duration_ms":9000,"expires_at":null,"height":1280,"thumbnail_url":"https://p16-common-sign.tiktokcdn-us.com/tos-useast8-p-0068-…","type":"video","url":null,"width":720}],"mentions":[],"metrics":{"comment_count":7503,"like_count":305900,"quote_count":null,"repost_count":null,"save_count":16282,"share_count":32600,"view_count":4600000},"platform":"tiktok","platform_specific":{"tiktok":{"author_sec_uid":"MS4wLjABAAAAwAg0rSzO65WQfz4RzQgGv2Xdv108BgPXhRrrmNVIHQZ9PO8-flwwRtEppYTS0OjA","country_code":null,"is_ad":false,"is_ai_generated":true,"location":null,"music":{"author_name":"Khabane lame","cover_url":"https://p16-common-sign.tiktokcdn-us.com/tos-useast8-avt-006…","duration_ms":9000,"id":"7697308007327730462","is_original":true,"play_url":"https://v16-webapp-prime.us.tiktok.com/video/tos/useast8/tos…","title":"original sound"}}},"platform_type":"video","published_at":"2026-10-16T16:46:21Z","text":"I guess is time to retire #learnfromkhaby #comedy 🥊","thumbnail_url":"https://p16-common-sign.tiktokcdn-us.com/tos-useast8-p-0068-…","title":null,"type":"video","unknown_fields":[],"url":"https://www.tiktok.com/@khaby.lame/video/7697307919943060767"},{"approximate_fields":["metrics.view_count","metrics.like_count"],"author":{"avatar_url":"https://p16-common-sign.tiktokcdn-us.com/tos-useast8-avt-006…","display_name":"Khabane lame","id":"127905465618821121","is_verified":true,"url":"https://www.tiktok.com/@khaby.lame","username":"khaby.lame"},"coauthors":[],"duration_ms":30000,"hashtags":["learnfromkhaby","comedy"],"id":"7694335322922044703","is_nsfw":null,"is_pinned":null,"is_sponsored":null,"language":"en","media":[{"alt_text":null,"duration_ms":30000,"expires_at":null,"height":1280,"thumbnail_url":"https://p19-common-sign.tiktokcdn-us.com/tos-useast8-p-0068-…","type":"video","url":null,"width":720}],"mentions":[],"metrics":{"comment_count":12600,"like_count":690100,"quote_count":null,"repost_count":null,"save_count":40145,"share_count":20800,"view_count":6900000},"platform":"tiktok","platform_specific":{"tiktok":{"author_sec_uid":"MS4wLjABAAAAwAg0rSzO65WQfz4RzQgGv2Xdv108BgPXhRrrmNVIHQZ9PO8-flwwRtEppYTS0OjA","country_code":null,"is_ad":false,"is_ai_generated":true,"location":null,"music":{"author_name":"Khabane lame","cover_url":"https://p16-common-sign.tiktokcdn-us.com/tos-useast8-avt-006…","duration_ms":30000,"id":"7694335437137087262","is_original":true,"play_url":"https://v16-webapp-prime.us.tiktok.com/video/tos/useast8/tos…","title":"original sound"}}},"platform_type":"video","published_at":"2026-10-08T16:31:16Z","text":"Was the ball out, or was it just my imagination? 🤨#learnfromkhaby #comedy","thumbnail_url":"https://p19-common-sign.tiktokcdn-us.com/tos-useast8-p-0068-…","title":null,"type":"video","unknown_fields":[],"url":"https://www.tiktok.com/@khaby.lame/video/7694335322922044703"}],"page":{"has_more":true,"next_cursor":"c1.EXAMPLE_CURSOR","next_cursor_expires_at":"2026-10-26T12:00:00Z","end_reason":null},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"tiktok","source":"api","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"profile","id":"127905465618821121","url":"https://www.tiktok.com/@khaby.lame"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - Large play, like, comment and share counts are rounded by TikTok (see approximate_fields). > - media\[\].url is null for videos: TikTok serves video files only with its player's cookie. > - If TikTok's list is down, the first page lists the latest posts from its embed profile, with fewer fields. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `handle` required | `string`
max 2,048 chars | TikTok username with or without the @, a profile URL (tiktok.com/@nasa) or a numeric user ID. | `nasa` | | `since` optional | `string`
date-time | Only items at or after this time: RFC 3339 (2026-10-01T12:00:00Z) or a date (2026-10-01, midnight UTC). On newest-first lists, paging stops when it is reached, so later pages are not fetched or charged. | `2026-10-01` | | `until` optional | `string`
date-time | Only items before this time (exclusive): RFC 3339 or a date (midnight UTC). | `2026-10-08` | `handle` takes TikTok profile links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). `limit` and `cursor` page through the list: see Pagination. ## Response ### 200 `{data, page, meta}`. `data` is an array of up to 15 [post](https://staging-fly.socialtap.dev/resources/post/index.md) objects, newest first; `page` says how to get the next page; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). Fields only TikTok has are under `platform_specific.tiktok`. Fields: the [post schema](https://staging-fly.socialtap.dev/resources/post/index.md), with `platform_specific.tiktok`. not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `invalid_cursor` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pagination Items come in pages, **newest first**. - **Order**: Newest first - **Page size**: Up to 15 posts (the default; `limit` lowers it) - **Cursor lifetime**: 24 hours; don't store cursors - **Price**: Per page, 1 credit [Pagination guide](https://staging-fly.socialtap.dev/docs/pagination/index.md) ## Pricing | Source | Price per successful page | Per 1,000 pages | | --- | --- | --- | | Any source | 1 credit | $0.75–$1.45 | Endpoint ID `tiktok.posts` · operationId `tiktok.posts.list` · [Product overview](https://staging-fly.socialtap.dev/apis/tiktok/posts/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/tiktok/profile/ # TikTok Profile API TikTok Beta Returns a public TikTok account: user ID, username, display name, bio, bio link, avatar, verification badge, whether the account is private, its business category, join date and exact follower, following, video and total like counts. Accepts a username with or without the @, a tiktok.com profile URL, or a numeric user ID. GET `/v1/tiktok/profile` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/tiktok/profile' \ --data-urlencode 'handle=https://www.tiktok.com/@nasa' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":{"approximate_fields":[],"avatar_url":"https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-006…","banner_url":null,"bio":"Making the seemingly impossible, possible.✨","category":null,"country_code":null,"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":2140425,"following_count":23,"post_count":60,"total_like_count":11385816,"total_view_count":null},"platform":"tiktok","platform_specific":{},"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":0,"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":[],"mode":"live"}} ``` > [!NOTE] > > **Good to know** > > - Private accounts return their profile with is_private true; their videos are not public. > - type is organization for accounts TikTok marks as organizations and unknown otherwise: TikTok does not label people. > - category is set only for business accounts. location, country_code, banner_url and metrics.total_view_count are always null: public TikTok profiles do not show them. > - When the profile page is unavailable the answer comes from TikTok's embed profile: follower and like counts are then rounded (listed in approximate_fields), the join date, category, type and video count are null, and external_links is \[\] listed in unknown_fields (the embed does not show the bio link). > - Share links (vm.tiktok.com) are not accepted: pass the username or the profile URL. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `handle` required | `string`
max 2,048 chars | TikTok username with or without the @ (nasa, @nasa), a profile URL (tiktok.com/@nasa, m.tiktok.com/@nasa, with or without query parameters), or a numeric user ID (tiktok.com/share/user/\ or the bare ID). Usernames are case-insensitive. | `nasa` | `handle` takes TikTok profile links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). ## Response ### 200 `{data, meta}`. `data` is a [profile](https://staging-fly.socialtap.dev/resources/profile/index.md) object; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). The fields are the shared profile schema, the same on every platform. Fields: the [profile schema](https://staging-fly.socialtap.dev/resources/profile/index.md). not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pricing | Source | Price per successful request | Per 1,000 requests | | --- | --- | --- | | Public page `page` | 1 credit | $0.75–$1.45 | Endpoint ID `tiktok.profile` · operationId `tiktok.profile.get` · [Product overview](https://staging-fly.socialtap.dev/apis/tiktok/profile/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/tiktok/transcript/ # TikTok Transcript API TikTok Beta Returns the timed transcript of a public TikTok video: creator-edited captions, TikTok's auto-captions, or TikTok's own translation when you ask for another language with lang. Videos without any caption track are transcribed from their audio with speech-to-text. Accepts tiktok.com video, mobile, embed and vm./vt./t/ share links, or a numeric video ID. GET `/v1/tiktok/transcript` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/tiktok/transcript' \ --data-urlencode 'url=7673108027645414678' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":{"available_languages":["en"],"duration_ms":495000,"id":"7673108027645414678","is_auto_generated":false,"language":"en","platform":"tiktok","platform_specific":{},"segments":[{"end_ms":4820,"start_ms":100,"text":"Let me show you how to make a Japanese cream stew!"},{"end_ms":8380,"start_ms":4821,"text":"I think there is a way to definitely make it from scratch,"}],"source":"captions","text":"Let me show you how to make a Japanese cream stew! I think t…","url":"https://www.tiktok.com/@niinoclock/video/7673108027645414678"},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"tiktok","source":"captions","is_cached":false,"credits_charged":1,"latency_ms":668,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"video","id":"7673108027645414678","url":"https://www.tiktok.com/@niinoclock/video/7673108027645414678"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - Photo posts have no audio track and return no_transcript. > - Videos with licensed music and no speech may transcribe the song lyrics. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `url` required | `string`
max 2,048 chars | TikTok video URL (tiktok.com/@user/video/\, m.tiktok.com/v/\.html, vm.tiktok.com/\, vt.tiktok.com/\, tiktok.com/t/\, embed URLs) or a numeric video ID. | `https://www.tiktok.com/@scout2015/video/6718335390845095173` | | `lang` optional | `string`
bcp47 | Preferred language: a BCP-47 tag such as en or pt-BR (case-insensitive; _ is read as -). Selects the caption track (TikTok also offers its own translations); falls back to the video's original language, then any track. TikTok's three-letter codes (eng-US) work too. | `en` | `url` takes TikTok video and post links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). ## Response ### 200 `{data, meta}`. `data` is a [transcript](https://staging-fly.socialtap.dev/resources/transcript/index.md) object; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). The fields are the shared transcript schema, the same on every platform. Fields: the [transcript schema](https://staging-fly.socialtap.dev/resources/transcript/index.md). not_found, private_or_unavailable and no_transcript are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `no_transcript` 404, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pricing | Source | Price per successful request | Per 1,000 requests | | --- | --- | --- | | Captions `captions` | 1 credit | $0.75–$1.45 | | Speech-to-text `asr` | 2 credits + 2 per started minute of media
up to 122 credits (60 started minutes of media) | $1.50–$2.90 + $1.50–$2.90 per started minute of media | Example: a result from Speech-to-text `asr` with 3 started minutes of media costs 2 + 2 × 3 = **8 credits**. Endpoint ID `tiktok.transcript` · operationId `tiktok.transcript.get` · [Product overview](https://staging-fly.socialtap.dev/apis/tiktok/transcript/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/youtube/comments/ # YouTube Comments API YouTube Beta Returns the public top-level comments of a YouTube video, short or live replay, a page at a time: text, author handle and channel, posting time, likes, reply count, and whether the comment is pinned, hearted, edited or written by the creator. Sort newest first (the default, which also supports since and until) or by YouTube's Top ranking, and follow page.next_cursor for more. Accepts every YouTube URL form or a bare 11-character video ID. GET `/v1/youtube/comments` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/youtube/comments' \ --data-urlencode 'sort=newest' \ --data-urlencode 'url=jNQXAC9IVRw' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":[{"approximate_fields":["published_at","metrics.like_count"],"author":{"avatar_url":"https://yt3.ggpht.com/i8RqA9tYqbbnOi4mlnYEY7IR3kXAoDqQbM_5Sf…","display_name":null,"id":"UCC5NfQ6Mf0dq_eEwv4P_hWA","is_verified":true,"url":"https://www.youtube.com/channel/UCC5NfQ6Mf0dq_eEwv4P_hWA","username":"SanDiegoZoo"},"edited_at":null,"id":"UgzuC3zzpRZkjc5Qzsd4AaABAg","is_by_post_author":false,"is_edited":false,"is_liked_by_author":true,"is_pinned":true,"metrics":{"like_count":4800000,"reply_count":1000},"parent_id":null,"platform":"youtube","platform_specific":{},"post_id":"jNQXAC9IVRw","published_at":"2020-10-26T18:59:19Z","text":"We're so honored that the first ever YouTube video was filmed here!","unknown_fields":[],"url":"https://www.youtube.com/watch?v=jNQXAC9IVRw&lc=UgzuC3zzpRZkjc5Qzsd4AaABAg"},{"approximate_fields":["published_at"],"author":{"avatar_url":"https://yt3.ggpht.com/U9zdJRwdpo6WwsenxMf6AyMyRMBTWoKsyAWCnJ…","display_name":null,"id":"UCjoZE_5A9tO470CK3G6U7dg","is_verified":false,"url":"https://www.youtube.com/channel/UCjoZE_5A9tO470CK3G6U7dg","username":"CTwGary"},"edited_at":null,"id":"UgxHbErLhWMUy5GlFnp4AaABAg","is_by_post_author":false,"is_edited":false,"is_liked_by_author":false,"is_pinned":false,"metrics":{"like_count":0,"reply_count":0},"parent_id":null,"platform":"youtube","platform_specific":{},"post_id":"jNQXAC9IVRw","published_at":"2026-10-26T18:58:19Z","text":"I can’t believe it, but it’s 21 years old now","unknown_fields":[],"url":"https://www.youtube.com/watch?v=jNQXAC9IVRw&lc=UgxHbErLhWMUy5GlFnp4AaABAg"}],"page":{"has_more":true,"next_cursor":"c1.EXAMPLE_CURSOR","next_cursor_expires_at":"2026-10-26T12:00:00Z","end_reason":null},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"youtube","source":"api","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"video","id":"jNQXAC9IVRw","url":"https://www.youtube.com/watch?v=jNQXAC9IVRw"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - YouTube shows comment times as relative labels ("3 weeks ago"): published_at is the latest time the label allows and is listed in approximate_fields, so since and until are approximate to the label's unit. > - Like and reply counts above a thousand are rounded by YouTube ("4.8K") and listed in approximate_fields. > - Comments show the author's @handle; author.display_name is null. > - The newest order includes comments YouTube's Top ranking holds back as potential spam. > - Videos with comments turned off and private videos return private_or_unavailable; deleted videos return not_found. > - Replies are not included; metrics.reply_count says how many a comment has. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `url` required | `string`
max 2,048 chars | YouTube video URL (watch, youtu.be, shorts, live, embed, m./music. hosts) or an 11-character video ID. | `https://www.youtube.com/watch?v=jNQXAC9IVRw` | | `sort` optional | `string` | Order of the results. newest (default): newest first. popular: YouTube's Top comments ranking. Either way the video's pinned comment comes first.
One of `newest`, `popular` | — | | `since` optional | `string`
date-time | Only items at or after this time: RFC 3339 (2026-10-01T12:00:00Z) or a date (2026-10-01, midnight UTC). On newest-first lists, paging stops when it is reached, so later pages are not fetched or charged. Compared with published_at, which YouTube only shows as a relative label ("2 days ago"): the walk stops at the first comment whose label is older, so comments up to one label unit older can be included. | `2026-10-01` | | `until` optional | `string`
date-time | Only items before this time (exclusive): RFC 3339 or a date (midnight UTC). | `2026-10-08` | `url` takes YouTube video links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). `limit` and `cursor` page through the list: see Pagination. ## Response ### 200 `{data, page, meta}`. `data` is an array of up to 20 [comment](https://staging-fly.socialtap.dev/resources/comment/index.md) objects, newest first; `page` says how to get the next page; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). The fields are the shared comment schema, the same on every platform. Fields: the [comment schema](https://staging-fly.socialtap.dev/resources/comment/index.md). not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `invalid_cursor` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pagination Items come in pages, **newest first**. - **Order**: `sort=newest` (the default): newest first; `sort=popular`: YouTube's Top comments ranking - **Page size**: Up to 20 comments (the default; `limit` lowers it) - **Cursor lifetime**: 1 hour; don't store cursors - **Price**: Per page, 1 credit [Pagination guide](https://staging-fly.socialtap.dev/docs/pagination/index.md) ## Pricing | Source | Price per successful page | Per 1,000 pages | | --- | --- | --- | | Platform API `api` | 1 credit | $0.75–$1.45 | Endpoint ID `youtube.comments` · operationId `youtube.comments.list` · [Product overview](https://staging-fly.socialtap.dev/apis/youtube/comments/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/youtube/post/ # YouTube Video API YouTube Beta Returns a public YouTube video, short or live stream: title, description with its hashtags and links, the exact publish time, duration, thumbnail, the channel and any collaborating channels, exact view, like and comment counts, and YouTube's category, tags, chapters and live stream times. Accepts every YouTube video URL form or a bare video ID. GET `/v1/youtube/post` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/youtube/post' \ --data-urlencode 'url=https://youtube.com/shorts/3cWm9B_0_kI?feature=share' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":{"approximate_fields":["platform_specific.youtube.author_subscriber_count"],"author":{"avatar_url":"https://yt3.ggpht.com/nxYrc_1_2f77DoBadyxMTmv7ZpRZapHR5jbuYe…","display_name":"MrBeast","id":"UCX6OQ3DkcsbYNE6H8uQQuVA","is_verified":true,"url":"https://www.youtube.com/@MrBeast","username":"MrBeast"},"coauthors":[],"duration_ms":20000,"hashtags":[],"id":"3cWm9B_0_kI","is_nsfw":false,"is_pinned":null,"is_sponsored":null,"language":null,"media":[{"alt_text":null,"duration_ms":20000,"expires_at":null,"height":null,"thumbnail_url":"https://i.ytimg.com/vi/3cWm9B_0_kI/maxresdefault.jpg?sqp=-oa…","type":"video","url":null,"width":null}],"mentions":[],"metrics":{"comment_count":37974,"like_count":13520602,"quote_count":null,"repost_count":null,"save_count":null,"share_count":null,"view_count":731918538},"platform":"youtube","platform_specific":{"youtube":{"author_subscriber_count":524000000,"category":"Entertainment","chapters":[],"description_links":[],"is_age_restricted":false,"is_live_now":false,"is_paywalled":false,"is_unlisted":false,"keywords":[],"live_ended_at":null,"live_started_at":null,"scheduled_start_at":null}},"platform_type":"short","published_at":"2024-12-27T19:00:00Z","text":null,"thumbnail_url":"https://i.ytimg.com/vi/3cWm9B_0_kI/maxresdefault.jpg?sqp=-oa…","title":"How Many People To Stop Ronaldo?","type":"video","unknown_fields":[],"url":"https://www.youtube.com/shorts/3cWm9B_0_kI"},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"youtube","source":"api","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"video","id":"3cWm9B_0_kI","url":"https://www.youtube.com/shorts/3cWm9B_0_kI"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` > [!NOTE] > > **Good to know** > > - platform_type is short, live (streams and premieres) or video. > - Age-restricted and paid videos are returned with their public details. > - media\[0\].url is null: YouTube's stream links only work from the IP that requested them. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `url` required | `string`
max 2,048 chars | YouTube video, short or live stream URL (watch, youtu.be, shorts, live, embed; m. and music. hosts) or an 11-character video ID. | `https://www.youtube.com/watch?v=dQw4w9WgXcQ` | `url` takes YouTube video links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). ## Response ### 200 `{data, meta}`. `data` is a [post](https://staging-fly.socialtap.dev/resources/post/index.md) object; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). Fields only YouTube has are under `platform_specific.youtube`. Fields: the [post schema](https://staging-fly.socialtap.dev/resources/post/index.md), with `platform_specific.youtube`. not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pricing | Source | Price per successful request | Per 1,000 requests | | --- | --- | --- | | Platform API `api` | 1 credit | $0.75–$1.45 | | Public page `page` | 1 credit | $0.75–$1.45 | Endpoint ID `youtube.post` · operationId `youtube.post.get` · [Product overview](https://staging-fly.socialtap.dev/apis/youtube/post/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/youtube/posts/ # YouTube Channel Videos API YouTube Beta Returns the public uploads of a YouTube channel a page at a time: its videos, shorts or live streams, each with its link, title, thumbnail, length, views and upload time, newest, oldest or most popular first. The newest uploads also carry exact times, view and like counts and descriptions. Accepts a handle, any channel link or a channel ID. GET `/v1/youtube/posts` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/youtube/posts' \ --data-urlencode 'handle=@jawed' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":[{"approximate_fields":[],"author":{"avatar_url":"https://yt3.googleusercontent.com/uI3VE4PVqvCy0xnWLqMJnEzyBU…","display_name":"jawed","id":"UC4QobU6STFB0P71PMvOGN5A","is_verified":null,"url":"https://www.youtube.com/channel/UC4QobU6STFB0P71PMvOGN5A","username":"jawed"},"coauthors":[],"duration_ms":19000,"hashtags":[],"id":"jNQXAC9IVRw","is_nsfw":null,"is_pinned":null,"is_sponsored":null,"language":null,"media":[{"alt_text":null,"duration_ms":19000,"expires_at":null,"height":360,"thumbnail_url":"https://i.ytimg.com/vi/jNQXAC9IVRw/hqdefault.jpg","type":"video","url":null,"width":480}],"mentions":[],"metrics":{"comment_count":null,"like_count":20117813,"quote_count":null,"repost_count":null,"save_count":null,"share_count":null,"view_count":446865949},"platform":"youtube","platform_specific":{"youtube":{"author_subscriber_count":null,"category":null,"chapters":[],"description_links":["https://www.youtube.com/watch?v=0PT5c1z3LL8","https://www.youtube.com/watch?v=RRBN_4L09Mg"],"is_age_restricted":null,"is_live_now":null,"is_paywalled":null,"is_unlisted":null,"keywords":[],"live_ended_at":null,"live_started_at":null,"scheduled_start_at":null}},"platform_type":"video","published_at":"2005-04-24T03:31:52Z","text":"Microplastics are accumulating in human brains at an alarmin…","thumbnail_url":"https://i.ytimg.com/vi/jNQXAC9IVRw/hqdefault.jpg","title":"Me at the zoo","type":"video","unknown_fields":["coauthors","platform_specific.youtube.keywords"],"url":"https://www.youtube.com/watch?v=jNQXAC9IVRw"}],"page":{"has_more":false,"next_cursor":null,"next_cursor_expires_at":null,"end_reason":"end"},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"youtube","source":"api","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"profile","id":"UC4QobU6STFB0P71PMvOGN5A","url":"https://www.youtube.com/@jawed"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` > [!NOTE] > > **Good to know** > > - Beyond each tab's newest uploads (up to 15, exact from YouTube's uploads feed), published_at comes from a relative label (the latest time it allows) and views are rounded, both in approximate_fields; text and metrics.like_count are null there, and a non-English title may be YouTube's English translation. > - Shorts beyond the newest have no published_at or duration_ms, so since and until are refused with tab=shorts. Streams live now or scheduled have no view count. > - A channel without the tab answers an empty page. A tab in a pasted link is ignored: set tab. > - The live tab lists upcoming and live streams first, then past streams by when they streamed: with tab=live, since and until filter the streams instead of ending the walk, so it reads further. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `handle` required | `string`
max 2,048 chars | YouTube channel: a handle with or without the @, any channel link (@handle, /channel/UC…, legacy /c/, /user/ and root vanity links, any tab, m. and music. hosts), a bare UC… channel ID, or c/\, user/\, vanity/\. | `@MrBeast` | | `tab` optional | `string` | The channel tab to list: videos (default), shorts or live (upcoming, live and past streams).
One of `videos`, `shorts`, `live` | `shorts` | | `sort` optional | `string` | Order of the results. newest (default), popular (most viewed first) or oldest.
One of `newest`, `popular`, `oldest` | — | | `since` optional | `string`
date-time | Only items at or after this time: RFC 3339 (2026-10-01T12:00:00Z) or a date (2026-10-01, midnight UTC). On newest-first lists, paging stops when it is reached, so later pages are not fetched or charged. Beyond the newest uploads published_at is approximate ("3 weeks ago"): uploads up to one label unit older may be included. Not with tab=shorts. | `2026-10-01` | | `until` optional | `string`
date-time | Only items before this time (exclusive): RFC 3339 or a date (midnight UTC). Not with tab=shorts. | `2026-10-08` | `handle` takes YouTube profile links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). `limit` and `cursor` page through the list: see Pagination. ## Response ### 200 `{data, page, meta}`. `data` is an array of up to 48 [post](https://staging-fly.socialtap.dev/resources/post/index.md) objects, newest first; `page` says how to get the next page; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). Fields only YouTube has are under `platform_specific.youtube`. Fields: the [post schema](https://staging-fly.socialtap.dev/resources/post/index.md), with `platform_specific.youtube`. not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `invalid_cursor` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pagination Items come in pages, **newest first**. - **Order**: `sort=newest` (the default): newest first; `sort=oldest`: oldest first; `sort=popular`: YouTube's Popular order (most viewed first) - **Page size**: Up to 48 posts (the default; `limit` lowers it) - **Cursor lifetime**: 1 hour; don't store cursors - **Price**: Per page, 1 credit [Pagination guide](https://staging-fly.socialtap.dev/docs/pagination/index.md) ## Pricing | Source | Price per successful page | Per 1,000 pages | | --- | --- | --- | | Any source | 1 credit | $0.75–$1.45 | Endpoint ID `youtube.posts` · operationId `youtube.posts.list` · [Product overview](https://staging-fly.socialtap.dev/apis/youtube/posts/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/youtube/profile/ # YouTube Channel API YouTube Beta Returns a public YouTube channel: channel ID, handle, name, description, keywords, avatar, banner, verification badge, country, join date, the links on its About panel with their labels, and its subscriber count with exact video and lifetime view counts. Accepts a handle with or without the @, any channel link with any tab (@handle, /channel/UC…, the legacy /c/, /user/ and root vanity links), or a bare channel ID. GET `/v1/youtube/profile` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/youtube/profile' \ --data-urlencode 'handle=@NASA' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":{"approximate_fields":["created_at","metrics.follower_count"],"avatar_url":"https://yt3.googleusercontent.com/eIf5fNPcIcj9ig-wZBeq4stFy1…","banner_url":"https://yt3.googleusercontent.com/y8Srh-oCcilLvBO8jAPI546m1b…","bio":"NASA's mission is to pioneer the future in space exploration…","category":null,"country_code":null,"created_at":"2008-06-03T07:00:00Z","display_name":"NASA","external_links":[{"title":"nasa.gov","url":"https://www.nasa.gov"},{"title":"X","url":"https://x.com/nasa"}],"id":"UCLA_DiR1FfKNvjuUpBHmylQ","is_private":false,"is_verified":true,"location":null,"metrics":{"follower_count":15100000,"following_count":null,"post_count":6194,"total_like_count":null,"total_view_count":1132473139},"platform":"youtube","platform_specific":{"youtube":{"is_business_email_listed":null,"is_official_artist_channel":false,"is_subscriber_count_hidden":false,"keywords":["nasa","space"]}},"type":"unknown","unknown_fields":[],"url":"https://www.youtube.com/@NASA","username":"NASA"},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"youtube","source":"api","is_cached":false,"credits_charged":1,"latency_ms":0,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"profile","id":"UCLA_DiR1FfKNvjuUpBHmylQ","url":"https://www.youtube.com/@NASA"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - metrics.follower_count is the subscriber count YouTube shows, rounded for display (listed in approximate_fields); null when the channel hides it. Video and view counts are exact; both are null for a channel without public videos. > - created_at is the start of the US Pacific day YouTube shows as the join date, in UTC: the channel was created within the following day (listed in approximate_fields). > - location is the country the owner set, as YouTube names it; country_code its ISO code. > - is_verified is true for the check mark and for an Official Artist Channel's note badge. YouTube's built-in channels (Music, Sports, Gaming, News, Learning, Live, Fashion & Beauty, Shopping) have no badge or banner (null); all but Music have no About panel, so they answer what their header shows. > - A bare string is a handle, even one shaped like a video ID: send a channel link or the UC… ID to be unambiguous. Links to youtube.com pages that are not channels answer not_found or invalid_input. > - metrics.following_count and metrics.total_like_count are always null and type is unknown: YouTube does not show them. Business email addresses are behind a sign-in and are not returned. > - Terminated and unknown channels answer not_found. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `handle` required | `string`
max 2,048 chars | YouTube channel: a handle with or without the @ (NASA, @NASA), any channel link (youtube.com/@NASA, youtube.com/channel/UC…, the legacy youtube.com/c/\, youtube.com/user/\ and youtube.com/\ links, with any tab or view after them such as /videos, /playlists, /posts or /about, and m. or music. hosts), a bare UC… channel ID, or c/\, user/\ or vanity/\. Handles are case-insensitive. A bare string is always a handle, even one shaped like a video ID. | `@NASA` | `handle` takes YouTube profile links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). ## Response ### 200 `{data, meta}`. `data` is a [profile](https://staging-fly.socialtap.dev/resources/profile/index.md) object; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). Fields only YouTube has are under `platform_specific.youtube`. Fields: the [profile schema](https://staging-fly.socialtap.dev/resources/profile/index.md), with `platform_specific.youtube`. not_found and private_or_unavailable are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pricing | Source | Price per successful request | Per 1,000 requests | | --- | --- | --- | | Any source | 1 credit | $0.75–$1.45 | Endpoint ID `youtube.profile` · operationId `youtube.profile.get` · [Product overview](https://staging-fly.socialtap.dev/apis/youtube/profile/index.md) --- Source: https://staging-fly.socialtap.dev/docs/reference/youtube/transcript/ # YouTube Transcript API YouTube Beta Returns the timed transcript of a public YouTube video, short or live replay: the creator's manual captions when they exist, otherwise YouTube's auto-generated track, in the language you ask for. Videos without any caption track are transcribed from their audio with speech-to-text. Accepts every YouTube URL form (watch, youtu.be, shorts, live, embed, music.) or a bare 11-character video ID. GET `/v1/youtube/transcript` ## Example cURL: ```shell curl -G 'https://staging-fly.socialtap.dev/v1/youtube/transcript' \ --data-urlencode 'url=dQw4w9WgXcQ' \ -H "x-api-key: $SOCIALTAP_API_KEY" ``` 200 OK: ```json {"data":{"available_languages":["en","de-DE"],"duration_ms":213000,"id":"dQw4w9WgXcQ","is_auto_generated":false,"language":"en","platform":"youtube","platform_specific":{},"segments":[{"end_ms":3040,"start_ms":1360,"text":"[♪♪♪]"},{"end_ms":21880,"start_ms":18640,"text":"♪ We're no strangers to love ♪"}],"source":"captions","text":"[♪♪♪] ♪ We're no strangers to love ♪ ♪ You know the rules an…","url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"},"meta":{"request_id":"req_3f9c2a7d1e8b4c6a9f0e2d4b6a8c0e1f","platform":"youtube","source":"captions","is_cached":false,"credits_charged":1,"latency_ms":511,"fetched_at":"2026-10-25T12:00:00Z","input":{"kind":"video","id":"dQw4w9WgXcQ","url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"},"credits_remaining":9999,"client_request_id":null,"warnings":[],"mode":"live"}} ``` Long arrays and strings are shortened for display; real responses are complete. > [!NOTE] > > **Good to know** > > - Manual captions are preferred over auto-generated ones; is_auto_generated tells you which you got. > - Private, members-only, rental and age-restricted videos return private_or_unavailable. ## Query parameters | Name | Type | Description | Example | | --- | --- | --- | --- | | `url` required | `string`
max 2,048 chars | YouTube video URL (watch, youtu.be, shorts, live, embed, m./music. hosts) or an 11-character video ID. | `https://www.youtube.com/watch?v=dQw4w9WgXcQ` | | `lang` optional | `string`
bcp47 | Preferred language: a BCP-47 tag such as en or pt-BR (case-insensitive; _ is read as -). Selects the caption track; a manual track is preferred over an auto-generated one. Default: the video's original-language manual track, else its auto-generated track. | `en` | `url` takes YouTube video links; another kind of link, or another platform's, fails free with [`invalid_input`](https://staging-fly.socialtap.dev/docs/errors/index.md#invalid_input). ## Response ### 200 `{data, meta}`. `data` is a [transcript](https://staging-fly.socialtap.dev/resources/transcript/index.md) object; `meta` describes the request ([meta](https://staging-fly.socialtap.dev/docs/reference/index.md#meta)). The fields are the shared transcript schema, the same on every platform. Fields: the [transcript schema](https://staging-fly.socialtap.dev/resources/transcript/index.md). not_found, private_or_unavailable and no_transcript are item-level: the request was fine, the item can't be returned. Errors: `invalid_input` 400, `unauthorized` 401, `forbidden` 403, `insufficient_credits` 402, `spend_limit_reached` 402, `rate_limited` 429, `not_found` 404, `private_or_unavailable` 403, `no_transcript` 404, `upstream_blocked` 503, `upstream_error` 502, `overloaded` 503, `internal` 500 (each code's meaning and retry rule: [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). ## Pricing | Source | Price per successful request | Per 1,000 requests | | --- | --- | --- | | Captions `captions` | 1 credit | $0.75–$1.45 | | Speech-to-text `asr` | 2 credits + 2 per started minute of media
up to 122 credits (60 started minutes of media) | $1.50–$2.90 + $1.50–$2.90 per started minute of media | Example: a result from Speech-to-text `asr` with 3 started minutes of media costs 2 + 2 × 3 = **8 credits**. Endpoint ID `youtube.transcript` · operationId `youtube.transcript.get` · [Product overview](https://staging-fly.socialtap.dev/apis/youtube/transcript/index.md) --- Source: https://staging-fly.socialtap.dev/resources/ad/ # Ad Schemas A public ad library entry: advertiser, creatives, run dates, placements and, where disclosed, reach and spend ranges. ## Endpoints 1 endpoint returns ads on Facebook. - [GETFacebook Ad Library Search API`/v1/facebook/search_ads`](https://staging-fly.socialtap.dev/docs/reference/facebook/search_ads/index.md) ## Schema Every endpoint above returns an array of these objects as `data`, with a [`page`](https://staging-fly.socialtap.dev/docs/pagination/index.md) object. The standard [`meta`](https://staging-fly.socialtap.dev/docs/reference/index.md#meta) comes with every response. | Field | Type | Description | | --- | --- | --- | | `platform` | `string` | Library owner's platform ID (facebook for Meta's library, tiktok, google, linkedin). | | `id` | `string` | Library ad ID (string). | | `url` | `string` (uri) | Public library page of the ad. | | `advertiser` | `object` | The advertising page, business or account. | | `advertiser.id` | `string` or `null` | Platform-native account ID; null when only the handle is known. | | `advertiser.username` | `string` or `null` | Handle without the @; null when the account has none. | | `advertiser.display_name` | `string` or `null` | Display name shown on the profile. | | `advertiser.url` | `string` (uri) or `null` | Canonical profile URL. | | `advertiser.avatar_url` | `string` (uri) or `null` | Profile picture URL. Platform CDN URLs can expire within hours. | | `advertiser.is_verified` | `boolean` or `null` | Platform verification badge; null where the platform has no badge. | | `paid_by` | `string` or `null` | Disclosed payer ("Paid for by"). | | `is_active` | `boolean` or `null` | True while the ad is running. | | `first_shown_at` | `string` (date-time) or `null` | First delivery, RFC 3339 UTC (often date-only: see approximate_fields). | | `last_shown_at` | `string` (date-time) or `null` | Last delivery, RFC 3339 UTC; null while active. | | `type` | `string` | Creative shape. New values may be added; treat unknown values as other.
One of `image`, `video`, `carousel`, `text`, `dynamic`, `other` | | `publisher_platforms` | `string[]` | Where it ran, lowercase IDs (facebook, instagram, messenger, audience_network, youtube, …); \[\] if not disclosed. | | `creatives` | `object[]` | The ad's creatives: one per card of a carousel and per variation of a dynamic or catalog ad, else one for the ad itself; \[\] if none are shown. | | `creatives[].title` | `string` or `null` | Headline. | | `creatives[].body` | `string` or `null` | Primary text. | | `creatives[].link_url` | `string` (uri) or `null` | Destination URL. | | `creatives[].link_description` | `string` or `null` | Link description text. | | `creatives[].cta_text` | `string` or `null` | Call-to-action label (e.g. Shop now). | | `creatives[].media` | `object[]` | Images and videos of this creative; \[\] if none. | | `creatives[].media[].type` | `string` | Kind of file. New values may be added; treat unknown values as other.
One of `image`, `video`, `audio`, `gif` | | `creatives[].media[].url` | `string` (uri) or `null` | Direct file URL; null when the platform withholds it, or when the link would only work from SocialTap's network or with a login. Often signed and expiring: see expires_at. | | `creatives[].media[].expires_at` | `string` (date-time) or `null` | When url stops working, RFC 3339 UTC, read from the platform's signed URL; null when unknown or when it does not expire. | | `creatives[].media[].thumbnail_url` | `string` (uri) or `null` | Poster or preview image URL. | | `creatives[].media[].width` | `integer` or `null` | Width in pixels. | | `creatives[].media[].height` | `integer` or `null` | Height in pixels. | | `creatives[].media[].duration_ms` | `integer` or `null` | Duration in milliseconds (video and audio only). | | `creatives[].media[].alt_text` | `string` or `null` | Accessibility text provided by the author or platform. | | `languages` | `string[]` | BCP-47 languages of the ad; \[\] if not disclosed. | | `country_codes` | `string[]` | ISO 3166-1 alpha-2 countries where it was shown; \[\] if not disclosed. | | `is_political` | `boolean` or `null` | Categorized as a political or social-issue ad; false when the library files it under another category, null when it shows none. | | `impressions` | `object` or `null` | Impressions or times shown range; null when not disclosed. | | `impressions.lower_bound` | `integer` | Inclusive lower bound. | | `impressions.upper_bound` | `integer` or `null` | Inclusive upper bound; null when open-ended (e.g. ">1M"). Equal to lower_bound when exact. | | `reach` | `object` or `null` | Unique users reached range; null when not disclosed. | | `reach.lower_bound` | `integer` | Inclusive lower bound. | | `reach.upper_bound` | `integer` or `null` | Inclusive upper bound; null when open-ended (e.g. ">1M"). Equal to lower_bound when exact. | | `audience_size` | `object` or `null` | Estimated target audience size range; null when not disclosed. | | `audience_size.lower_bound` | `integer` | Inclusive lower bound. | | `audience_size.upper_bound` | `integer` or `null` | Inclusive upper bound; null when open-ended (e.g. ">1M"). Equal to lower_bound when exact. | | `spend` | `object` or `null` | Spend range; null when not disclosed. | | `spend.lower_bound` | `integer` | Inclusive lower bound, in whole units of currency. | | `spend.upper_bound` | `integer` or `null` | Inclusive upper bound in whole units of currency; null when open-ended. | | `spend.currency` | `string` | ISO 4217 currency code, uppercase (USD). | | `platform_specific` | `object` | Fields only this platform has, under its ID (platform_specific.\); each null when unknown. | | `approximate_fields` | `string[]` | JSON paths of values that are rounded, date-only or parsed from display text. A list path (external_links) means the list may be incomplete. \[\] when all exact. | | `unknown_fields` | `string[]` | JSON paths of lists whose \[\] means unknown rather than none (a source that cannot see them); null already means unknown for every other field. \[\] when every list is known. | ## Item errors Besides the [request-level errors](https://staging-fly.socialtap.dev/docs/errors/index.md) every endpoint can return, ad endpoints return: `not_found` (see [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). --- Source: https://staging-fly.socialtap.dev/resources/comment/ # Comment Schemas A public comment or reply on a post, with its author, thread position and like/reply counts. ## Endpoints 3 endpoints return comments on Rumble, TikTok and YouTube. - [GETRumble Comments API`/v1/rumble/comments`](https://staging-fly.socialtap.dev/docs/reference/rumble/comments/index.md) - [GETTikTok Comments API`/v1/tiktok/comments`](https://staging-fly.socialtap.dev/docs/reference/tiktok/comments/index.md) - [GETYouTube Comments API`/v1/youtube/comments`](https://staging-fly.socialtap.dev/docs/reference/youtube/comments/index.md) ## Schema Every endpoint above returns an array of these objects as `data`, with a [`page`](https://staging-fly.socialtap.dev/docs/pagination/index.md) object. The standard [`meta`](https://staging-fly.socialtap.dev/docs/reference/index.md#meta) comes with every response. | Field | Type | Description | | --- | --- | --- | | `platform` | `string` | Platform ID (tiktok, youtube, …). | | `id` | `string` | Platform-native comment ID (string; Reddit without t1_). | | `post_id` | `string` | ID of the post commented on (equals post.id). | | `parent_id` | `string` or `null` | ID of the comment this replies to; null for top-level comments. | | `url` | `string` (uri) or `null` | Permalink, when the platform has one. | | `text` | `string` | Comment body as plain text. | | `author` | `object` | The commenting account. | | `author.id` | `string` or `null` | Platform-native account ID; null when only the handle is known. | | `author.username` | `string` or `null` | Handle without the @; null when the account has none. | | `author.display_name` | `string` or `null` | Display name shown on the profile. | | `author.url` | `string` (uri) or `null` | Canonical profile URL. | | `author.avatar_url` | `string` (uri) or `null` | Profile picture URL. Platform CDN URLs can expire within hours. | | `author.is_verified` | `boolean` or `null` | Platform verification badge; null where the platform has no badge. | | `published_at` | `string` (date-time) or `null` | When the comment was posted, RFC 3339 UTC. Where the platform shows only a relative time ("3 weeks ago"), the latest time that label allows (the fetch time minus the stated amount), listed in approximate_fields. | | `edited_at` | `string` (date-time) or `null` | When the comment was last edited, RFC 3339 UTC; null when never edited or when the platform marks edits without a time (see is_edited). | | `is_edited` | `boolean` or `null` | The comment was edited after posting (the platform's edited mark); null when the platform does not say. | | `is_pinned` | `boolean` or `null` | Pinned by the post's author. | | `is_liked_by_author` | `boolean` or `null` | Liked or hearted by the post's author. | | `is_by_post_author` | `boolean` or `null` | Written by the post's author (the creator's own comment, marked as such by the platform). | | `metrics` | `object` | Counts at fetch time; null = hidden or not exposed, 0 = zero. | | `metrics.like_count` | `integer` or `null` | Likes or upvotes; never negative. | | `metrics.reply_count` | `integer` or `null` | Direct replies. | | `platform_specific` | `object` | Fields only this platform has, under its ID (platform_specific.\); each null when unknown. | | `platform_specific.tiktok` | `object` or `null` | TikTok's own comment fields. | | `platform_specific.tiktok.language` | `string` or `null` | The comment's language as TikTok detected it, an ISO 639-1 code (en, fr); null when TikTok could not tell (emoji, a name) or does not say. | | `platform_specific.rumble` | `object` or `null` | Rumble's own comment fields. | | `platform_specific.rumble.dislike_count` | `integer` or `null` | Thumbs-down votes on the comment. Exact below 1,000; from 1,000 Rumble shows a label such as "1.31K", taken to be cut to three significant digits as Rumble's other counts are (not checked against an exact count), and listed in approximate_fields. | | `platform_specific.rumble.author_badges` | `string[]` | The badges Rumble shows beside the commenter's name, by their label as Rumble writes it (seen: Verified, Admin, Moderator, Supporter, Supporter+, Supporter++, Sub, Locals Supporter; new labels may appear). \[\] when none. | | `approximate_fields` | `string[]` | JSON paths of values rounded by the platform or parsed from display text. A list path (external_links) means the list may be incomplete. \[\] when all exact. | | `unknown_fields` | `string[]` | JSON paths of lists whose \[\] means unknown rather than none (a source that cannot see them); null already means unknown for every other field. \[\] when every list is known. | ## Item errors Besides the [request-level errors](https://staging-fly.socialtap.dev/docs/errors/index.md) every endpoint can return, comment endpoints return: `not_found`, `private_or_unavailable` (see [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). --- Source: https://staging-fly.socialtap.dev/resources/post/ # Post Schemas A public post (video, reel, short, photo, carousel, tweet or text) with its author, media and engagement metrics. ## Endpoints 10 endpoints return posts on Facebook, Instagram, Rumble, TikTok and YouTube. - [GETFacebook Post API`/v1/facebook/post`](https://staging-fly.socialtap.dev/docs/reference/facebook/post/index.md) - [GETInstagram Post API`/v1/instagram/post`](https://staging-fly.socialtap.dev/docs/reference/instagram/post/index.md) - [GETInstagram Posts API`/v1/instagram/posts`](https://staging-fly.socialtap.dev/docs/reference/instagram/posts/index.md) - [GETRumble Video API`/v1/rumble/post`](https://staging-fly.socialtap.dev/docs/reference/rumble/post/index.md) - [GETRumble Channel Videos API`/v1/rumble/posts`](https://staging-fly.socialtap.dev/docs/reference/rumble/posts/index.md) - [GETRumble Search API`/v1/rumble/search_posts`](https://staging-fly.socialtap.dev/docs/reference/rumble/search_posts/index.md) - [GETTikTok Post API`/v1/tiktok/post`](https://staging-fly.socialtap.dev/docs/reference/tiktok/post/index.md) - [GETTikTok Posts API`/v1/tiktok/posts`](https://staging-fly.socialtap.dev/docs/reference/tiktok/posts/index.md) - [GETYouTube Video API`/v1/youtube/post`](https://staging-fly.socialtap.dev/docs/reference/youtube/post/index.md) - [GETYouTube Channel Videos API`/v1/youtube/posts`](https://staging-fly.socialtap.dev/docs/reference/youtube/posts/index.md) ## Schema Single-item endpoints return this object as `data`; list endpoints return an array of them as `data` with a [`page`](https://staging-fly.socialtap.dev/docs/pagination/index.md) object. The standard [`meta`](https://staging-fly.socialtap.dev/docs/reference/index.md#meta) comes with every response. | Field | Type | Description | | --- | --- | --- | | `platform` | `string` | Platform ID (tiktok, youtube, …). | | `id` | `string` | Platform-native post ID (string; Instagram shortcode, Reddit without t3_). | | `url` | `string` (uri) | Canonical post URL. | | `type` | `string` | Normalized content shape. New values may be added; treat unknown values as other.
One of `video`, `image`, `carousel`, `text`, `link`, `audio` | | `platform_type` | `string` or `null` | The platform's own product label, lowercase (reel, short, story, live, tweet, …). | | `title` | `string` or `null` | Headline (YouTube title, Reddit title); null on caption-only platforms. | | `text` | `string` or `null` | Caption, description or body as plain text. | | `language` | `string` or `null` | BCP-47 language of text, when the platform states it. | | `published_at` | `string` (date-time) or `null` | When the post went public, RFC 3339 UTC. | | `author` | `object` | The posting account. | | `author.id` | `string` or `null` | Platform-native account ID; null when only the handle is known. | | `author.username` | `string` or `null` | Handle without the @; null when the account has none. | | `author.display_name` | `string` or `null` | Display name shown on the profile. | | `author.url` | `string` (uri) or `null` | Canonical profile URL. | | `author.avatar_url` | `string` (uri) or `null` | Profile picture URL. Platform CDN URLs can expire within hours. | | `author.is_verified` | `boolean` or `null` | Platform verification badge; null where the platform has no badge. | | `coauthors` | `object[]` | Other accounts credited as co-authors of the post (Instagram and Facebook collabs, YouTube collaborations), in the platform's order, author excluded; \[\] if none. | | `coauthors[].id` | `string` or `null` | Platform-native account ID; null when only the handle is known. | | `coauthors[].username` | `string` or `null` | Handle without the @; null when the account has none. | | `coauthors[].display_name` | `string` or `null` | Display name shown on the profile. | | `coauthors[].url` | `string` (uri) or `null` | Canonical profile URL. | | `coauthors[].avatar_url` | `string` (uri) or `null` | Profile picture URL. Platform CDN URLs can expire within hours. | | `coauthors[].is_verified` | `boolean` or `null` | Platform verification badge; null where the platform has no badge. | | `duration_ms` | `integer` or `null` | Length of the primary video or audio in milliseconds; null for other posts. | | `thumbnail_url` | `string` (uri) or `null` | Cover or poster image URL. | | `media` | `object[]` | Attachments in display order (carousel items); \[\] for text posts. | | `media[].type` | `string` | Kind of file. New values may be added; treat unknown values as other.
One of `image`, `video`, `audio`, `gif` | | `media[].url` | `string` (uri) or `null` | Direct file URL; null when the platform withholds it, or when the link would only work from SocialTap's network or with a login. Often signed and expiring: see expires_at. | | `media[].expires_at` | `string` (date-time) or `null` | When url stops working, RFC 3339 UTC, read from the platform's signed URL; null when unknown or when it does not expire. | | `media[].thumbnail_url` | `string` (uri) or `null` | Poster or preview image URL. | | `media[].width` | `integer` or `null` | Width in pixels. | | `media[].height` | `integer` or `null` | Height in pixels. | | `media[].duration_ms` | `integer` or `null` | Duration in milliseconds (video and audio only). | | `media[].alt_text` | `string` or `null` | Accessibility text provided by the author or platform. | | `hashtags` | `string[]` | Hashtags without #, case kept; \[\] if none. | | `mentions` | `string[]` | Mentioned or tagged usernames without @; \[\] if none. | | `is_pinned` | `boolean` or `null` | Pinned to the author's profile. | | `is_sponsored` | `boolean` or `null` | Labeled as a paid partnership or branded content. | | `is_nsfw` | `boolean` or `null` | Marked adult or sensitive by the platform. | | `metrics` | `object` | Counts at fetch time; null = hidden or not exposed, 0 = zero. | | `metrics.view_count` | `integer` or `null` | Views or plays. | | `metrics.like_count` | `integer` or `null` | Likes, hearts, total reactions or upvotes; never negative. Null when hidden by the owner. | | `metrics.comment_count` | `integer` or `null` | Comments and replies. | | `metrics.share_count` | `integer` or `null` | Shares or sends (not reposts). | | `metrics.repost_count` | `integer` or `null` | Reposts, retweets or reshares. | | `metrics.quote_count` | `integer` or `null` | Quote posts. | | `metrics.save_count` | `integer` or `null` | Saves, bookmarks or favorites. | | `platform_specific` | `object` | Fields only this platform has, under its ID (platform_specific.\); each null when unknown. | | `platform_specific.tiktok` | `object` or `null` | A TikTok post's own fields. | | `platform_specific.tiktok.author_sec_uid` | `string` or `null` | The author's secUid, TikTok's account key that survives a rename. | | `platform_specific.tiktok.country_code` | `string` or `null` | ISO 3166-1 alpha-2 country TikTok registers the post to (not the viewer's). | | `platform_specific.tiktok.is_ad` | `boolean` or `null` | The post runs as a TikTok ad. | | `platform_specific.tiktok.is_ai_generated` | `boolean` or `null` | TikTok labels the post AI-generated. | | `platform_specific.tiktok.location` | `object` or `null` | The tagged place; null when none. | | `platform_specific.tiktok.location.id` | `string` or `null` | Place ID. | | `platform_specific.tiktok.location.name` | `string` or `null` | Place name. | | `platform_specific.tiktok.location.address` | `string` or `null` | Address line. | | `platform_specific.tiktok.location.city` | `string` or `null` | City. | | `platform_specific.tiktok.location.category` | `string` or `null` | Top-level place category. | | `platform_specific.tiktok.music` | `object` or `null` | The post's sound. | | `platform_specific.tiktok.music.id` | `string` or `null` | Sound ID. | | `platform_specific.tiktok.music.title` | `string` or `null` | Sound title. | | `platform_specific.tiktok.music.author_name` | `string` or `null` | Who TikTok credits for the sound. | | `platform_specific.tiktok.music.is_original` | `boolean` or `null` | An original sound a creator uploaded (false: a catalogue track). | | `platform_specific.tiktok.music.duration_ms` | `integer` or `null` | Sound length in milliseconds. | | `platform_specific.tiktok.music.cover_url` | `string` (uri) or `null` | Sound cover image URL. | | `platform_specific.tiktok.music.play_url` | `string` (uri) or `null` | Audio file URL, signed and expiring. | | `platform_specific.youtube` | `object` or `null` | A YouTube video's own fields. | | `platform_specific.youtube.category` | `string` or `null` | YouTube category (Music, Gaming, …), in English. | | `platform_specific.youtube.keywords` | `string[]` | The uploader's tags; \[\] when none. | | `platform_specific.youtube.chapters` | `object[]` | Chapters in playback order; \[\] when none. | | `platform_specific.youtube.chapters[].title` | `string` or `null` | Chapter title. | | `platform_specific.youtube.chapters[].start_ms` | `integer` or `null` | Start, in ms from the beginning. | | `platform_specific.youtube.description_links` | `string[]` | Links in the description, in order, deduplicated; \[\] when none. | | `platform_specific.youtube.is_unlisted` | `boolean` or `null` | Unlisted: watchable by link, not listed on the channel or in search. | | `platform_specific.youtube.is_age_restricted` | `boolean` or `null` | Viewers must sign in to confirm their age; null when the answer does not tell. | | `platform_specific.youtube.is_paywalled` | `boolean` or `null` | Must be bought or rented (movies, paid content). | | `platform_specific.youtube.is_live_now` | `boolean` or `null` | A live stream on air now. | | `platform_specific.youtube.live_started_at` | `string` (date-time) or `null` | When a live stream or premiere went on air; null if never live. | | `platform_specific.youtube.live_ended_at` | `string` (date-time) or `null` | When a live stream or premiere ended; null while live or if never live. | | `platform_specific.youtube.scheduled_start_at` | `string` (date-time) or `null` | When a scheduled stream or premiere is due to start; null otherwise. | | `platform_specific.youtube.author_subscriber_count` | `integer` or `null` | The channel's subscriber count, rounded by YouTube (in approximate_fields); null when hidden. | | `platform_specific.rumble` | `object` or `null` | A Rumble video's own fields. | | `platform_specific.rumble.embed_id` | `string` or `null` | The video's embed player ID (rumble.com/embed/\/): its own namespace, not id. | | `platform_specific.rumble.embed_url` | `string` (uri) or `null` | The embeddable player URL. | | `platform_specific.rumble.numeric_id` | `string` or `null` | Rumble's numeric video ID, which its own services use. | | `platform_specific.rumble.dislike_count` | `integer` or `null` | Thumbs-down votes. Exact in Rumble's JSON (shorts, grid lists); a video's page and the older HTML cards cut it to three significant digits from 1,000 (in approximate_fields). | | `platform_specific.rumble.category` | `string` or `null` | Rumble's primary category (News, Podcasts, …). | | `platform_specific.rumble.secondary_category` | `string` or `null` | Rumble's secondary category; null when none. | | `platform_specific.rumble.keywords` | `string[]` | The uploader's tags; \[\] when none. | | `platform_specific.rumble.caption_tracks` | `object[]` | Caption tracks (WebVTT); \[\] when none. | | `platform_specific.rumble.caption_tracks[].language` | `string` or `null` | Language code (en, es, …). | | `platform_specific.rumble.caption_tracks[].label` | `string` or `null` | Rumble's label ("English (auto)"). | | `platform_specific.rumble.caption_tracks[].url` | `string` (uri) or `null` | WebVTT file URL. | | `platform_specific.rumble.caption_tracks[].is_auto_generated` | `boolean` or `null` | Generated by Rumble's speech recognition. | | `platform_specific.rumble.files` | `object[]` | Every file and stream Rumble's player offers, largest video first; \[\] when none. | | `platform_specific.rumble.files[].kind` | `string` or `null` | video (picture and sound), audio (sound only) or preview (the seek bar's low-resolution video, no sound). New values may be added.
One of `video`, `audio`, `preview` | | `platform_specific.rumble.files[].format` | `string` or `null` | mp4 or webm file, hls playlist (all qualities), tar (one HLS quality packed in one file; url is its playlist) or aac audio. New values may be added.
One of `mp4`, `webm`, `hls`, `tar`, `aac` | | `platform_specific.rumble.files[].url` | `string` (uri) or `null` | File or playlist URL. | | `platform_specific.rumble.files[].width` | `integer` or `null` | Width in pixels; null for audio and playlists. | | `platform_specific.rumble.files[].height` | `integer` or `null` | Height in pixels; null for audio and playlists. | | `platform_specific.rumble.files[].bitrate_kbps` | `integer` or `null` | Bitrate in kbit/s. | | `platform_specific.rumble.files[].size_bytes` | `integer` or `null` | File size in bytes; null when Rumble does not state it. | | `platform_specific.rumble.is_live_now` | `boolean` or `null` | A live stream on air now. | | `platform_specific.rumble.live_started_at` | `string` (date-time) or `null` | When a live stream went on air; null if never live. | | `platform_specific.rumble.scheduled_start_at` | `string` (date-time) or `null` | When a scheduled live stream is due to start; null otherwise. | | `platform_specific.rumble.concurrent_viewer_count` | `integer` or `null` | Viewers watching a live stream now; null when not live. | | `platform_specific.rumble.is_age_restricted` | `boolean` or `null` | Viewers must confirm their age to watch; null when the source does not show it. | | `platform_specific.rumble.author_follower_count` | `integer` or `null` | The channel's or user's follower count. Exact in Rumble's JSON (shorts, lists); a video's page cuts it to three significant digits (in approximate_fields). | | `platform_specific.instagram` | `object` or `null` | An Instagram post's own fields (core/post_instagram.go). | | `platform_specific.instagram.is_ai_generated` | `boolean` or `null` | Instagram shows its AI label (made or edited with AI); false when it shows none. | | `platform_specific.facebook` | `object` or `null` | A Facebook post's own fields. | | `platform_specific.facebook.feedback_id` | `string` or `null` | Facebook's feedback ID of the post: the thread its comments and reactions hang on. | | `platform_specific.facebook.video_id` | `string` or `null` | The video's ID (facebook.com/reel/\) on a video or reel post; null on other posts. | | `platform_specific.facebook.reactions` | `object[]` | Reactions by type, most first, as Facebook lists them under the post; \[\] when none. | | `platform_specific.facebook.reactions[].type` | `string` | Reaction type. New values may be added.
One of `like`, `love`, `care`, `haha`, `wow`, `sad`, `angry` | | `platform_specific.facebook.reactions[].count` | `integer` or `null` | How many people reacted with it. | | `platform_specific.facebook.shared_post` | `object` or `null` | The post this one reshares; null when it is not a reshare. | | `platform_specific.facebook.shared_post.id` | `string` or `null` | Its post ID. | | `platform_specific.facebook.shared_post.url` | `string` (uri) or `null` | Its URL. | | `platform_specific.facebook.shared_post.author` | `object` | Its author. | | `platform_specific.facebook.shared_post.author.id` | `string` or `null` | Platform-native account ID; null when only the handle is known. | | `platform_specific.facebook.shared_post.author.username` | `string` or `null` | Handle without the @; null when the account has none. | | `platform_specific.facebook.shared_post.author.display_name` | `string` or `null` | Display name shown on the profile. | | `platform_specific.facebook.shared_post.author.url` | `string` (uri) or `null` | Canonical profile URL. | | `platform_specific.facebook.shared_post.author.avatar_url` | `string` (uri) or `null` | Profile picture URL. Platform CDN URLs can expire within hours. | | `platform_specific.facebook.shared_post.author.is_verified` | `boolean` or `null` | Platform verification badge; null where the platform has no badge. | | `platform_specific.facebook.link` | `object` or `null` | The link card a post shares (an article, a site); null when none. | | `platform_specific.facebook.link.url` | `string` (uri) or `null` | The linked page, as the author shared it (Facebook's l.facebook.com redirect removed). | | `platform_specific.facebook.link.title` | `string` or `null` | The card's headline. | | `platform_specific.facebook.link.image_url` | `string` (uri) or `null` | The card's image. | | `approximate_fields` | `string[]` | JSON paths of values rounded by the platform or parsed from display text. A list path (external_links) means the list may be incomplete. \[\] when all exact. | | `unknown_fields` | `string[]` | JSON paths of lists whose \[\] means unknown rather than none (a source that cannot see them); null already means unknown for every other field. \[\] when every list is known. | ## Item errors Besides the [request-level errors](https://staging-fly.socialtap.dev/docs/errors/index.md) every endpoint can return, post endpoints return: `not_found`, `private_or_unavailable` (see [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). --- Source: https://staging-fly.socialtap.dev/resources/profile/ # Profile Schemas A public account (creator, channel, page, company or community) with its bio, links and follower metrics. ## Endpoints 5 endpoints return profiles on Instagram, Rumble, Threads, TikTok and YouTube. - [GETInstagram Profile API`/v1/instagram/profile`](https://staging-fly.socialtap.dev/docs/reference/instagram/profile/index.md) - [GETRumble Channel API`/v1/rumble/profile`](https://staging-fly.socialtap.dev/docs/reference/rumble/profile/index.md) - [GETThreads Profile API`/v1/threads/profile`](https://staging-fly.socialtap.dev/docs/reference/threads/profile/index.md) - [GETTikTok Profile API`/v1/tiktok/profile`](https://staging-fly.socialtap.dev/docs/reference/tiktok/profile/index.md) - [GETYouTube Channel API`/v1/youtube/profile`](https://staging-fly.socialtap.dev/docs/reference/youtube/profile/index.md) ## Schema Every endpoint above returns this object as `data`. The standard [`meta`](https://staging-fly.socialtap.dev/docs/reference/index.md#meta) comes with every response. | Field | Type | Description | | --- | --- | --- | | `platform` | `string` | Platform ID (tiktok, youtube, …). | | `id` | `string` | Platform-native account ID (string; YouTube UC…, Reddit without t2_). | | `username` | `string` or `null` | Handle without the @; null when the account has none. | | `display_name` | `string` or `null` | Display name, channel title or page name; null when empty or not exposed. | | `url` | `string` (uri) | Canonical profile URL. | | `type` | `string` | Coarse account kind, as the platform labels it; unknown when it does not (TikTok labels organizations only). New values may be added; treat unknown values as unknown.
One of `person`, `organization`, `community`, `unknown` | | `bio` | `string` or `null` | Profile bio, channel description or page about text; null when empty or not exposed. | | `avatar_url` | `string` (uri) or `null` | Profile picture URL (largest available). Platform CDN URLs can expire within hours. | | `banner_url` | `string` (uri) or `null` | Header or cover image URL. | | `is_verified` | `boolean` or `null` | Platform verification badge; null where the platform has no badge. | | `is_private` | `boolean` or `null` | True when the account's posts are not public. | | `category` | `string` or `null` | Platform category label (e.g. Musician, Media & Entertainment), as shown; not normalized. Often set only for business or creator accounts. | | `external_links` | `object[]` | Links in the bio or channel links; \[\] if none or not exposed. | | `external_links[].url` | `string` (uri) | Link target, absolute (https:// is added where the platform stores the link without a scheme). | | `external_links[].title` | `string` or `null` | Link label shown by the platform. | | `location` | `string` or `null` | Self-declared location text. | | `country_code` | `string` or `null` | ISO 3166-1 alpha-2 country the platform states for the account. | | `created_at` | `string` (date-time) or `null` | Account creation or join date, RFC 3339 UTC (some platforms give only the day: see approximate_fields). | | `metrics` | `object` | Counts at fetch time; null = hidden or not exposed, 0 = zero. | | `metrics.follower_count` | `integer` or `null` | Followers or subscribers. | | `metrics.following_count` | `integer` or `null` | Accounts this profile follows. | | `metrics.post_count` | `integer` or `null` | Posts or videos as the platform counts them on the profile (for a private account, posts the caller cannot see). | | `metrics.total_like_count` | `integer` or `null` | Likes received across all of the profile's posts (not likes given). | | `metrics.total_view_count` | `integer` or `null` | Lifetime views across all of the profile's posts. | | `platform_specific` | `object` | Fields only this platform has, under its ID (platform_specific.\); each null when unknown. | | `platform_specific.youtube` | `object` or `null` | A YouTube channel's own fields (core/profile_youtube.go). | | `platform_specific.youtube.keywords` | `string[]` | The channel's keywords as its owner wrote them, a quoted phrase as one keyword; \[\] when it has none (listed in unknown_fields when the source cannot see them). | | `platform_specific.youtube.is_official_artist_channel` | `boolean` or `null` | True for an Official Artist Channel (the note badge after its name), false for any other channel; null when the page shows no badge row (YouTube's built-in channels). | | `platform_specific.youtube.is_subscriber_count_hidden` | `boolean` or `null` | True when the channel hides its subscriber count (metrics.follower_count is then null; neither its About panel nor its header shows one), false when YouTube shows it; null when that cannot be told (no About panel or header seen). | | `platform_specific.youtube.is_business_email_listed` | `boolean` or `null` | True when the channel lists a business email (shown to signed-in users only; the address is not returned); null otherwise, since YouTube shows nothing for a channel without one. | | `platform_specific.instagram` | `object` or `null` | An Instagram account's own fields (core/profile_instagram.go). | | `platform_specific.instagram.fbid` | `string` or `null` | The account's Meta-wide ID (17841…), the one Meta's Instagram Graph API uses; id is Instagram's own user ID. Null when the source does not show it. | | `platform_specific.instagram.is_memorialized` | `boolean` or `null` | True when Instagram has memorialized the account (its owner has died), false otherwise; null when unknown. | | `platform_specific.instagram.is_story_live` | `boolean` or `null` | True when the account has a story up now (stories last 24 hours), false when it has none; null when unknown (Instagram does not say for private accounts). | | `platform_specific.instagram.is_highlights_shown` | `boolean` or `null` | True when the profile shows story highlights, false when it shows none; null when unknown (private accounts, or a source that does not show them). | | `platform_specific.instagram.pronouns` | `string[]` | The pronouns the owner chose to show on the profile, as shown; \[\] when none (listed in unknown_fields when the source cannot see them). | | `platform_specific.rumble` | `object` or `null` | A Rumble channel's or user's own fields (core/profile_rumble.go). | | `platform_specific.rumble.account_type` | `string` or `null` | channel for a Rumble channel (rumble.com/c/\), user for a Rumble user (rumble.com/user/\: the account that uploads, which can own channels). Null when unknown. New values may be added; treat unknown values as unknown.
One of `channel`, `user` | | `platform_specific.rumble.numeric_id` | `string` or `null` | Rumble's numeric ID of the channel or the user, in decimal (the channel's is the number in its rumble.com/c/c-\ link). Channels and users are numbered separately, so the same number can name a channel and a user: id is the unambiguous reference. Null when unknown. | | `platform_specific.rumble.user_id` | `string` or `null` | The id of the Rumble user that owns the channel (_u…, the id this endpoint answers for that user); for a user, its own id. Channels of one owner share it. Null when the page does not show it. | | `platform_specific.threads` | `object` or `null` | A Threads account's own fields (core/profile_threads.go). | | `platform_specific.threads.interest_tags` | `string[]` | Topic labels Threads shows on the profile, as shown; \[\] when it shows none (listed in unknown_fields when the source cannot see them). | | `platform_specific.threads.verified_link_urls` | `string[]` | The external_links URLs Threads marks as verified, in the same form; \[\] when none is marked (listed in unknown_fields when the source cannot see the links). | | `approximate_fields` | `string[]` | JSON paths of values rounded by the platform or parsed from display text (e.g. metrics.follower_count). A list path (external_links) means the list may be incomplete. \[\] when all exact. | | `unknown_fields` | `string[]` | JSON paths of lists whose \[\] means unknown rather than none (a source that cannot see them); null already means unknown for every other field. \[\] when every list is known. | ## Item errors Besides the [request-level errors](https://staging-fly.socialtap.dev/docs/errors/index.md) every endpoint can return, profile endpoints return: `not_found`, `private_or_unavailable` (see [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)). --- Source: https://staging-fly.socialtap.dev/resources/transcript/ # Transcript Schemas Timed transcript of a public video: the platform's caption track, or speech-to-text on its audio when there is none. ## Endpoints 5 endpoints return transcripts on Facebook, Instagram, Rumble, TikTok and YouTube. - [GETFacebook Video Transcript API`/v1/facebook/transcript`](https://staging-fly.socialtap.dev/docs/reference/facebook/transcript/index.md) - [GETInstagram Reels Transcript API`/v1/instagram/transcript`](https://staging-fly.socialtap.dev/docs/reference/instagram/transcript/index.md) - [GETRumble Transcript API`/v1/rumble/transcript`](https://staging-fly.socialtap.dev/docs/reference/rumble/transcript/index.md) - [GETTikTok Transcript API`/v1/tiktok/transcript`](https://staging-fly.socialtap.dev/docs/reference/tiktok/transcript/index.md) - [GETYouTube Transcript API`/v1/youtube/transcript`](https://staging-fly.socialtap.dev/docs/reference/youtube/transcript/index.md) ## Schema Every endpoint above returns this object as `data`. The standard [`meta`](https://staging-fly.socialtap.dev/docs/reference/index.md#meta) comes with every response. | Field | Type | Description | | --- | --- | --- | | `platform` | `string` | Platform the media is hosted on. | | `id` | `string` | Platform-native ID of the post (equals post.id). | | `url` | `string` (uri) | Canonical URL of the post. | | `language` | `string` or `null` | BCP-47 language of the returned transcript; null when unknown. | | `source` | `string` | captions = platform caption track; asr = speech-to-text on the audio. New values may be added.
One of `captions`, `asr` | | `is_auto_generated` | `boolean` | True when the track was machine-generated (platform auto-captions or ASR). | | `available_languages` | `string[]` | BCP-47 caption languages the platform offers for this post; \[\] if none or not listed. | | `duration_ms` | `integer` or `null` | Media duration in milliseconds; null when unknown. | | `segments` | `object[]` | Timed cues in order; never empty on success. | | `segments[].start_ms` | `integer` | Start time in milliseconds from the beginning of the media. | | `segments[].end_ms` | `integer` | End time in milliseconds from the beginning of the media. | | `segments[].text` | `string` | Cue text, whitespace-normalized. | | `text` | `string` | Full transcript: segment texts joined with single spaces. | | `platform_specific` | `object` | Fields only this platform has, under its ID (platform_specific.\); each null when unknown. | | `platform_specific.rumble` | `object` or `null` | A Rumble transcript's own fields. | | `platform_specific.rumble.track_label` | `string` or `null` | Rumble's label of the caption track used ("English (auto)"); null for speech-to-text. | | `platform_specific.rumble.caption_url` | `string` (uri) or `null` | The WebVTT file of the caption track used; null for speech-to-text. | | `platform_specific.rumble.caption_tracks` | `object[]` | Every caption track Rumble offers for the video (WebVTT); \[\] when none. | | `platform_specific.rumble.caption_tracks[].language` | `string` or `null` | Language code (en, es, …). | | `platform_specific.rumble.caption_tracks[].label` | `string` or `null` | Rumble's label ("English (auto)"). | | `platform_specific.rumble.caption_tracks[].url` | `string` (uri) or `null` | WebVTT file URL. | | `platform_specific.rumble.caption_tracks[].is_auto_generated` | `boolean` or `null` | Generated by Rumble's speech recognition. | ## Item errors Besides the [request-level errors](https://staging-fly.socialtap.dev/docs/errors/index.md) every endpoint can return, transcript endpoints return: `not_found`, `private_or_unavailable`, `no_transcript` (see [error codes](https://staging-fly.socialtap.dev/docs/errors/index.md)).