# TypeScript

SDKs

Typed methods for every endpoint, with retries, pagination and errors built in.

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

The TypeScript SDK for the SocialTap API: a method for every endpoint, typed models and errors, retries and pagination. Generated from the SocialTap catalog (version 1.509.29). Docs: [https://socialtap.dev/docs](https://socialtap.dev/docs).

## Install

Shell:

```sh
npm install socialtap
npm pkg set type=module   # the examples are ES modules (or name your file .mjs or .mts)
```

Zero runtime dependencies (`fetch`); Node 20.3+, Deno, Bun and Cloudflare Workers (server-side: the API key is a secret). ESM only: in Node, your project needs `"type": "module"` in its package.json (the second command above) or a `.mjs`/`.mts` file, else Node stops at the first `import` ("Cannot use import statement outside a module"). Run TypeScript directly with Node 22.18+ (`node app.ts`) or `npx tsx app.ts`. Params and models use the API's snake_case names; SDK objects (options, `credits`, errors) are camelCase.

`null` means unknown. With `fields`, results are typed `PartialModel<T>` (every member may be absent). A 2xx that is not the API's envelope (no `data`, not JSON) throws `APIDecodeError`; a `retry-after` longer than `MAX_RETRY_AFTER_S` throws `RetryAfterExceededError` instead of retrying early. Results keep the API's objects as sent, members this version doesn't model included. `Event` is a union narrowed by `type`; a job item's `params` are typed by its `endpoint`.

## Authenticate

The client reads your API key from `SOCIALTAP_API_KEY`, or takes it as an option. Requests send it in the `x-api-key` header. Get a key at [https://socialtap.dev/signup/](https://socialtap.dev/signup/): every account starts with 100 free credits.

## First call: Exact follower and like counts, bio and country

TypeScript:

```ts
import { SocialTap } from "socialtap";

const client = new SocialTap(); // apiKey defaults to process.env.SOCIALTAP_API_KEY

const res = await client.tiktok.profile.get({ handle: "nasa" });
console.log(res.data);
console.log(res.credits.charged, res.meta.request_id);
```

`data` is a `Profile` (example response, truncated):

JSON:

```json
{
  "approximate_fields": [],
  "avatar_url": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/e5143212a59c09a008bf50f487d54d1f~tplv-tiktokx-cropcenter:1080:1080.jpeg?dr=9640\u0026refresh_token=29840d4e\u0026x-expires=4070908800\u0026x-signature=SCRUBBED\u0026t=4d5b0474\u0026ps=13740610\u0026shp=a5d48078\u0026shcp=81f88b70\u0026idc=useast5",
  "banner_url": null,
  "bio": "Making the seemingly impossible, possible.✨",
  "category": null,
  "country_code": "US",
  "created_at": "2026-07-20T15:55:49Z",
  "display_name": "NASA",
  "external_links": [
    {
      "title": null,
      "url": "https://science.nasa.gov/feature/artemis-ii-preliminary-lunar-science-report"
    }
  ],
  "id": "7664638705177150477",
  "is_private": false,
  "is_verified": true,
  "location": null,
  "metrics": {
    "follower_count": 2152843,
    "following_count": 23,
    "post_count": 62,
    "total_like_count": 11486661,
    "total_view_count": null
  },
  "platform": "tiktok",
  "platform_specific": {
    "tiktok": {
      "display_name_changed_at": "2026-07-20T15:58:00Z",
      "friend_count": 17,
      "is_business_account": false,
      "is_shop_seller": false,
      "language": "en",
      "liked_post_count": null,
      "sec_uid": "MS4wLjABAAAAU9BRVzC8oCaegVnia8IbqWhPb_-dbU7s00Y3wS1_Nx8g5RUaYvyXrpejgjdxTwd6"
    }
  },
  "type": "organization",
  "unknown_fields": [],
  "url": "https://www.tiktok.com/@nasa",
  "username": "nasa"
}
```

Every result also carries the envelope `meta` (request ID, source, cache status, fetch time) and `credits` (charged by this request, and the balance after it when the API reports it). Failed requests and cache hits are free.

## Endpoints

| endpoint | method | cost |
| --- | --- | --- |
| `amazon.deal_products` | `client.amazon.dealProducts.list()` | 1 credit |
| `amazon.offers` | `client.amazon.offers.list()` | 1 credit |
| `amazon.product` | `client.amazon.product.get()` | 1 credit |
| `amazon.reviews` | `client.amazon.reviews.list()` | 1 credit |
| `amazon.search_products` | `client.amazon.searchProducts.list()` | 1 credit |
| `amazon.seller` | `client.amazon.seller.get()` | 1 credit |
| `amazon.shop` | `client.amazon.shop.get()` | 1 credit |
| `amazon.shop_lists` | `client.amazon.shopLists.list()` | 1 credit |
| `amazon.shop_posts` | `client.amazon.shopPosts.list()` | 1 credit |
| `amazon.top_products` | `client.amazon.topProducts.list()` | 1 credit |
| `applemusic.album` | `client.applemusic.album.get()` | 1 credit |
| `applemusic.artist` | `client.applemusic.artist.get()` | 1 credit |
| `applemusic.search_albums` | `client.applemusic.searchAlbums.list()` | 1 credit |
| `applemusic.search_artists` | `client.applemusic.searchArtists.list()` | 1 credit |
| `applemusic.search_playlists` | `client.applemusic.searchPlaylists.list()` | 1 credit |
| `applemusic.search_tracks` | `client.applemusic.searchTracks.list()` | 1 credit |
| `applemusic.top_albums` | `client.applemusic.topAlbums.list()` | 1 credit |
| `applemusic.top_playlists` | `client.applemusic.topPlaylists.list()` | 1 credit |
| `applemusic.top_tracks` | `client.applemusic.topTracks.list()` | 1 credit |
| `applemusic.track` | `client.applemusic.track.get()` | 1 credit |
| `bluesky.comments` | `client.bluesky.comments.list()` | 1 credit |
| `bluesky.post` | `client.bluesky.post.get()` | 1 credit |
| `bluesky.posts` | `client.bluesky.posts.list()` | 1 credit |
| `bluesky.profile` | `client.bluesky.profile.get()` | 1 credit |
| `bluesky.search_posts` | `client.bluesky.searchPosts.list()` | 1 credit |
| `facebook.ad` | `client.facebook.ad.get()` | 1 credit |
| `facebook.ad_transcript` | `client.facebook.adTranscript.get()` | 2 credits + 2 per started minute of media |
| `facebook.ads` | `client.facebook.ads.list()` | 1 credit |
| `facebook.comments` | `client.facebook.comments.list()` | 1 credit |
| `facebook.engagement` | `client.facebook.engagement.get()` | 1 credit |
| `facebook.event` | `client.facebook.event.get()` | 1 credit |
| `facebook.events` | `client.facebook.events.list()` | 1 credit |
| `facebook.group` | `client.facebook.group.get()` | 1 credit |
| `facebook.group_posts` | `client.facebook.groupPosts.list()` | 1 credit |
| `facebook.listing` | `client.facebook.listing.get()` | 1 credit |
| `facebook.photos` | `client.facebook.photos.list()` | 1 credit |
| `facebook.place_events` | `client.facebook.placeEvents.list()` | 1 credit |
| `facebook.post` | `client.facebook.post.get()` | 1 credit |
| `facebook.posts` | `client.facebook.posts.list()` | 1 credit |
| `facebook.profile` | `client.facebook.profile.get()` | 1 credit |
| `facebook.reels` | `client.facebook.reels.list()` | 1 credit |
| `facebook.replies` | `client.facebook.replies.list()` | 1 credit |
| `facebook.search_ads` | `client.facebook.searchAds.list()` | 1 credit |
| `facebook.search_advertisers` | `client.facebook.searchAdvertisers.list()` | 1 credit |
| `facebook.search_events` | `client.facebook.searchEvents.list()` | 1 credit |
| `facebook.search_groups` | `client.facebook.searchGroups.list()` | 1 credit + 1 per result filled in |
| `facebook.search_listings` | `client.facebook.searchListings.list()` | 1 credit |
| `facebook.search_places` | `client.facebook.searchPlaces.list()` | 1 credit |
| `facebook.search_posts` | `client.facebook.searchPosts.list()` | 1 credit + 1 per result filled in |
| `facebook.search_profiles` | `client.facebook.searchProfiles.list()` | 1 credit + 1 per result filled in |
| `facebook.transcript` | `client.facebook.transcript.get()` | 1 credit (captions) or 2 credits + 2 per started minute of media (speech-to-text) |
| `google.ad` | `client.google.ad.get()` | 1 credit |
| `google.ads` | `client.google.ads.list()` | 1 credit |
| `google.advertiser` | `client.google.advertiser.get()` | 1 credit |
| `google.search_ads` | `client.google.searchAds.list()` | 1 credit |
| `google.search_advertisers` | `client.google.searchAdvertisers.list()` | 1 credit |
| `google.search_suggestions` | `client.google.searchSuggestions.list()` | 1 credit |
| `instagram.comment` | `client.instagram.comment.get()` | 1 credit |
| `instagram.comments` | `client.instagram.comments.list()` | 1 credit |
| `instagram.embed` | `client.instagram.embed.get()` | 1 credit |
| `instagram.engagement` | `client.instagram.engagement.get()` | 1 credit |
| `instagram.hashtag` | `client.instagram.hashtag.get()` | 1 credit |
| `instagram.hashtag_posts` | `client.instagram.hashtagPosts.list()` | 1 credit |
| `instagram.highlights` | `client.instagram.highlights.list()` | 1 credit |
| `instagram.place` | `client.instagram.place.get()` | 1 credit |
| `instagram.place_posts` | `client.instagram.placePosts.list()` | 1 credit |
| `instagram.playlist_stories` | `client.instagram.playlistStories.list()` | 1 credit |
| `instagram.post` | `client.instagram.post.get()` | 1 credit |
| `instagram.posts` | `client.instagram.posts.list()` | 1 credit |
| `instagram.profile` | `client.instagram.profile.get()` | 1 credit |
| `instagram.reels` | `client.instagram.reels.list()` | 1 credit |
| `instagram.search_hashtags` | `client.instagram.searchHashtags.list()` | 1 credit |
| `instagram.search_places` | `client.instagram.searchPlaces.list()` | 1 credit + 1 per result filled in |
| `instagram.search_posts` | `client.instagram.searchPosts.list()` | 1 credit |
| `instagram.search_profiles` | `client.instagram.searchProfiles.list()` | 1 credit + 1 per result filled in |
| `instagram.search_sounds` | `client.instagram.searchSounds.list()` | 1 credit + 1 per result filled in |
| `instagram.search_suggestions` | `client.instagram.searchSuggestions.list()` | 1 credit |
| `instagram.similar_profiles` | `client.instagram.similarProfiles.list()` | 1 credit |
| `instagram.sound` | `client.instagram.sound.get()` | 1 credit |
| `instagram.sound_posts` | `client.instagram.soundPosts.list()` | 1 credit |
| `instagram.story` | `client.instagram.story.get()` | 1 credit |
| `instagram.topic` | `client.instagram.topic.get()` | 1 credit |
| `instagram.transcript` | `client.instagram.transcript.get()` | 1 credit (captions) or 2 credits + 2 per started minute of media (speech-to-text) |
| `instagram.trending_posts` | `client.instagram.trendingPosts.list()` | 1 credit |
| `instagram.username` | `client.instagram.username.get()` | 1 credit |
| `kick.post` | `client.kick.post.get()` | 1 credit |
| `kick.transcript` | `client.kick.transcript.get()` | 2 credits + 2 per started minute of media |
| `komi.page` | `client.komi.page.get()` | 1 credit |
| `kwai.post` | `client.kwai.post.get()` | 1 credit |
| `kwai.posts` | `client.kwai.posts.list()` | 1 credit |
| `kwai.profile` | `client.kwai.profile.get()` | 1 credit |
| `linkbio.profile` | `client.linkbio.profile.get()` | 1 credit |
| `linkedin.ad` | `client.linkedin.ad.get()` | 1 credit |
| `linkedin.post` | `client.linkedin.post.get()` | 1 credit |
| `linkedin.profile` | `client.linkedin.profile.get()` | 1 credit (public page or web search) or 5 credits (data partner) |
| `linkedin.search_ads` | `client.linkedin.searchAds.list()` | 1 credit |
| `linkedin.search_posts` | `client.linkedin.searchPosts.list()` | 1 credit + 1 per result filled in |
| `linkedin.search_profiles` | `client.linkedin.searchProfiles.list()` | 1 credit + 1 per result filled in |
| `linkme.profile` | `client.linkme.profile.get()` | 1 credit |
| `linktree.profile` | `client.linktree.profile.get()` | 1 credit |
| `pillar.page` | `client.pillar.page.get()` | 1 credit |
| `pinterest.board` | `client.pinterest.board.get()` | 1 credit |
| `pinterest.board_pins` | `client.pinterest.boardPins.list()` | 1 credit |
| `pinterest.boards` | `client.pinterest.boards.list()` | 1 credit |
| `pinterest.pin` | `client.pinterest.pin.get()` | 1 credit |
| `pinterest.pins` | `client.pinterest.pins.list()` | 1 credit |
| `pinterest.profile` | `client.pinterest.profile.get()` | 1 credit |
| `pinterest.search_pins` | `client.pinterest.searchPins.list()` | 1 credit |
| `pinterest.top_trends` | `client.pinterest.topTrends.list()` | 1 credit |
| `pinterest.topic` | `client.pinterest.topic.get()` | 1 credit |
| `pinterest.web_page` | `client.pinterest.webPage.get()` | 1 credit |
| `reddit.comments` | `client.reddit.comments.list()` | 1 credit |
| `reddit.engagement` | `client.reddit.engagement.get()` | 1 credit |
| `reddit.post` | `client.reddit.post.get()` | 1 credit |
| `reddit.posts` | `client.reddit.posts.list()` | 1 credit |
| `reddit.profile` | `client.reddit.profile.get()` | 1 credit |
| `reddit.profile_comments` | `client.reddit.profileComments.list()` | 1 credit |
| `reddit.search_comments` | `client.reddit.searchComments.list()` | 1 credit |
| `reddit.search_posts` | `client.reddit.searchPosts.list()` | 1 credit |
| `reddit.search_profiles` | `client.reddit.searchProfiles.list()` | 1 credit |
| `reddit.search_subreddits` | `client.reddit.searchSubreddits.list()` | 1 credit |
| `reddit.subreddit` | `client.reddit.subreddit.get()` | 1 credit |
| `reddit.subreddit_posts` | `client.reddit.subredditPosts.list()` | 1 credit |
| `reddit.transcript` | `client.reddit.transcript.get()` | 1 credit (captions) or 2 credits + 2 per started minute of media (speech-to-text) |
| `rumble.comments` | `client.rumble.comments.list()` | 1 credit |
| `rumble.engagement` | `client.rumble.engagement.get()` | 1 credit |
| `rumble.post` | `client.rumble.post.get()` | 1 credit |
| `rumble.posts` | `client.rumble.posts.list()` | 1 credit |
| `rumble.profile` | `client.rumble.profile.get()` | 1 credit |
| `rumble.search_posts` | `client.rumble.searchPosts.list()` | 1 credit |
| `rumble.transcript` | `client.rumble.transcript.get()` | 1 credit (captions) or 2 credits + 2 per started minute of media (speech-to-text) |
| `snapchat.comments` | `client.snapchat.comments.list()` | 1 credit |
| `snapchat.profile` | `client.snapchat.profile.get()` | 1 credit |
| `snapchat.spotlight` | `client.snapchat.spotlight.get()` | 1 credit |
| `soundcloud.artist` | `client.soundcloud.artist.get()` | 1 credit |
| `soundcloud.track` | `client.soundcloud.track.get()` | 1 credit |
| `soundcloud.tracks` | `client.soundcloud.tracks.list()` | 1 credit |
| `spotify.album` | `client.spotify.album.get()` | 1 credit |
| `spotify.artist` | `client.spotify.artist.get()` | 1 credit |
| `spotify.playlist` | `client.spotify.playlist.get()` | 1 credit |
| `spotify.playlist_tracks` | `client.spotify.playlistTracks.list()` | 1 credit |
| `spotify.podcast` | `client.spotify.podcast.get()` | 1 credit |
| `spotify.podcast_episodes` | `client.spotify.podcastEpisodes.list()` | 1 credit |
| `spotify.search_albums` | `client.spotify.searchAlbums.list()` | 1 credit |
| `spotify.search_artists` | `client.spotify.searchArtists.list()` | 1 credit |
| `spotify.search_playlists` | `client.spotify.searchPlaylists.list()` | 1 credit |
| `spotify.search_podcasts` | `client.spotify.searchPodcasts.list()` | 1 credit |
| `spotify.search_tracks` | `client.spotify.searchTracks.list()` | 1 credit |
| `spotify.track` | `client.spotify.track.get()` | 1 credit |
| `telegram.channel` | `client.telegram.channel.get()` | 1 credit |
| `telegram.channel_posts` | `client.telegram.channelPosts.list()` | 1 credit |
| `telegram.post` | `client.telegram.post.get()` | 1 credit |
| `telegram.search_channels` | `client.telegram.searchChannels.list()` | 1 credit + 1 per result filled in |
| `threads.comments` | `client.threads.comments.list()` | 1 credit |
| `threads.post` | `client.threads.post.get()` | 1 credit |
| `threads.posts` | `client.threads.posts.list()` | 1 credit |
| `threads.profile` | `client.threads.profile.get()` | 1 credit |
| `threads.search_posts` | `client.threads.searchPosts.list()` | 1 credit |
| `threads.search_profiles` | `client.threads.searchProfiles.list()` | 1 credit |
| `tiktok.ad` | `client.tiktok.ad.get()` | 1 credit |
| `tiktok.comment` | `client.tiktok.comment.get()` | 1 credit |
| `tiktok.comments` | `client.tiktok.comments.list()` | 1 credit |
| `tiktok.download` | `client.tiktok.download.get()` | 1 credit |
| `tiktok.effect` | `client.tiktok.effect.get()` | 1 credit |
| `tiktok.effect_posts` | `client.tiktok.effectPosts.list()` | 1 credit |
| `tiktok.engagement` | `client.tiktok.engagement.get()` | 1 credit |
| `tiktok.followers` | `client.tiktok.followers.list()` | 1 credit |
| `tiktok.following` | `client.tiktok.following.list()` | 1 credit |
| `tiktok.hashtag` | `client.tiktok.hashtag.get()` | 1 credit |
| `tiktok.hashtag_posts` | `client.tiktok.hashtagPosts.list()` | 1 credit |
| `tiktok.liked_posts` | `client.tiktok.likedPosts.list()` | 1 credit |
| `tiktok.live` | `client.tiktok.live.get()` | 1 credit |
| `tiktok.place_posts` | `client.tiktok.placePosts.list()` | 1 credit |
| `tiktok.playlist_posts` | `client.tiktok.playlistPosts.list()` | 1 credit |
| `tiktok.playlists` | `client.tiktok.playlists.list()` | 1 credit |
| `tiktok.post` | `client.tiktok.post.get()` | 1 credit |
| `tiktok.posts` | `client.tiktok.posts.list()` | 1 credit |
| `tiktok.profile` | `client.tiktok.profile.get()` | 1 credit |
| `tiktok.replies` | `client.tiktok.replies.list()` | 1 credit |
| `tiktok.search_ads` | `client.tiktok.searchAds.list()` | 1 credit |
| `tiktok.search_hashtags` | `client.tiktok.searchHashtags.list()` | 1 credit + 1 per result filled in |
| `tiktok.search_posts` | `client.tiktok.searchPosts.list()` | 1 credit |
| `tiktok.search_profiles` | `client.tiktok.searchProfiles.list()` | 1 credit + 1 per result filled in |
| `tiktok.search_sounds` | `client.tiktok.searchSounds.list()` | 1 credit + 1 per result filled in |
| `tiktok.search_suggestions` | `client.tiktok.searchSuggestions.list()` | 1 credit |
| `tiktok.search_top` | `client.tiktok.searchTop.list()` | 1 credit + 1 per result filled in |
| `tiktok.sound` | `client.tiktok.sound.get()` | 1 credit |
| `tiktok.sound_posts` | `client.tiktok.soundPosts.list()` | 1 credit |
| `tiktok.top_ads` | `client.tiktok.topAds.list()` | 1 credit |
| `tiktok.top_posts` | `client.tiktok.topPosts.list()` | 1 credit |
| `tiktok.transcript` | `client.tiktok.transcript.get()` | 1 credit (captions) or 2 credits + 2 per started minute of media (speech-to-text) |
| `tiktok.trending_hashtags` | `client.tiktok.trendingHashtags.list()` | 1 credit |
| `tiktok.trending_posts` | `client.tiktok.trendingPosts.list()` | 1 credit |
| `tiktokshop.product` | `client.tiktokshop.product.get()` | 1 credit |
| `tiktokshop.products` | `client.tiktokshop.products.list()` | 1 credit |
| `tiktokshop.reviews` | `client.tiktokshop.reviews.list()` | 1 credit |
| `tiktokshop.search_products` | `client.tiktokshop.searchProducts.list()` | 1 credit |
| `truthsocial.post` | `client.truthsocial.post.get()` | 1 credit |
| `truthsocial.posts` | `client.truthsocial.posts.list()` | 1 credit |
| `truthsocial.profile` | `client.truthsocial.profile.get()` | 1 credit |
| `twitch.profile` | `client.twitch.profile.get()` | 1 credit |
| `twitch.schedule` | `client.twitch.schedule.list()` | 1 credit |
| `twitch.transcript` | `client.twitch.transcript.get()` | 2 credits + 2 per started minute of media |
| `twitch.video` | `client.twitch.video.get()` | 1 credit |
| `twitch.videos` | `client.twitch.videos.list()` | 1 credit |
| `x.community` | `client.x.community.get()` | 1 credit |
| `x.post` | `client.x.post.get()` | 1 credit |
| `x.profile` | `client.x.profile.get()` | 1 credit |
| `x.search_communities` | `client.x.searchCommunities.list()` | 1 credit + 1 per result filled in |
| `x.search_hashtags` | `client.x.searchHashtags.list()` | 1 credit |
| `x.search_posts` | `client.x.searchPosts.list()` | 1 credit + 1 per result filled in |
| `x.search_profiles` | `client.x.searchProfiles.list()` | 1 credit + 1 per result filled in |
| `youtube.comments` | `client.youtube.comments.list()` | 1 credit |
| `youtube.engagement` | `client.youtube.engagement.get()` | 1 credit |
| `youtube.post` | `client.youtube.post.get()` | 1 credit |
| `youtube.posts` | `client.youtube.posts.list()` | 1 credit |
| `youtube.profile` | `client.youtube.profile.get()` | 1 credit |
| `youtube.transcript` | `client.youtube.transcript.get()` | 1 credit (captions) or 2 credits + 2 per started minute of media (speech-to-text) |

### Endpoints newer than this package

A version has a method for every endpoint the API had when it was released. For a newer one, update the package, or send the request yourself: the raw request adds your key and retries and raises errors like a method, and returns the status, headers and body (the envelope, `data` and `meta`). Each endpoint's docs page has its path and params.

TypeScript:

```ts
const res = await client.request({ method: "GET", path: "/v1/tiktok/profile", query: { handle: "nasa" } });
console.log(res.status, res.body);
```

## Errors

Every API error is an `APIError` subclass named after its code, with `status`, `code`, `retryable`, `remediation`, `requestId` and `creditsCharged` (0: errors are free). Codes this SDK version doesn't know arrive as a plain `APIError` with their `code`; no response at all is an `APIConnectionError` (`APITimeoutError` for timeouts).

TypeScript:

```ts
import { APIError, NotFoundError } from "socialtap";

try {
  await client.tiktok.profile.get({ handle: "nasa" });
} catch (err) {
  if (err instanceof NotFoundError) {
    // Check the link or ID. Do not retry.
  } else if (err instanceof APIError) {
    console.error(err.code, err.status, err.retryable, err.remediation, err.requestId);
  } else throw err;
}
```

| code | class | status | retried |
| --- | --- | --- | --- |
| `invalid_input` | `InvalidInputError` | 400 | no |
| `invalid_cursor` | `InvalidCursorError` | 400 | no |
| `unauthorized` | `UnauthorizedError` | 401 | no |
| `forbidden` | `ForbiddenError` | 403 | no |
| `insufficient_credits` | `InsufficientCreditsError` | 402 | no |
| `spend_limit_reached` | `SpendLimitReachedError` | 402 | no |
| `rate_limited` | `RateLimitedError` | 429 | yes |
| `request_too_large` | `RequestTooLargeError` | 413 | no |
| `method_not_allowed` | `MethodNotAllowedError` | 405 | no |
| `endpoint_retired` | `EndpointRetiredError` | 410 | no |
| `idempotency_key_reused` | `IdempotencyKeyReusedError` | 422 | no |
| `idempotency_key_in_progress` | `IdempotencyKeyInProgressError` | 409 | yes |
| `job_not_found` | `JobNotFoundError` | 404 | no |
| `unknown_endpoint` | `UnknownEndpointError` | 404 | no |
| `not_found` | `NotFoundError` | 404 | no |
| `private_or_unavailable` | `PrivateOrUnavailableError` | 403 | no |
| `no_transcript` | `NoTranscriptError` | 404 | no |
| `no_live` | `NoLiveError` | 404 | no |
| `no_media` | `NoMediaError` | 404 | no |
| `upstream_blocked` | `UpstreamBlockedError` | 503 | yes |
| `upstream_error` | `UpstreamError` | 502 | yes |
| `overloaded` | `OverloadedError` | 503 | yes |
| `internal` | `InternalError` | 500 | yes |

## Retries and timeouts

Retryable failures (the codes above, connection errors, timeouts) are retried 2 times by default with exponential backoff and jitter, honoring `retry-after`. A GET retry sends `max_age` set to the seconds since the first attempt: if the first response was lost in transit, the retry is served free from the cache. Timeouts are per attempt: 60 s by default, and 300 s for endpoints that may transcribe audio (`facebook.ad_transcript`, `facebook.transcript`, `instagram.transcript`, `kick.transcript`, `reddit.transcript`, `rumble.transcript`, `tiktok.transcript`, `twitch.transcript`, `youtube.transcript`), because speech-to-text on long media takes minutes, and for create-and-wait's long-polls. Most requests answer in a few seconds. A timeout you set, on the client or on one request, applies to every endpoint, and so does `maxRetries`.

## Pagination

List endpoints return one page (`data`, `page.has_more`, `page.next_cursor`). `iterate()` returns a `Paginator` (`for await` over items, `.byPage()` for pages) with two guards, `maxPages` and `maxCredits`; afterwards `pagesFetched`, `creditsCharged`, `stopReason` (`"end"`, or the guard that stopped it) and `endReason` (the API's `page.end_reason`) say what happened. Each page is checked against its catalog price before it is fetched, and an empty page with `has_more` is not the end.

TypeScript:

```ts
const items = client.amazon.dealProducts.iterate({}, { maxPages: 10, maxCredits: 100 });
for await (const item of items) {
  console.log(item);
}
console.log(items.pagesFetched, items.creditsCharged, items.stopReason, items.endReason);
```

## Account, API keys, jobs, webhooks and events

The gateway's own routes (account, usage, credit ledger, API keys, jobs, webhook endpoints, events) are free and have methods too, named from their operation IDs (`<noun>.<verb>`, or `<noun>.<sub>.<verb>` for a sub-resource). They answer the same envelope (`data`, `meta`, `credits`) with their own `meta`; cursor-paged list routes have `iterate` like list endpoints. Every POST sends an `idempotency-key` header (yours, or a new UUID kept for the call's retries), so it is retried like a GET and a retry never repeats the work; `replayed` says when the API answered from an earlier request with the same key, and `status` is 202 when accepted work continues. Each route needs a key with the scope below (a key never gets a scope its creator lacks).

TypeScript:

```ts
const res = await client.account.get();
console.log(res.data, res.meta.request_id);
```

| operation | method | scope |
| --- | --- | --- |
| `account.get` | `client.account.get()` | `account:read` |
| `account.update` | `client.account.update(body)` | `admin` |
| `usage.list` | `client.usage.list(params)` | `account:read` |
| `usage.rate.list` | `client.usage.rate.list(params)` | `account:read` |
| `credit_transactions.list` | `client.creditTransactions.list(params)` | `account:read` |
| `billing.checkout.get` | `client.billing.checkout.get(id)` | `account:read` |
| `billing.subscription.get` | `client.billing.subscription.get()` | `account:read` |
| `billing.auto_topup.get` | `client.billing.autoTopup.get()` | `account:read` |
| `billing.purchases.list` | `client.billing.purchases.list(params)` | `account:read` |
| `billing.purchases.receipt` | `client.billing.purchases.receipt(id)` | `account:read` |
| `requests.list` | `client.requests.list(params)` | `account:read` |
| `requests.get` | `client.requests.get(id)` | `account:read` |
| `requests.get_response` | `client.requests.getResponse(id)` | `account:read` |
| `api_keys.create` | `client.apiKeys.create(body)` | `admin` |
| `api_keys.list` | `client.apiKeys.list(params)` | `account:read` |
| `api_keys.get` | `client.apiKeys.get(id)` | `account:read` |
| `api_keys.update` | `client.apiKeys.update(id, body)` | `admin` |
| `api_keys.rotate_secret` | `client.apiKeys.rotateSecret(id, body)` | `admin` |
| `api_keys.revoke` | `client.apiKeys.revoke(id)` | `admin` |
| `jobs.create` | `client.jobs.create(body, params)` | `jobs` |
| `jobs.list` | `client.jobs.list(params)` | `jobs:read` |
| `jobs.get` | `client.jobs.get(id, params)` | `jobs:read` |
| `jobs.results.list` | `client.jobs.results.list(id, params)` | `jobs:read` |
| `jobs.cancel` | `client.jobs.cancel(id)` | `jobs` |
| `webhook_endpoints.create` | `client.webhookEndpoints.create(body)` | `admin` |
| `webhook_endpoints.list` | `client.webhookEndpoints.list(params)` | `account:read` |
| `webhook_endpoints.get` | `client.webhookEndpoints.get(id)` | `account:read` |
| `webhook_endpoints.update` | `client.webhookEndpoints.update(id, body)` | `admin` |
| `webhook_endpoints.delete` | `client.webhookEndpoints.delete(id)` | `admin` |
| `webhook_endpoints.rotate_secret` | `client.webhookEndpoints.rotateSecret(id, body)` | `admin` |
| `webhook_endpoints.test` | `client.webhookEndpoints.test(id)` | `admin` |
| `webhook_endpoints.deliveries.list` | `client.webhookEndpoints.deliveries.list(id, params)` | `account:read` |
| `events.list` | `client.events.list(params)` | `account:read` |
| `events.get` | `client.events.get(id)` | `account:read` |
| `events.redeliver` | `client.events.redeliver(id, body)` | `admin` |
| `notification_preferences.get` | `client.notificationPreferences.get()` | `account:read` |
| `notification_preferences.update` | `client.notificationPreferences.update(body)` | `admin` |

### Jobs and webhooks

Many requests (or one long list walk) run as a background job at the synchronous prices; failed items are free. The create-and-wait helper creates one, long-polls it until it ends and returns every result; one Idempotency-Key covers the create's retries, so a retry never runs a job twice. Webhook deliveries are signed per Standard Webhooks: verify the raw body with your endpoint's secret before trusting it, and deduplicate on the event ID.

TypeScript:

```ts
const { job, results } = await client.jobs.createAndWait({ requests: [{ custom_id: "row-1", endpoint: "tiktok.profile", params: { handle: "nasa" } }] });
for (const r of results) console.log(r.custom_id, r.status, r.error?.code);

// A webhook delivery: the raw body, the request headers, your endpoint's secret.
const event = await client.webhooks.verify(rawBody, request.headers, process.env.SOCIALTAP_WEBHOOK_SECRET!);
```

## Tools for AI agents

The SDK hands the SocialTap MCP server's tools to a model: each tool's name, description (its price and rules included) and input JSON Schema, and a call that sends one stateless MCP request with your key. A tool behaves exactly as it does for an MCP client: concise results (`detail: "full"` for every field), `next_cursor` to continue, and failures returned as the tool's output (`error` with `retryable` and `remediation`) so the model can act on them. Transport failures raise as for any request, and tools that may be repeated are retried.

TypeScript:

```ts
// The Vercel AI SDK (ai is an optional peer dependency): the default tools (call_endpoint reaches every endpoint),
// or socialtapTools({ tools: [...] }) / socialtapTools({ tools: "all" }).
import { generateText, stepCountIs } from "ai";
import { socialtapTools } from "socialtap/ai";

// model: any AI SDK model; prompt: the task. The model calls the tools, the SDK runs them with your key.
const { text } = await generateText({ model, tools: socialtapTools(), stopWhen: stepCountIs(5), prompt });

// Any other tool-calling API: hand TOOLS (name, description, inputSchema) to the model, run its calls with callTool.
import { SocialTap, TOOLS, callTool } from "socialtap";

const r = await callTool(new SocialTap(), "find_endpoint", { query: "Exact follower and like counts, bio and country" });
console.log(r.isError, r.output);
```

| tool | what it does |
| --- | --- |
| `find_endpoint` | Find an endpoint |
| `get_post` | Post by link |
| `list_posts` | Posts of a profile |
| `get_profile` | Profile by handle |
| `list_comments` | Comments of a post |
| `get_transcript` | Transcript by link |
| `get_engagement` | Engagement by handle |
| `search` | Search by query |
| `call_endpoint` | Call any endpoint |
| `search_docs` | Search the docs |
| `read_doc` | Read a docs page |
| `get_account` | Get account balance |
| `get_story` | Story by link |
| `list_community_posts` | Community posts |
| `list_hashtag_posts` | Hashtag posts |
| `list_place_posts` | Place posts |
| `list_playlist_posts` | Playlist posts |
| `list_reels` | Reels of a profile |
| `list_sound_posts` | Sound posts |
| `list_top_posts` | Top posts |
| `list_trending_posts` | Trending posts |
| `list_channel_posts` | Channel posts of a profile |
| `list_effect_posts` | Effect posts |
| `list_liked_posts` | Liked posts of a profile |
| `list_photos` | Photos of a profile |
| `list_pins` | Pins of a profile |
| `list_playlist_stories` | Playlist stories |
| `list_playlist_tracks` | Playlist tracks |
| `list_podcast_episodes` | Podcast episodes |
| `list_shop_posts` | Shop posts of a profile |
| `list_tracks` | Tracks of a profile |
| `list_videos` | Videos of a profile |
| `get_seller` | Seller by handle |
| `get_shop` | Shop by handle |
| `list_followers` | Followers of a profile |
| `list_following` | Following of a profile |
| `list_similar_profiles` | Similar profiles of a profile |
| `get_comment` | Comment by link |
| `list_replies` | Replies of a post |
| `list_profile_comments` | Profile comments |
| `get_ad_transcript` | Ad transcript by link |
| `get_ad` | Ad by link |
| `list_ads` | Ads of a profile |
| `list_top_ads` | Top ads |
| `get_hashtag` | Hashtag by link |
| `list_trending_hashtags` | Trending hashtags |
| `get_community` | Community by link |
| `get_product` | Product by link |
| `get_album` | Album by link |
| `get_playlist` | Playlist by link |
| `get_sound` | Sound by link |
| `get_topic` | Topic by link |
| `get_download` | Download by link |
| `get_effect` | Effect by link |
| `get_embed` | Embed by link |
| `get_event` | Event by link |
| `get_live` | Live by handle |
| `get_place` | Place by link |
| `get_podcast` | Podcast by link |
| `get_username` | Username by handle |
| `get_web_page` | Web page by link |
| `list_reviews` | Reviews of a product |
| `list_boards` | Boards of a profile |
| `list_deal_products` | Deal products |
| `list_events` | Events of a profile |
| `list_highlights` | Highlights of a profile |
| `list_offers` | Offers of a product |
| `list_place_events` | Place events |
| `list_playlists` | Playlists of a profile |
| `list_products` | Products of a profile |
| `list_schedule` | Schedule of a profile |
| `list_shop_lists` | Shop lists of a profile |
| `list_top_albums` | Top albums |
| `list_top_playlists` | Top playlists |
| `list_top_products` | Top products |
| `list_top_trends` | Top trends |
| `create_job` | Create a background job |
| `get_job` | Get a background job |
| `list_job_results` | List a job's results |

## CLI

The package's bin: every endpoint as a command (JSON on stdout; a list walk with `--all` prints one item per line), the agent tools, and `init`, which sets up coding agents.

Shell:

```sh
npx socialtap tiktok profile --handle nasa
npx socialtap endpoints          # every command, its params and price
npx socialtap tool find_endpoint '{"query":"Exact follower and like counts, bio and country"}'
npx socialtap init               # Claude Code, Codex, Cursor, Gemini CLI: the Agent Skill + the MCP server
```

`init` installs the Agent Skill the site publishes (its discovery index, digest checked) into each agent's user skills directory and registers the MCP server with your key (`--api-key`, or `SOCIALTAP_API_KEY`) in each agent's own user config: Claude Code through `claude mcp add-json`, Codex's `config.toml`, Cursor's `mcp.json`, Gemini CLI's `settings.json`. It sets up every agent it finds (`--agent <id>` to choose) and is safe to run again.

## Versioning

The version is derived from the API contract: major = API version, minor = the contract's revision (it grows with every additive change, a new endpoint included), patch = the SDK generator's revision. Questions and bug reports: [support@socialtap.dev](mailto:support@socialtap.dev).

---

[HTML version](https://socialtap.dev/docs/sdks/typescript/) · [Docs index for AI agents](https://socialtap.dev/llms.txt) · [All docs in one file](https://socialtap.dev/llms-full.txt)
