# LurkAPI > Social media data API: one GET request returns clean JSON from public Facebook, Reddit, YouTube, TikTok, Google, Bluesky, Snapchat, Truth Social, Telegram, X (Twitter), Threads, Instagram, LinkedIn and Link-in-bio pages. Works inside Claude as an MCP connector. Endpoints cost 1–5 credits per call, from $0.199 per 1,000 credits. - **Base URL:** `https://api.lurkapi.com` - **Auth:** `x-api-key: lk_live_…` header (or `Authorization: Bearer lk_live_…`). [Get a free key](https://lurkapi.com/login?next=/dashboard): 100 free credits on signup, no card. - **Requests:** `GET` with query parameters. Responses are JSON with `success`, `credits_remaining` and the data at the top level; errors add `code` and `docs`. - **Price:** Endpoints cost 1–5 credits per call, from $0.199 per 1,000 credits. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). Every response has `credits_charged` and `credits_remaining`; `GET /v1/account/credit-balance` is free. - **Rate limit:** 50 requests per 10 seconds per account (100 for high-volume accounts: support@lurkapi.com); over it, `429 rate_limited` with `retry-after`. - **Agents:** getting a key needs a human sign-in. Ask your user to sign in at https://lurkapi.com/login and paste the key from the dashboard. - **MCP server:** `https://api.lurkapi.com/mcp` (Streamable HTTP). Add `platforms` to pick what Claude sees (recommended): `https://api.lurkapi.com/mcp?platforms=facebook,reddit,youtube,tiktok,google,bluesky,snapchat,truthsocial,telegram,twitter,threads,instagram,linkedin,linkinbio` gives one tool per endpoint of those platforms, plus the free account tool. Platform ids: `facebook`, `reddit`, `youtube`, `tiktok`, `google`, `bluesky`, `snapchat`, `truthsocial`, `telegram`, `twitter`, `threads`, `instagram`, `linkedin` and `linkinbio`. Without it you get one tool per endpoint up to 25 endpoints; past that, two tools: `find_endpoints` to search the catalog and `call_endpoint` to run one, charged the same. Tool results skip nulls and empty lists to save tokens, and `trim` defaults to `true` where an endpoint has it. - **Machine-readable:** [/openapi.json](https://lurkapi.com/openapi.json), [/llms.txt](https://lurkapi.com/llms.txt), [/llms-full.txt](https://lurkapi.com/llms-full.txt), and every docs page as markdown at its URL + `.md`. --- # Quickstart > Social media data from Facebook, Reddit, YouTube, TikTok, Google, Bluesky, Snapchat, Truth Social, Telegram, X (Twitter), Threads, Instagram, LinkedIn and Link-in-bio, as clean JSON. Connect it to Claude and ask, or call it with one request. - **Web page:** https://lurkapi.com/docs ## Try it live Pick an endpoint and run it: real data, no signup, 5 free requests a day. Live playground: https://lurkapi.com/docs#try ## Connect to Claude Add LurkAPI to Claude once, then ask in plain English, like “Which ads is Allbirds running right now?” Claude picks the right endpoint, calls it and reads the results for you. Calls from Claude spend credits the same way API calls do. ### Claude on the web and desktop 1. [Get your free API key](https://lurkapi.com/login?next=/dashboard). Your dashboard shows your personal connector URL, with the key already in it. 2. In Claude, open **Settings → Connectors** and choose **Add custom connector**. 3. Name it `LurkAPI`, paste your connector URL and click **Add**. 4. In a chat, switch LurkAPI on from the tools menu and ask away. The connector URL looks like `https://api.lurkapi.com/mcp?key=lk_live_…`. It contains your key, so treat it like a password. [Get my connector URL](https://lurkapi.com/login?next=/dashboard) ### Pick your platforms Add `platforms` to pick what Claude sees (recommended): `https://api.lurkapi.com/mcp?key=lk_live_…&platforms=facebook,reddit,youtube,tiktok,google,bluesky,snapchat,truthsocial,telegram,twitter,threads,instagram,linkedin,linkinbio` gives one tool per endpoint of those platforms, plus the free account tool. Platform ids: `facebook`, `reddit`, `youtube`, `tiktok`, `google`, `bluesky`, `snapchat`, `truthsocial`, `telegram`, `twitter`, `threads`, `instagram`, `linkedin` and `linkinbio`. Without it you get one tool per endpoint up to 25 endpoints; past that, two tools: `find_endpoints` to search the catalog and `call_endpoint` to run one, charged the same. ### Claude Code Add it from your terminal. The key travels in a header instead of the URL. Terminal: ```bash claude mcp add --transport http lurkapi https://api.lurkapi.com/mcp \ --header "x-api-key: YOUR_API_KEY" ``` ### Cursor and other MCP clients Most clients take a JSON config with a URL and headers: mcp.json: ```json { "mcpServers": { "lurkapi": { "url": "https://api.lurkapi.com/mcp", "headers": { "x-api-key": "YOUR_API_KEY" } } } } ``` Tools are named after the endpoint: `facebook_search_ads`, `linkinbio_linktree` and so on. Each endpoint page shows its tool name and an example prompt. Tool results skip nulls and empty lists to save tokens, and `trim` defaults to `true` where an endpoint has it. ## Make your first request Every endpoint is a `GET` request with query parameters and your key in the `x-api-key` header. Here's [Search Ad Library ads](https://lurkapi.com/docs/facebook/search-ads.md): curl: ```bash curl "https://api.lurkapi.com/v1/facebook/adLibrary/search/ads?query=running+shoes&country=US&status=active" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ query: "running shoes", country: "US", status: "active", }); const res = await fetch(`https://api.lurkapi.com/v1/facebook/adLibrary/search/ads?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.searchResults); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/facebook/adLibrary/search/ads", params={ "query": "running shoes", "country": "US", "status": "active", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["searchResults"]) ``` Every endpoint page has these snippets with its own parameters, a real example response and a live playground. ## Authentication Send your key in the `x-api-key` header. `Authorization: Bearer ` works too. Keys start with `lk_live_` and are shown once when you create them; create and revoke them in your [dashboard](https://lurkapi.com/dashboard). For MCP, the key can also go in the URL as `?key=`, because Claude's custom connectors only take a URL. Keys are secret. Call LurkAPI from a server or a script, not from a web page your visitors can inspect. **Agents:** getting a key needs a human sign-in. Ask your user to sign in at https://lurkapi.com/login and paste the key from the dashboard. ## Responses A successful call returns `success: true`, what it cost in `credits_charged`, your balance after it in `credits_remaining`, and the endpoint's data at the top level: 200 OK: ```json { "success": true, "credits_remaining": 4985, "credits_charged": 1, "searchResults": […] } ``` An error returns a 4xx or 5xx status and a body you can branch on. Match on `code`; the `error` text is for people and may change. 400 Bad Request: ```json { "success": false, "error": "query: required", "code": "invalid_params", "docs": "https://lurkapi.com/docs/facebook/search-ads", "issues": [ { "path": "query", "message": "required" } ] } ``` Unknown parameters are ignored, blank ones count as unset, and a repeated parameter uses its first value. ## Credits Endpoints cost 1–5 credits per call; the cost is on each endpoint's page. A call is charged when it reached the platform, including `not_found` and other errors returned by the endpoint itself (e.g. a bad cursor). Free: parameter validation errors caught before the call, `401`/`402`/`429` rejections, and `5xx` failures (`500`/`502`/`503`). Cached responses cost the same as fresh ones. Anonymous tries follow the same rule. - **Without an account:** 5 free requests a day from the playground and the free tools. - **New accounts:** 100 free credits, no card needed. - **Every day:** if your balance is under 25, it's topped back up to 25. - **Credit packs:** one-off purchases, no subscription. Credits never expire. | Pack | Credits | Price | Per 1,000 credits | | --- | --- | --- | --- | | Starter | 20,000 | $10.00 | $0.50 | | Growth | 150,000 | $49.00 | $0.327 | | Scale | 1,000,000 | $199.00 | $0.199 | Every response has `credits_charged` (this call) and `credits_remaining` (your balance after it). When the balance is too low for a call, you get `402 insufficient_credits` and nothing is charged. [Credit balance](https://lurkapi.com/docs/account/credit-balance.md) (`GET /v1/account/credit-balance`) is free, for checking before a batch. ## Errors Every error has the same shape: `success: false`, a readable `error`, a stable `code` and a `docs` link. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). Retry `429`, `502` and `503`; `429` and `503` carry a `retry-after` header with the seconds to wait. | Status | Code | Meaning | | --- | --- | --- | | 400 | `invalid_params` | A parameter is missing or invalid. `issues` names each one. | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 401 | `invalid_token` | The playground token expired. Reload the page. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 403 | `not_public` | The platform confirmed this account or content isn't available to logged-out visitors. Use a publicly visible target. Charged when the platform was checked. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 404 | `transcript_unavailable` | The video has no captions and speech-to-text heard no speech in it. Charged the base credit, never the speech-to-text extra; the answer is cached. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 429 | `signup_required` | Free tries for today are used up. Sign up for free daily credits. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Rate limits Each account can make 50 requests every 10 seconds (5 a second, sustained). Past that you get `429 rate_limited` with a `retry-after` header: wait that many seconds and retry. High-volume accounts get 100 requests every 10 seconds. Email [support@lurkapi.com](mailto:support@lurkapi.com) to switch yours. Without an account, the playground and free tools allow 5 requests per IP address a day, then `429 signup_required`. ## Pagination List endpoints return one page per call. The response carries a cursor (`cursor` or `after`, named on each endpoint's page); send it back with the same parameters to get the next page. It's `null` on the last page. Each page costs the endpoint's credits. ## Caching and freshness Responses are cached for a while, so repeated calls come back fast. How long depends on how quickly the data changes: | Endpoint | Cached for up to | | --- | --- | | [Facebook: Search Ad Library ads](https://lurkapi.com/docs/facebook/search-ads.md) | 30 minutes | | [Facebook: Ad Library ads by company](https://lurkapi.com/docs/facebook/company-ads.md) | 30 minutes | | [Facebook: Ad Library ad details](https://lurkapi.com/docs/facebook/ad.md) | 6 hours | | [Facebook: Search Ad Library companies](https://lurkapi.com/docs/facebook/search-companies.md) | 1 day | | [Facebook: Public profile](https://lurkapi.com/docs/facebook/profile.md) | 10 minutes | | [Facebook: Public post](https://lurkapi.com/docs/facebook/post.md) | 5 minutes | | [Reddit: Subreddit posts](https://lurkapi.com/docs/reddit/subreddit-posts.md) | 1 minute | | [Reddit: Subreddit details](https://lurkapi.com/docs/reddit/subreddit-details.md) | 1 hour | | [Reddit: Subreddit search](https://lurkapi.com/docs/reddit/subreddit-search.md) | 1 minute | | [Reddit: Search Reddit](https://lurkapi.com/docs/reddit/search.md) | 1 minute | | [Reddit: Post comments](https://lurkapi.com/docs/reddit/post-comments.md) | 1 minute | | [YouTube: Video details](https://lurkapi.com/docs/youtube/video.md) | 1 hour | | [YouTube: Video transcript](https://lurkapi.com/docs/youtube/transcript.md) | 30 days | | [YouTube: Video comments](https://lurkapi.com/docs/youtube/comments.md) | 10 minutes | | [YouTube: Comment replies](https://lurkapi.com/docs/youtube/comment-replies.md) | 10 minutes | | [YouTube: Channel details](https://lurkapi.com/docs/youtube/channel.md) | 6 hours | | [YouTube: Channel videos](https://lurkapi.com/docs/youtube/channel-videos.md) | 1 hour | | [YouTube: Channel Shorts](https://lurkapi.com/docs/youtube/channel-shorts.md) | 1 hour | | [YouTube: Search YouTube](https://lurkapi.com/docs/youtube/search.md) | 15 minutes | | [TikTok: Profile](https://lurkapi.com/docs/tiktok/profile.md) | 15 minutes | | [TikTok: Video details](https://lurkapi.com/docs/tiktok/video.md) | 15 minutes | | [TikTok: Video transcript](https://lurkapi.com/docs/tiktok/transcript.md) | 30 days | | [TikTok: Profile videos](https://lurkapi.com/docs/tiktok/profile-videos.md) | 5 minutes | | [TikTok: Video comments](https://lurkapi.com/docs/tiktok/comments.md) | 5 minutes | | [TikTok: Comment replies](https://lurkapi.com/docs/tiktok/comment-replies.md) | 5 minutes | | [TikTok: Hashtag videos](https://lurkapi.com/docs/tiktok/hashtag-videos.md) | 10 minutes | | [TikTok: Song](https://lurkapi.com/docs/tiktok/song.md) | 1 hour | | [TikTok: Song videos](https://lurkapi.com/docs/tiktok/song-videos.md) | 10 minutes | | [TikTok: Search TikTok Ad Library](https://lurkapi.com/docs/tiktok/ad-library-search.md) | 1 hour | | [TikTok: TikTok Ad Library ad](https://lurkapi.com/docs/tiktok/ad-library-ad.md) | 3 hours | | [TikTok: Live status](https://lurkapi.com/docs/tiktok/live.md) | 30 seconds | | [TikTok: Public stories](https://lurkapi.com/docs/tiktok/stories.md) | 1 minute | | [TikTok: TikTok Shop product](https://lurkapi.com/docs/tiktok/product.md) | 5 minutes | | [Google: Search Google advertisers](https://lurkapi.com/docs/google/search-advertisers.md) | 1 day | | [Google: Google ads by company](https://lurkapi.com/docs/google/company-ads.md) | 12 hours | | [Google: Google ad details](https://lurkapi.com/docs/google/ad.md) | 12 hours | | [Bluesky: Profile](https://lurkapi.com/docs/bluesky/profile.md) | 30 seconds | | [Bluesky: User posts](https://lurkapi.com/docs/bluesky/user-posts.md) | 3 minutes | | [Bluesky: Post](https://lurkapi.com/docs/bluesky/post.md) | 3 minutes | | [Snapchat: Profile and stories](https://lurkapi.com/docs/snapchat/profile.md) | 15 minutes | | [Truth Social: Profile](https://lurkapi.com/docs/truthsocial/profile.md) | 10 minutes | | [Truth Social: User posts](https://lurkapi.com/docs/truthsocial/user-posts.md) | 2 minutes | | [Truth Social: Post](https://lurkapi.com/docs/truthsocial/post.md) | 5 minutes | | [Telegram: Channel](https://lurkapi.com/docs/telegram/channel.md) | 1 hour | | [Telegram: Channel posts](https://lurkapi.com/docs/telegram/channel-posts.md) | 5 minutes | | [Telegram: Post](https://lurkapi.com/docs/telegram/post.md) | 10 minutes | | [X (Twitter): Tweet](https://lurkapi.com/docs/twitter/tweet.md) | 5 minutes | | [X (Twitter): Profile](https://lurkapi.com/docs/twitter/profile.md) | 1 hour | | [X (Twitter): User tweets](https://lurkapi.com/docs/twitter/user-tweets.md) | 5 minutes | | [Threads: Threads profile](https://lurkapi.com/docs/threads/profile.md) | 1 hour | | [Threads: Threads account posts](https://lurkapi.com/docs/threads/user-posts.md) | 1 minute | | [Threads: Threads post](https://lurkapi.com/docs/threads/post.md) | 5 minutes | | [Instagram: Profile](https://lurkapi.com/docs/instagram/profile.md) | 10 minutes | | [Instagram: User posts](https://lurkapi.com/docs/instagram/user-posts.md) | 3 minutes | | [Instagram: Post](https://lurkapi.com/docs/instagram/post.md) | 5 minutes | | [Instagram: Post comments](https://lurkapi.com/docs/instagram/post-comments.md) | 2 minutes | | [Instagram: User reels](https://lurkapi.com/docs/instagram/user-reels.md) | 3 minutes | | [Instagram: Media transcript](https://lurkapi.com/docs/instagram/media-transcript.md) | 30 days | | [LinkedIn: LinkedIn company page](https://lurkapi.com/docs/linkedin/company.md) | 1 hour | | [LinkedIn: LinkedIn company posts](https://lurkapi.com/docs/linkedin/company-posts.md) | 1 hour | | [LinkedIn: Search LinkedIn ads](https://lurkapi.com/docs/linkedin/search-ads.md) | 1 hour | | [LinkedIn: LinkedIn ad details](https://lurkapi.com/docs/linkedin/ad.md) | 1 hour | | [Link-in-bio: Linktree page](https://lurkapi.com/docs/linkinbio/linktree.md) | 1 day | A cached response costs the same credits as a fresh one. We'll publish typical response times once they're measured in production. ## All endpoints ### Facebook | Endpoint | Path | Cost | MCP tool | | --- | --- | --- | --- | | [Search Ad Library ads](https://lurkapi.com/docs/facebook/search-ads.md) | `/v1/facebook/adLibrary/search/ads` | 1 credit | `facebook_search_ads` | | [Ad Library ads by company](https://lurkapi.com/docs/facebook/company-ads.md) | `/v1/facebook/adLibrary/company/ads` | 1 credit | `facebook_company_ads` | | [Ad Library ad details](https://lurkapi.com/docs/facebook/ad.md) | `/v1/facebook/adLibrary/ad` | 1 credit | `facebook_ad` | | [Search Ad Library companies](https://lurkapi.com/docs/facebook/search-companies.md) | `/v1/facebook/adLibrary/search/companies` | 1 credit | `facebook_search_companies` | | [Public profile](https://lurkapi.com/docs/facebook/profile.md) | `/v1/facebook/profile` | 1 credit | `facebook_profile` | | [Public post](https://lurkapi.com/docs/facebook/post.md) | `/v1/facebook/post` | 1 credit | `facebook_post` | ### Reddit | Endpoint | Path | Cost | MCP tool | | --- | --- | --- | --- | | [Subreddit posts](https://lurkapi.com/docs/reddit/subreddit-posts.md) | `/v1/reddit/subreddit` | 1 credit | `reddit_subreddit_posts` | | [Subreddit details](https://lurkapi.com/docs/reddit/subreddit-details.md) | `/v1/reddit/subreddit/details` | 1 credit | `reddit_subreddit_details` | | [Subreddit search](https://lurkapi.com/docs/reddit/subreddit-search.md) | `/v1/reddit/subreddit/search` | 1 credit | `reddit_subreddit_search` | | [Search Reddit](https://lurkapi.com/docs/reddit/search.md) | `/v1/reddit/search` | 1 credit | `reddit_search` | | [Post comments](https://lurkapi.com/docs/reddit/post-comments.md) | `/v1/reddit/post/comments` | 1 credit | `reddit_post_comments` | ### YouTube | Endpoint | Path | Cost | MCP tool | | --- | --- | --- | --- | | [Video details](https://lurkapi.com/docs/youtube/video.md) | `/v1/youtube/video` | 1 credit | `youtube_video` | | [Video transcript](https://lurkapi.com/docs/youtube/transcript.md) | `/v1/youtube/video/transcript` | 1 credit | `youtube_transcript` | | [Video comments](https://lurkapi.com/docs/youtube/comments.md) | `/v1/youtube/video/comments` | 1 credit | `youtube_comments` | | [Comment replies](https://lurkapi.com/docs/youtube/comment-replies.md) | `/v1/youtube/video/comment/replies` | 1 credit | `youtube_comment_replies` | | [Channel details](https://lurkapi.com/docs/youtube/channel.md) | `/v1/youtube/channel` | 1 credit | `youtube_channel` | | [Channel videos](https://lurkapi.com/docs/youtube/channel-videos.md) | `/v1/youtube/channel-videos` | 1 credit | `youtube_channel_videos` | | [Channel Shorts](https://lurkapi.com/docs/youtube/channel-shorts.md) | `/v1/youtube/channel/shorts` | 1 credit | `youtube_channel_shorts` | | [Search YouTube](https://lurkapi.com/docs/youtube/search.md) | `/v1/youtube/search` | 1 credit | `youtube_search` | ### TikTok | Endpoint | Path | Cost | MCP tool | | --- | --- | --- | --- | | [Profile](https://lurkapi.com/docs/tiktok/profile.md) | `/v1/tiktok/profile` | 1 credit | `tiktok_profile` | | [Video details](https://lurkapi.com/docs/tiktok/video.md) | `/v2/tiktok/video` | 1 credit | `tiktok_video` | | [Video transcript](https://lurkapi.com/docs/tiktok/transcript.md) | `/v1/tiktok/video/transcript` | 1 credit; 5 if speech-to-text runs | `tiktok_transcript` | | [Profile videos](https://lurkapi.com/docs/tiktok/profile-videos.md) | `/v3/tiktok/profile/videos` | 1 credit | `tiktok_profile_videos` | | [Video comments](https://lurkapi.com/docs/tiktok/comments.md) | `/v1/tiktok/video/comments` | 1 credit | `tiktok_comments` | | [Comment replies](https://lurkapi.com/docs/tiktok/comment-replies.md) | `/v1/tiktok/video/comment/replies` | 1 credit | `tiktok_comment_replies` | | [Hashtag videos](https://lurkapi.com/docs/tiktok/hashtag-videos.md) | `/v1/tiktok/search/hashtag` | 1 credit | `tiktok_hashtag_videos` | | [Song](https://lurkapi.com/docs/tiktok/song.md) | `/v1/tiktok/song` | 1 credit | `tiktok_song` | | [Song videos](https://lurkapi.com/docs/tiktok/song-videos.md) | `/v1/tiktok/song/videos` | 1 credit | `tiktok_song_videos` | | [Search TikTok Ad Library](https://lurkapi.com/docs/tiktok/ad-library-search.md) | `/v1/tiktok/ad-library/search` | 1 credit | `tiktok_ad_library_search` | | [TikTok Ad Library ad](https://lurkapi.com/docs/tiktok/ad-library-ad.md) | `/v1/tiktok/ad-library/ad` | 1 credit | `tiktok_ad_library_ad` | | [Live status](https://lurkapi.com/docs/tiktok/live.md) | `/v1/tiktok/user/live` | 1 credit | `tiktok_live` | | [Public stories](https://lurkapi.com/docs/tiktok/stories.md) | `/v1/tiktok/user/stories` | 1 credit | `tiktok_stories` | | [TikTok Shop product](https://lurkapi.com/docs/tiktok/product.md) | `/v1/tiktok/product` | 1 credit | `tiktok_product` | ### Google | Endpoint | Path | Cost | MCP tool | | --- | --- | --- | --- | | [Search Google advertisers](https://lurkapi.com/docs/google/search-advertisers.md) | `/v1/google/adLibrary/advertisers/search` | 1 credit | `google_search_advertisers` | | [Google ads by company](https://lurkapi.com/docs/google/company-ads.md) | `/v1/google/company/ads` | 1 credit | `google_company_ads` | | [Google ad details](https://lurkapi.com/docs/google/ad.md) | `/v1/google/ad` | 1 credit | `google_ad` | ### Bluesky | Endpoint | Path | Cost | MCP tool | | --- | --- | --- | --- | | [Profile](https://lurkapi.com/docs/bluesky/profile.md) | `/v1/bluesky/profile` | 1 credit | `bluesky_profile` | | [User posts](https://lurkapi.com/docs/bluesky/user-posts.md) | `/v1/bluesky/user/posts` | 1 credit | `bluesky_user_posts` | | [Post](https://lurkapi.com/docs/bluesky/post.md) | `/v1/bluesky/post` | 1 credit | `bluesky_post` | ### Snapchat | Endpoint | Path | Cost | MCP tool | | --- | --- | --- | --- | | [Profile and stories](https://lurkapi.com/docs/snapchat/profile.md) | `/v1/snapchat/profile` | 1 credit | `snapchat_profile` | ### Truth Social | Endpoint | Path | Cost | MCP tool | | --- | --- | --- | --- | | [Profile](https://lurkapi.com/docs/truthsocial/profile.md) | `/v1/truthsocial/profile` | 1 credit | `truthsocial_profile` | | [User posts](https://lurkapi.com/docs/truthsocial/user-posts.md) | `/v1/truthsocial/user/posts` | 1 credit | `truthsocial_user_posts` | | [Post](https://lurkapi.com/docs/truthsocial/post.md) | `/v1/truthsocial/post` | 1 credit | `truthsocial_post` | ### Telegram | Endpoint | Path | Cost | MCP tool | | --- | --- | --- | --- | | [Channel](https://lurkapi.com/docs/telegram/channel.md) | `/v1/telegram/channel` | 1 credit | `telegram_channel` | | [Channel posts](https://lurkapi.com/docs/telegram/channel-posts.md) | `/v1/telegram/channel/posts` | 1 credit | `telegram_channel_posts` | | [Post](https://lurkapi.com/docs/telegram/post.md) | `/v1/telegram/post` | 1 credit | `telegram_post` | ### X (Twitter) | Endpoint | Path | Cost | MCP tool | | --- | --- | --- | --- | | [Tweet](https://lurkapi.com/docs/twitter/tweet.md) | `/v1/twitter/tweet` | 1 credit | `twitter_tweet` | | [Profile](https://lurkapi.com/docs/twitter/profile.md) | `/v1/twitter/profile` | 1 credit | `twitter_profile` | | [User tweets](https://lurkapi.com/docs/twitter/user-tweets.md) | `/v1/twitter/user-tweets` | 1 credit | `twitter_user_tweets` | ### Threads | Endpoint | Path | Cost | MCP tool | | --- | --- | --- | --- | | [Threads profile](https://lurkapi.com/docs/threads/profile.md) | `/v1/threads/profile` | 1 credit | `threads_profile` | | [Threads account posts](https://lurkapi.com/docs/threads/user-posts.md) | `/v1/threads/user/posts` | 1 credit | `threads_user_posts` | | [Threads post](https://lurkapi.com/docs/threads/post.md) | `/v1/threads/post` | 1 credit | `threads_post` | ### Instagram | Endpoint | Path | Cost | MCP tool | | --- | --- | --- | --- | | [Profile](https://lurkapi.com/docs/instagram/profile.md) | `/v1/instagram/profile` | 1 credit | `instagram_profile` | | [User posts](https://lurkapi.com/docs/instagram/user-posts.md) | `/v2/instagram/user/posts` | 1 credit | `instagram_user_posts` | | [Post](https://lurkapi.com/docs/instagram/post.md) | `/v1/instagram/post` | 1 credit | `instagram_post` | | [Post comments](https://lurkapi.com/docs/instagram/post-comments.md) | `/v2/instagram/post/comments` | 1 credit | `instagram_post_comments` | | [User reels](https://lurkapi.com/docs/instagram/user-reels.md) | `/v1/instagram/user/reels` | 1 credit | `instagram_user_reels` | | [Media transcript](https://lurkapi.com/docs/instagram/media-transcript.md) | `/v2/instagram/media/transcript` | 1 credit; 5 if speech-to-text runs | `instagram_media_transcript` | ### LinkedIn | Endpoint | Path | Cost | MCP tool | | --- | --- | --- | --- | | [LinkedIn company page](https://lurkapi.com/docs/linkedin/company.md) | `/v1/linkedin/company` | 1 credit | `linkedin_company` | | [LinkedIn company posts](https://lurkapi.com/docs/linkedin/company-posts.md) | `/v1/linkedin/company/posts` | 1 credit | `linkedin_company_posts` | | [Search LinkedIn ads](https://lurkapi.com/docs/linkedin/search-ads.md) | `/v1/linkedin/ads/search` | 1 credit | `linkedin_search_ads` | | [LinkedIn ad details](https://lurkapi.com/docs/linkedin/ad.md) | `/v1/linkedin/ad` | 1 credit | `linkedin_ad` | ### Link-in-bio | Endpoint | Path | Cost | MCP tool | | --- | --- | --- | --- | | [Linktree page](https://lurkapi.com/docs/linkinbio/linktree.md) | `/v1/linktree` | 1 credit | `linkinbio_linktree` | ### Account | Endpoint | Path | Cost | MCP tool | | --- | --- | --- | --- | | [Credit balance](https://lurkapi.com/docs/account/credit-balance.md) | `/v1/account/credit-balance` | free | `account_credit_balance` | ## For AI agents - **Base URL:** `https://api.lurkapi.com` - **Auth:** `x-api-key: lk_live_…` header (or `Authorization: Bearer lk_live_…`). [Get a free key](https://lurkapi.com/login?next=/dashboard): 100 free credits on signup, no card. - **Requests:** `GET` with query parameters. Responses are JSON with `success`, `credits_remaining` and the data at the top level; errors add `code` and `docs`. - **Price:** Endpoints cost 1–5 credits per call, from $0.199 per 1,000 credits. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). Every response has `credits_charged` and `credits_remaining`; `GET /v1/account/credit-balance` is free. - **Rate limit:** 50 requests per 10 seconds per account (100 for high-volume accounts: support@lurkapi.com); over it, `429 rate_limited` with `retry-after`. - **Agents:** getting a key needs a human sign-in. Ask your user to sign in at https://lurkapi.com/login and paste the key from the dashboard. - **MCP server:** `https://api.lurkapi.com/mcp` (Streamable HTTP). Add `platforms` to pick what Claude sees (recommended): `https://api.lurkapi.com/mcp?platforms=facebook,reddit,youtube,tiktok,google,bluesky,snapchat,truthsocial,telegram,twitter,threads,instagram,linkedin,linkinbio` gives one tool per endpoint of those platforms, plus the free account tool. Platform ids: `facebook`, `reddit`, `youtube`, `tiktok`, `google`, `bluesky`, `snapchat`, `truthsocial`, `telegram`, `twitter`, `threads`, `instagram`, `linkedin` and `linkinbio`. Without it you get one tool per endpoint up to 25 endpoints; past that, two tools: `find_endpoints` to search the catalog and `call_endpoint` to run one, charged the same. Tool results skip nulls and empty lists to save tokens, and `trim` defaults to `true` where an endpoint has it. - **Machine-readable:** [/openapi.json](https://lurkapi.com/openapi.json), [/llms.txt](https://lurkapi.com/llms.txt), [/llms-full.txt](https://lurkapi.com/llms-full.txt), and every docs page as markdown at its URL + `.md`. `GET https://api.lurkapi.com/` returns a JSON index of every endpoint. --- # Migrating from another provider > Paths follow the `/v1//…` pattern and take your key in the `x-api-key` header, so most clients only change the base URL and the key. - **Web page:** https://lurkapi.com/docs/switching ## The switch 1. [Get a LurkAPI key](https://lurkapi.com/login?next=/dashboard). Keys start with `lk_live_`. 2. Change the base URL to `https://api.lurkapi.com`. Paths stay the same. 3. Keep sending the key in `x-api-key`, and check your error handling against the differences below. Paths and the `x-api-key` header match ScrapeCreators-style APIs, so a client written for one usually works after changing the base URL: curl: ```bash curl "https://api.lurkapi.com/v1/facebook/adLibrary/search/ads?query=running+shoes&country=US&status=active" \ -H "x-api-key: YOUR_API_KEY" ``` ## Endpoints Endpoints cost 1–5 credits per call. | Path | Endpoint | Cost | | --- | --- | --- | | `/v1/facebook/adLibrary/search/ads` | [Facebook: Search Ad Library ads](https://lurkapi.com/docs/facebook/search-ads.md) | 1 credit | | `/v1/facebook/adLibrary/company/ads` | [Facebook: Ad Library ads by company](https://lurkapi.com/docs/facebook/company-ads.md) | 1 credit | | `/v1/facebook/adLibrary/ad` | [Facebook: Ad Library ad details](https://lurkapi.com/docs/facebook/ad.md) | 1 credit | | `/v1/facebook/adLibrary/search/companies` | [Facebook: Search Ad Library companies](https://lurkapi.com/docs/facebook/search-companies.md) | 1 credit | | `/v1/facebook/profile` | [Facebook: Public profile](https://lurkapi.com/docs/facebook/profile.md) | 1 credit | | `/v1/facebook/post` | [Facebook: Public post](https://lurkapi.com/docs/facebook/post.md) | 1 credit | | `/v1/reddit/subreddit` | [Reddit: Subreddit posts](https://lurkapi.com/docs/reddit/subreddit-posts.md) | 1 credit | | `/v1/reddit/subreddit/details` | [Reddit: Subreddit details](https://lurkapi.com/docs/reddit/subreddit-details.md) | 1 credit | | `/v1/reddit/subreddit/search` | [Reddit: Subreddit search](https://lurkapi.com/docs/reddit/subreddit-search.md) | 1 credit | | `/v1/reddit/search` | [Reddit: Search Reddit](https://lurkapi.com/docs/reddit/search.md) | 1 credit | | `/v1/reddit/post/comments` | [Reddit: Post comments](https://lurkapi.com/docs/reddit/post-comments.md) | 1 credit | | `/v1/youtube/video` | [YouTube: Video details](https://lurkapi.com/docs/youtube/video.md) | 1 credit | | `/v1/youtube/video/transcript` | [YouTube: Video transcript](https://lurkapi.com/docs/youtube/transcript.md) | 1 credit | | `/v1/youtube/video/comments` | [YouTube: Video comments](https://lurkapi.com/docs/youtube/comments.md) | 1 credit | | `/v1/youtube/video/comment/replies` | [YouTube: Comment replies](https://lurkapi.com/docs/youtube/comment-replies.md) | 1 credit | | `/v1/youtube/channel` | [YouTube: Channel details](https://lurkapi.com/docs/youtube/channel.md) | 1 credit | | `/v1/youtube/channel-videos` | [YouTube: Channel videos](https://lurkapi.com/docs/youtube/channel-videos.md) | 1 credit | | `/v1/youtube/channel/shorts` | [YouTube: Channel Shorts](https://lurkapi.com/docs/youtube/channel-shorts.md) | 1 credit | | `/v1/youtube/search` | [YouTube: Search YouTube](https://lurkapi.com/docs/youtube/search.md) | 1 credit | | `/v1/tiktok/profile` | [TikTok: Profile](https://lurkapi.com/docs/tiktok/profile.md) | 1 credit | | `/v2/tiktok/video` | [TikTok: Video details](https://lurkapi.com/docs/tiktok/video.md) | 1 credit | | `/v1/tiktok/video/transcript` | [TikTok: Video transcript](https://lurkapi.com/docs/tiktok/transcript.md) | 1 credit; 5 if speech-to-text runs | | `/v3/tiktok/profile/videos` | [TikTok: Profile videos](https://lurkapi.com/docs/tiktok/profile-videos.md) | 1 credit | | `/v1/tiktok/video/comments` | [TikTok: Video comments](https://lurkapi.com/docs/tiktok/comments.md) | 1 credit | | `/v1/tiktok/video/comment/replies` | [TikTok: Comment replies](https://lurkapi.com/docs/tiktok/comment-replies.md) | 1 credit | | `/v1/tiktok/search/hashtag` | [TikTok: Hashtag videos](https://lurkapi.com/docs/tiktok/hashtag-videos.md) | 1 credit | | `/v1/tiktok/song` | [TikTok: Song](https://lurkapi.com/docs/tiktok/song.md) | 1 credit | | `/v1/tiktok/song/videos` | [TikTok: Song videos](https://lurkapi.com/docs/tiktok/song-videos.md) | 1 credit | | `/v1/tiktok/ad-library/search` | [TikTok: Search TikTok Ad Library](https://lurkapi.com/docs/tiktok/ad-library-search.md) | 1 credit | | `/v1/tiktok/ad-library/ad` | [TikTok: TikTok Ad Library ad](https://lurkapi.com/docs/tiktok/ad-library-ad.md) | 1 credit | | `/v1/tiktok/user/live` | [TikTok: Live status](https://lurkapi.com/docs/tiktok/live.md) | 1 credit | | `/v1/tiktok/user/stories` | [TikTok: Public stories](https://lurkapi.com/docs/tiktok/stories.md) | 1 credit | | `/v1/tiktok/product` | [TikTok: TikTok Shop product](https://lurkapi.com/docs/tiktok/product.md) | 1 credit | | `/v1/google/adLibrary/advertisers/search` | [Google: Search Google advertisers](https://lurkapi.com/docs/google/search-advertisers.md) | 1 credit | | `/v1/google/company/ads` | [Google: Google ads by company](https://lurkapi.com/docs/google/company-ads.md) | 1 credit | | `/v1/google/ad` | [Google: Google ad details](https://lurkapi.com/docs/google/ad.md) | 1 credit | | `/v1/bluesky/profile` | [Bluesky: Profile](https://lurkapi.com/docs/bluesky/profile.md) | 1 credit | | `/v1/bluesky/user/posts` | [Bluesky: User posts](https://lurkapi.com/docs/bluesky/user-posts.md) | 1 credit | | `/v1/bluesky/post` | [Bluesky: Post](https://lurkapi.com/docs/bluesky/post.md) | 1 credit | | `/v1/snapchat/profile` | [Snapchat: Profile and stories](https://lurkapi.com/docs/snapchat/profile.md) | 1 credit | | `/v1/truthsocial/profile` | [Truth Social: Profile](https://lurkapi.com/docs/truthsocial/profile.md) | 1 credit | | `/v1/truthsocial/user/posts` | [Truth Social: User posts](https://lurkapi.com/docs/truthsocial/user-posts.md) | 1 credit | | `/v1/truthsocial/post` | [Truth Social: Post](https://lurkapi.com/docs/truthsocial/post.md) | 1 credit | | `/v1/telegram/channel` | [Telegram: Channel](https://lurkapi.com/docs/telegram/channel.md) | 1 credit | | `/v1/telegram/channel/posts` | [Telegram: Channel posts](https://lurkapi.com/docs/telegram/channel-posts.md) | 1 credit | | `/v1/telegram/post` | [Telegram: Post](https://lurkapi.com/docs/telegram/post.md) | 1 credit | | `/v1/twitter/tweet` | [X (Twitter): Tweet](https://lurkapi.com/docs/twitter/tweet.md) | 1 credit | | `/v1/twitter/profile` | [X (Twitter): Profile](https://lurkapi.com/docs/twitter/profile.md) | 1 credit | | `/v1/twitter/user-tweets` | [X (Twitter): User tweets](https://lurkapi.com/docs/twitter/user-tweets.md) | 1 credit | | `/v1/threads/profile` | [Threads: Threads profile](https://lurkapi.com/docs/threads/profile.md) | 1 credit | | `/v1/threads/user/posts` | [Threads: Threads account posts](https://lurkapi.com/docs/threads/user-posts.md) | 1 credit | | `/v1/threads/post` | [Threads: Threads post](https://lurkapi.com/docs/threads/post.md) | 1 credit | | `/v1/instagram/profile` | [Instagram: Profile](https://lurkapi.com/docs/instagram/profile.md) | 1 credit | | `/v2/instagram/user/posts` | [Instagram: User posts](https://lurkapi.com/docs/instagram/user-posts.md) | 1 credit | | `/v1/instagram/post` | [Instagram: Post](https://lurkapi.com/docs/instagram/post.md) | 1 credit | | `/v2/instagram/post/comments` | [Instagram: Post comments](https://lurkapi.com/docs/instagram/post-comments.md) | 1 credit | | `/v1/instagram/user/reels` | [Instagram: User reels](https://lurkapi.com/docs/instagram/user-reels.md) | 1 credit | | `/v2/instagram/media/transcript` | [Instagram: Media transcript](https://lurkapi.com/docs/instagram/media-transcript.md) | 1 credit; 5 if speech-to-text runs | | `/v1/linkedin/company` | [LinkedIn: LinkedIn company page](https://lurkapi.com/docs/linkedin/company.md) | 1 credit | | `/v1/linkedin/company/posts` | [LinkedIn: LinkedIn company posts](https://lurkapi.com/docs/linkedin/company-posts.md) | 1 credit | | `/v1/linkedin/ads/search` | [LinkedIn: Search LinkedIn ads](https://lurkapi.com/docs/linkedin/search-ads.md) | 1 credit | | `/v1/linkedin/ad` | [LinkedIn: LinkedIn ad details](https://lurkapi.com/docs/linkedin/ad.md) | 1 credit | | `/v1/linktree` | [Link-in-bio: Linktree page](https://lurkapi.com/docs/linkinbio/linktree.md) | 1 credit | | `/v1/account/credit-balance` | [Account: Credit balance](https://lurkapi.com/docs/account/credit-balance.md) | free | ## What stays the same - Paths, the `x-api-key` header, and `error` as a string. - Allowed values in any case (`status=ACTIVE` works), an `r/` prefix on subreddit names, and trailing slashes. - Unknown parameters such as `get_transcript` are ignored, blank parameters count as unset, and a repeated parameter uses its first value. - Search companies accepts `country=ALL`. ## What's different 1. **Base URL** is `https://api.lurkapi.com`. 2. **Keys** start with `lk_live_`. `Authorization: Bearer ` also works. 3. **`credits_remaining`** is your real balance after the call. 4. **Status codes:** `402 insufficient_credits`; `429 rate_limited` or `429 signup_required`. A missing key is `401 missing_api_key` and a revoked key is `401 invalid_api_key`. 5. **Rate limit:** 50 requests per 10 seconds per account, with a `retry-after` header on `429`. High-volume accounts get 100; email [support@lurkapi.com](mailto:support@lurkapi.com). 6. **Error bodies** are `{ success: false, error, code, docs, issues? }`. Branch on `code`, not the `error` text. `502` and `503` responses don't include upstream details. [All codes](https://lurkapi.com/docs.md#errors). 7. **`trim` must be `true` or `false`** (any case). Anything else is `400 invalid_params`. 8. **Search companies `country`** must be `ALL` or a 2-letter country code. 9. **Only `GET`.** Other methods return `405 method_not_allowed`. Some providers also accept POST on search ads, company ads and post comments; send those as GET with query parameters. 10. **Charging:** A call is charged when it reached the platform, including `not_found` and other errors returned by the endpoint itself (e.g. a bad cursor). Free: parameter validation errors caught before the call, `401`/`402`/`429` rejections, and `5xx` failures (`500`/`502`/`503`). Cached responses cost the same as fresh ones. Anonymous tries follow the same rule. 11. **`cache_max_age` is ignored.** Each endpoint has its own cache time (see [Caching and freshness](https://lurkapi.com/docs.md#caching)). ### Fields we don't return Reddit data comes from reddit.com's own pages, so fields those pages don't show are left out rather than guessed. Every Facebook endpoint and subreddit search return every key. - [Subreddit posts](https://lurkapi.com/docs/reddit/subreddit-posts.md) (`posts[]`): no `downs`, `subreddit_subscribers`, `num_crossposts`, `edited`, `distinguished`, `archived`, `pinned`, `media`, `secure_media`, `media_embed`, `gilded`, `all_awardings`, `author_flair_text` or `link_flair_css_class`, and none of Reddit's moderator (`mod_reports`, `banned_by`…) or viewer-state (`saved`, `likes`, `clicked`…) fields. - [Search Reddit](https://lurkapi.com/docs/reddit/search.md) (`posts[]`): only what the search page shows, so also no `selftext`, `selftext_html`, `url`, `url_overridden_by_dest`, `domain`, `is_self`, `is_video`, `preview`, `upvote_ratio`, `link_flair_text`, `stickied`, `locked` or `total_awards_received`. Pass the post's URL to [post comments](https://lurkapi.com/docs/reddit/post-comments.md) for the full post. - [Post comments](https://lurkapi.com/docs/reddit/post-comments.md): `post` lacks the same fields as subreddit posts, plus `num_duplicates`; `comments[]` have no `downs`, `edited`, `distinguished`, `stickied`, `locked`, `controversiality`, `score_hidden`, `gilded`, `all_awardings`, `total_awards_received`, `author_flair_text` or `collapsed_reason`, nor moderator or viewer-state fields. - [Subreddit details](https://lurkapi.com/docs/reddit/subreddit-details.md): no `subscribers`, `advertiser_category` or `submit_text`. Use `weekly_active_users` for size. ### Other response differences - [Subreddit search](https://lurkapi.com/docs/reddit/subreddit-search.md) returns posts only. - Facebook `watermarked_resized_image_url` is `""` rather than `null`, and `ig_verification` is `false` rather than `null`. - [Company ads](https://lurkapi.com/docs/facebook/company-ads.md) defaults to `status=all`. Pass `status=active` for running ads only. - `get_transcript` does nothing. ### Legacy mesmertools clients - `/api/v1/...` paths still work: `https://api.lurkapi.com/api/v1/facebook/adLibrary/search/ads` is the same endpoint. - `credits_remaining` used to be `-1`; errors used to be `{ error }` with different text ("missing required query param: query" is now "query: required"); a revoked key was `403`, now `401`. - The Facebook ad endpoint and the Reddit endpoints used to read any `trim` value but `true` as false; now it's a `400`. ## Also included - **Claude and MCP.** Every endpoint is also an MCP tool at `https://api.lurkapi.com/mcp`. [Connect it to Claude](https://lurkapi.com/docs.md#claude). Tool results skip nulls and empty lists to save tokens, and `trim` defaults to `true` where an endpoint has it. - **Docs for agents.** [llms.txt](https://lurkapi.com/llms.txt), [openapi.json](https://lurkapi.com/openapi.json) and every docs page as markdown (add `.md` to the URL). - **Pay per call**, from $0.199 per 1,000 credits, with no subscription. [Pricing](https://lurkapi.com/pricing.md). [Get a free API key](https://lurkapi.com/login?next=/dashboard) --- # Facebook API > Ads running on Facebook and Instagram, from Meta's Ad Library: copy, creative, landing page, dates and platforms. - **Platform:** [Facebook](https://lurkapi.com/docs/facebook.md) (facebook.com/ads/library) - **Web page:** https://lurkapi.com/docs/facebook ## Overview Search Meta's public Ad Library by keyword or advertiser, and pull full detail for any ad. Great for competitor research, swipe files and creative trend tracking. Source: facebook.com/ads/library. ## Try it live Pick an endpoint and run it: real data, no signup, 5 free requests a day. Live playground: https://lurkapi.com/docs/facebook#try ## Endpoints - [Search Ad Library ads](https://lurkapi.com/docs/facebook/search-ads.md): Find ads in Meta's Ad Library by keyword, with their copy, media, landing page and run dates. (GET /v1/facebook/adLibrary/search/ads · 1 credit) - [Ad Library ads by company](https://lurkapi.com/docs/facebook/company-ads.md): Every ad one advertiser is running (or ran) on Facebook and Instagram, from Meta's Ad Library. (GET /v1/facebook/adLibrary/company/ads · 1 credit) - [Ad Library ad details](https://lurkapi.com/docs/facebook/ad.md): Full detail for one ad from Meta's Ad Library, by its id or URL. (GET /v1/facebook/adLibrary/ad · 1 credit) - [Search Ad Library companies](https://lurkapi.com/docs/facebook/search-companies.md): Find an advertiser's Facebook page id by name in Meta's Ad Library, with followers and linked Instagram. (GET /v1/facebook/adLibrary/search/companies · 1 credit) - [Public profile](https://lurkapi.com/docs/facebook/profile.md): A Facebook Page's or public person's name, pictures, verification and follower counts. (GET /v1/facebook/profile · 1 credit) - [Public post](https://lurkapi.com/docs/facebook/post.md): A public post's text, author, engagement counts and attached media. (GET /v1/facebook/post · 1 credit) ## Call it Every endpoint is a `GET` with your key in the `x-api-key` header. For example, [Search Ad Library ads](https://lurkapi.com/docs/facebook/search-ads.md): curl: ```bash curl "https://api.lurkapi.com/v1/facebook/adLibrary/search/ads?query=running+shoes&country=US&status=active" \ -H "x-api-key: YOUR_API_KEY" ``` [Get a free API key](https://lurkapi.com/login?next=/dashboard) ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), each endpoint is a tool Claude can call: | Tool | What it does | | --- | --- | | `facebook_search_ads` | [Search Ad Library ads](https://lurkapi.com/docs/facebook/search-ads.md): Find ads in Meta's Ad Library by keyword, with their copy, media, landing page and run dates. | | `facebook_company_ads` | [Ad Library ads by company](https://lurkapi.com/docs/facebook/company-ads.md): Every ad one advertiser is running (or ran) on Facebook and Instagram, from Meta's Ad Library. | | `facebook_ad` | [Ad Library ad details](https://lurkapi.com/docs/facebook/ad.md): Full detail for one ad from Meta's Ad Library, by its id or URL. | | `facebook_search_companies` | [Search Ad Library companies](https://lurkapi.com/docs/facebook/search-companies.md): Find an advertiser's Facebook page id by name in Meta's Ad Library, with followers and linked Instagram. | | `facebook_profile` | [Public profile](https://lurkapi.com/docs/facebook/profile.md): A Facebook Page's or public person's name, pictures, verification and follower counts. | | `facebook_post` | [Public post](https://lurkapi.com/docs/facebook/post.md): A public post's text, author, engagement counts and attached media. | --- # Search Ad Library ads > Find ads in Meta's Ad Library by keyword, with their copy, media, landing page and run dates. - **Request:** `GET https://api.lurkapi.com/v1/facebook/adLibrary/search/ads` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `facebook_search_ads` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 30 minutes - **Try it live:** https://lurkapi.com/docs/facebook/search-ads#try (no signup; free tool: [Facebook Ad Library search](https://lurkapi.com/free-tools/facebook-ad-library-search)) - **Platform:** [Facebook](https://lurkapi.com/docs/facebook.md) (facebook.com/ads/library) - **Web page:** https://lurkapi.com/docs/facebook/search-ads ## When to use this Use it to see how a whole market advertises: the angles, offers and formats that keep running. For one advertiser's ads, use company ads (facebook_company_ads) instead. `status` defaults to all; pass `active` for ads running now. Keyword search across every ad in the Meta Ad Library, the same as typing into facebook.com/ads/library. Filter by country, status, media type, language and date range. The default sort puts the most-seen ads first. **Pagination.** The first page returns up to 30 ads plus `searchResultsCount`. Pass `cursor` back (with the same filters) for the next page: cursor pages return up to 10 ads, which is Facebook's own page size, and `searchResultsCount` is null. `cursor` is null on the last page. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `query` | string | yes | | | Keywords to search ad text for, e.g. `running shoes`. | `running shoes` | | `search_type` | string | no | `keyword_unordered` | `keyword_unordered`, `keyword_exact_phrase` | `keyword_unordered` matches all words in any order; `keyword_exact_phrase` matches the phrase as typed. | | | `country` | string | no | `ALL` | | Where the ad ran: a 2-letter ISO country code (US, GB, DE…) or ALL. One country per request. | `US` | | `status` | string | no | `all` | `all`, `active`, `inactive` | Only ads that are running now (active), only stopped ads (inactive), or both (all). | `active` | | `media_type` | string | no | `all` | `all`, `image`, `video`, `meme`, `image_and_meme`, `none` | Creative format. `meme` is Facebook's name for image + text; `none` means text-only. | | | `ad_type` | string | no | `ALL` | `ALL`, `POLITICAL_AND_ISSUE_ADS`, `EMPLOYMENT_ADS`, `HOUSING_ADS`, `CREDIT_ADS` | Ad category. Political ads also carry spend and impressions ranges. | | | `language` | string | no | | | Language of the ad text as an ISO 639-1 code, e.g. `en` or `es`. | | | `sort_by` | string | no | `total_impressions` | `total_impressions`, `relevancy_monthly_grouped` | `total_impressions`: most-seen ads first. `relevancy_monthly_grouped`: most recent first. | | | `start_date` | string | no | | | Only ads shown on or after this date (YYYY-MM-DD). The ad itself may have launched earlier. | | | `end_date` | string | no | | | Only ads shown on or before this date (YYYY-MM-DD). | | | `cursor` | string | no | | | The `cursor` from the previous response, to get the next page. Keep every other param the same. | | | `trim` | boolean | no | `false` | `true`, `false` | `true` drops image and video URLs (inside `snapshot.cards` too) for a much smaller response. All text is kept. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/facebook/adLibrary/search/ads?query=running+shoes&country=US&status=active" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ query: "running shoes", country: "US", status: "active", }); const res = await fetch(`https://api.lurkapi.com/v1/facebook/adLibrary/search/ads?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.searchResults); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/facebook/adLibrary/search/ads", params={ "query": "running shoes", "country": "US", "status": "active", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["searchResults"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999, "credits_charged": 1, "searchResults": [ { "ad_archive_id": "1279489107485101", "ad_id": null, "categories": [ "UNKNOWN" ], "collation_count": 3, "collation_id": "955107020207956", "contains_digital_created_media": false, "contains_sensitive_content": false, "currency": "", "end_date": 1790751600, "end_date_string": null, "fev_info": null, "gated_type": "ELIGIBLE", "has_user_reported": false, "hide_data_status": "NONE", "impressions_with_index": { "impressions_text": null, "impressions_index": -1 }, "is_aaa_eligible": false, "is_active": true, "menu_items": [], "page_id": "103032366405927", "page_is_deleted": false, "page_name": "HOKA", "publisher_platform": [ "FACEBOOK", "INSTAGRAM" ], "reach_estimate": null, "regional_regulation_data": { "finserv": { "is_deemed_finserv": false, "is_limited_delivery": false }, "tw_anti_scam": { "is_limited_delivery": false } }, "report_count": null, "snapshot": { "additional_info": null, "branded_content": null, "brazil_tax_id": null, "byline": null, "caption": "HOKA.com", "cards": [], "country_iso_code": null, "cta_text": "Learn more", "cta_type": "LEARN_MORE", "disclaimer_label": null, "display_format": "IMAGE", "ec_certificates": [], "event": null, "extra_images": [], "extra_links": [], "extra_texts": [], "extra_videos": [], "images": [ { "image_crops": [], "original_image_url": "https://scontent-sin11-2.xx.fbcdn.net/v/t39.35426-6/660610392_9315190863…", "resized_image_url": "https://scontent-sin6-3.xx.fbcdn.net/v/t39.35426-6/658157694_15247761589…", "watermarked_resized_image_url": "" } ], "is_reshared": false, "link_description": "From first summits to 5Ks, HOKA designs running shoes and gear built to …", "link_url": "https://www.hoka.com/en/us/fly-human-fly/?utm_source=facebookinstagram&u…", "page_categories": [ "Outdoor & Sporting Goods Company" ], "page_id": "103032366405927", "page_is_deleted": false, "page_like_count": 1218804, "page_name": "HOKA", "page_profile_picture_url": "https://scontent-sin2-1.xx.fbcdn.net/v/t39.35426-6/661719637_14799039102…", "page_profile_uri": "https://facebook.com/hoka", "root_reshared_post": null, "title": "FLY HUMAN FLY™", "videos": [], "body": { "text": "The legs give out. The heart doesn’t. Together We Fly Higher." } }, "spend": null, "start_date": 1775026800, "start_date_string": null, "state_media_run_label": null, "targeted_or_reached_countries": [], "total_active_time": null, "url": null }, { "ad_archive_id": "1672311423863159", "ad_id": null, "categories": [ "UNKNOWN" ], "collation_count": null, "collation_id": null, "contains_digital_created_media": false, "contains_sensitive_content": false, "currency": "", "end_date": 1790751600, "end_date_string": null, "fev_info": null, "gated_type": "ELIGIBLE", "has_user_reported": false, "hide_data_status": "NONE", "impressions_with_index": { "impressions_text": null, "impressions_index": -1 }, "is_aaa_eligible": false, "is_active": true, "menu_items": [], "page_id": "9062006483", "page_is_deleted": false, "page_name": "REI", "publisher_platform": [ "FACEBOOK", "INSTAGRAM" ], "reach_estimate": null, "regional_regulation_data": { "finserv": { "is_deemed_finserv": false, "is_limited_delivery": false }, "tw_anti_scam": { "is_limited_delivery": false } }, "report_count": null, "snapshot": { "additional_info": null, "branded_content": null, "brazil_tax_id": null, "byline": null, "caption": "rei.com", "cards": [], "country_iso_code": null, "cta_text": "Shop now", "cta_type": "SHOP_NOW", "disclaimer_label": null, "display_format": "VIDEO", "ec_certificates": [], "event": null, "extra_images": [], "extra_links": [], "extra_texts": [], "extra_videos": [], "images": [], "is_reshared": false, "link_description": "Trail-running gear", "link_url": "https://www.rei.com/c/trail-running-shoes?cm_mmc=sm_fbig_76501-_-hero_tr…", "page_categories": [ "Outdoor & Sporting Goods Company" ], "page_id": "9062006483", "page_is_deleted": false, "page_like_count": 2113405, "page_name": "REI", "page_profile_picture_url": "https://scontent-sin6-3.xx.fbcdn.net/v/t39.35426-6/753881065_89714526678…", "page_profile_uri": "https://facebook.com/REI", "root_reshared_post": null, "title": "Get your gear for the trail", "videos": [ { "video_hd_url": "https://video-sin2-2.xx.fbcdn.net/o1/v/t2/f2/m366/AQNiLrO3Mqup5to6yp2shI…", "video_preview_image_url": "https://scontent-sin11-1.xx.fbcdn.net/v/t39.35426-6/753145822_1584018949…", "video_sd_url": "https://video-sin11-1.xx.fbcdn.net/o1/v/t2/f2/m412/AQOUVO8Jd7h4piW3xQtRw…", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null } ], "body": { "text": "Shoes required. Fun type may vary." } }, "spend": null, "start_date": 1785481200, "start_date_string": null, "state_media_run_label": null, "targeted_or_reached_countries": [], "total_active_time": null, "url": null } ], "searchResultsCount": 37660, "cursor": "AQHTNrjEvTw_VUW75_xQkUb5_yjH682WwzNgh8oZPUIyUZdNkk1m_9q-4jP_law5Dv9p" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `searchResults` | `object[]` | Matching ads. | | `searchResults[].ad_archive_id` | `string`, nullable | The Ad Library id. Pass it as `id` to the ad details endpoint. | | `searchResults[].ad_id` | `string`, nullable | Meta's internal ad id. null in every response we've seen; use the Ad Library id. | | `searchResults[].page_id` | `string`, nullable | The advertiser's Facebook page id. Pass it as `pageId` to the company ads endpoint. | | `searchResults[].page_name` | `string`, nullable | The advertiser's page name. | | `searchResults[].is_active` | `boolean` | Whether the ad is running now. | | `searchResults[].start_date` | `number`, nullable | When the ad started running, Unix seconds (UTC). | | `searchResults[].end_date` | `number`, nullable | When the ad stopped, or its latest delivery date if still running, Unix seconds (UTC). | | `searchResults[].start_date_string` | `string`, nullable | The same date as ISO 8601 text when Facebook includes it; often null, so prefer the Unix seconds field. | | `searchResults[].end_date_string` | `string`, nullable | The same date as ISO 8601 text when Facebook includes it; often null, so prefer the Unix seconds field. | | `searchResults[].total_active_time` | `number`, nullable | Seconds the ad has run, when Facebook reports it; usually null. | | `searchResults[].publisher_platform` | `string[]` | Where it ran, uppercase: FACEBOOK, INSTAGRAM, AUDIENCE_NETWORK, MESSENGER, THREADS. | | `searchResults[].collation_count` | `number`, nullable | How many near-identical versions are grouped under this ad. A high count means the advertiser is scaling it; null for a single version. | | `searchResults[].collation_id` | `string`, nullable | Id shared by the grouped versions of this ad; null for a single version. | | `searchResults[].snapshot` | `object` | The creative: copy, media, landing page and advertiser details. | | `searchResults[].snapshot.page_id` | `string`, nullable | The advertiser's page id (same as the ad's). | | `searchResults[].snapshot.page_name` | `string`, nullable | The advertiser's page name as shown on the ad. | | `searchResults[].snapshot.page_profile_uri` | `string`, nullable | The advertiser's Facebook page URL. | | `searchResults[].snapshot.page_profile_picture_url` | `string`, nullable | The advertiser's profile picture, 60×60 px. | | `searchResults[].snapshot.page_like_count` | `number`, nullable | Followers of the advertiser's page. | | `searchResults[].snapshot.page_categories` | `string[]` | Page categories, e.g. ["Outdoor & Sporting Goods Company"]. | | `searchResults[].snapshot.page_is_deleted` | `boolean` | Whether the advertiser's page has been deleted. | | `searchResults[].snapshot.title` | `string`, nullable | Headline under the creative; null when there is none. | | `searchResults[].snapshot.caption` | `string`, nullable | Display link under the creative, usually the domain (e.g. HOKA.com). | | `searchResults[].snapshot.link_url` | `string`, nullable | Landing page the ad sends people to, UTM tags included. | | `searchResults[].snapshot.link_description` | `string`, nullable | Text under the headline; null when there is none. | | `searchResults[].snapshot.cta_text` | `string`, nullable | Button label, e.g. "Shop now". | | `searchResults[].snapshot.cta_type` | `string`, nullable | Button type, e.g. SHOP_NOW, LEARN_MORE, NO_BUTTON. | | `searchResults[].snapshot.display_format` | `string`, nullable | Creative format, e.g. IMAGE, VIDEO, DCO (dynamic creative), DPA (dynamic product ad) or MULTI_IMAGES. For DCO and DPA ads the creative is in `cards`. | | `searchResults[].snapshot.images` | `object[]` | Image creatives. Empty for video and card-based ads, and with trim=true. | | `searchResults[].snapshot.images[].original_image_url` | `string`, nullable | Full-size image URL. Facebook CDN links expire after a few days; download what you need. | | `searchResults[].snapshot.images[].resized_image_url` | `string`, nullable | 600 px image URL, good for thumbnails. | | `searchResults[].snapshot.images[].watermarked_resized_image_url` | `string` | Watermarked copy; empty or null in every response we've seen. | | `searchResults[].snapshot.images[].image_crops` | `any[]` | Facebook internal field; an empty array in every response we've seen. Crop hints for the image. | | `searchResults[].snapshot.videos` | `object[]` | Video creatives. Empty for image and card-based ads, and with trim=true. | | `searchResults[].snapshot.videos[].video_hd_url` | `string`, nullable | HD video file URL; null if Facebook has no HD version. | | `searchResults[].snapshot.videos[].video_sd_url` | `string`, nullable | SD video file URL. | | `searchResults[].snapshot.videos[].video_preview_image_url` | `string`, nullable | Poster frame (still image) URL. | | `searchResults[].snapshot.videos[].watermarked_video_hd_url` | `string`, nullable | Watermarked copy; empty or null in every response we've seen. | | `searchResults[].snapshot.videos[].watermarked_video_sd_url` | `string`, nullable | Watermarked copy; empty or null in every response we've seen. | | `searchResults[].snapshot.cards` | `object[]` | Carousel, dynamic-creative and product versions, each with its own copy and media. With trim=true the text stays and media URLs are null. | | `searchResults[].snapshot.cards[].title` | `string`, nullable | This version's headline. | | `searchResults[].snapshot.cards[].body` | `string`, nullable | This version's primary text. | | `searchResults[].snapshot.cards[].caption` | `string`, nullable | This version's display link, usually the domain. | | `searchResults[].snapshot.cards[].cta_text` | `string`, nullable | Button label, e.g. "Shop now". | | `searchResults[].snapshot.cards[].cta_type` | `string`, nullable | Button type, e.g. SHOP_NOW, LEARN_MORE. | | `searchResults[].snapshot.cards[].link_url` | `string`, nullable | This version's landing page URL. | | `searchResults[].snapshot.cards[].link_description` | `string`, nullable | Text under the headline; null when there is none. | | `searchResults[].snapshot.cards[].original_image_url` | `string`, nullable | Full-size image URL; null for video versions. | | `searchResults[].snapshot.cards[].resized_image_url` | `string`, nullable | 600 px image URL; null for video versions. | | `searchResults[].snapshot.cards[].watermarked_resized_image_url` | `string` | Watermarked copy; empty or null in every response we've seen. | | `searchResults[].snapshot.cards[].image_crops` | `any[]` | Facebook internal field; an empty array in every response we've seen. Crop hints for the image. | | `searchResults[].snapshot.cards[].video_hd_url` | `string`, nullable | HD video file URL; null for image versions. | | `searchResults[].snapshot.cards[].video_sd_url` | `string`, nullable | SD video file URL; null for image versions. | | `searchResults[].snapshot.cards[].video_preview_image_url` | `string`, nullable | Poster frame URL; null for image versions. | | `searchResults[].snapshot.cards[].watermarked_video_hd_url` | `string`, nullable | Watermarked copy; empty or null in every response we've seen. | | `searchResults[].snapshot.cards[].watermarked_video_sd_url` | `string`, nullable | Watermarked copy; empty or null in every response we've seen. | | `searchResults[].snapshot.byline` | `string`, nullable | Line under the page name, when shown; null or empty in most ads. | | `searchResults[].snapshot.disclaimer_label` | `string`, nullable | "Paid for by" line on political and issue ads; null otherwise. | | `searchResults[].snapshot.country_iso_code` | `string`, nullable | Facebook internal field; null in every response we've seen. A country code on some ads. | | `searchResults[].snapshot.is_reshared` | `boolean` | Whether the ad reshares an existing post (see `root_reshared_post`). | | `searchResults[].snapshot.root_reshared_post` | `any` | The original post when `is_reshared` is true; null otherwise. | | `searchResults[].snapshot.branded_content` | `any` | Paid partnership details when the ad is branded content; null otherwise. | | `searchResults[].snapshot.additional_info` | `any` | Facebook internal field; null in every response we've seen. | | `searchResults[].snapshot.brazil_tax_id` | `any` | Advertiser tax id shown on ads under Brazil's rules; null otherwise. | | `searchResults[].snapshot.ec_certificates` | `any[]` | Facebook internal field; an empty array in every response we've seen. | | `searchResults[].snapshot.event` | `any` | Event details on event ads; null otherwise. | | `searchResults[].snapshot.extra_images` | `any[]` | More images from dynamic creative; usually empty. | | `searchResults[].snapshot.extra_links` | `any[]` | More landing page URLs from dynamic creative; usually empty. | | `searchResults[].snapshot.extra_texts` | `any[]` | More text variations from dynamic creative; usually empty. | | `searchResults[].snapshot.extra_videos` | `any[]` | More videos from dynamic creative; usually empty. | | `searchResults[].snapshot.body` | `object`, nullable | Primary text as `{ text }`; null when the copy is only in `cards`. | | `searchResults[].snapshot.body.text` | `string`, nullable | The ad's primary text. | | `searchResults[].categories` | `string[]` | Special ad categories Facebook assigned. ["UNKNOWN"] for ordinary ads. | | `searchResults[].targeted_or_reached_countries` | `string[]` | Countries the ad targeted or reached, when Facebook discloses them; empty in most results. | | `searchResults[].currency` | `string` | Currency code of `spend` (e.g. USD) when spend is reported; an empty string otherwise. | | `searchResults[].spend` | `any` | Spend range, reported for political and issue ads only; null otherwise. | | `searchResults[].impressions_with_index` | `object` | Impressions range, reported for political and issue ads only. | | `searchResults[].impressions_with_index.impressions_text` | `string`, nullable | The range as Facebook shows it; null when not reported. | | `searchResults[].impressions_with_index.impressions_index` | `number` | Facebook's bucket number for the range; -1 when not reported. | | `searchResults[].reach_estimate` | `any` | Estimated reach, which Facebook reports for some EU-delivered ads; null otherwise. | | `searchResults[].url` | `string`, nullable | Ad Library link for this ad. Often null; build it as https://www.facebook.com/ads/library/?id=. | | `searchResults[].contains_digital_created_media` | `boolean` | Whether Facebook labels the creative as AI-generated or digitally altered. | | `searchResults[].contains_sensitive_content` | `boolean` | Facebook internal field; false in every response we've seen. Facebook's sensitive-content flag. | | `searchResults[].gated_type` | `string` | Facebook internal field; ELIGIBLE (lowercase in ad details) in every response we've seen. | | `searchResults[].has_user_reported` | `boolean` | Whether the viewing user reported the ad. Always false: we read the Ad Library logged out. | | `searchResults[].hide_data_status` | `string` | Facebook internal field; NONE in every response we've seen. | | `searchResults[].is_aaa_eligible` | `boolean` | Facebook flag, true on a few ads; meaning undocumented. Ad details' `aaa_info` holds the related data when present. | | `searchResults[].menu_items` | `any[]` | Facebook internal field; an empty array in every response we've seen. | | `searchResults[].page_is_deleted` | `boolean` | Whether the advertiser's page has been deleted. | | `searchResults[].regional_regulation_data` | `object` | Delivery limits under regional ad rules. | | `searchResults[].regional_regulation_data.finserv` | `object` | Financial-services ad rules. | | `searchResults[].regional_regulation_data.finserv.is_deemed_finserv` | `boolean` | Facebook treats the ad as a financial-services ad. | | `searchResults[].regional_regulation_data.finserv.is_limited_delivery` | `boolean` | Delivery is limited under financial-services rules. | | `searchResults[].regional_regulation_data.tw_anti_scam` | `object` | Taiwan's anti-scam ad rules. | | `searchResults[].regional_regulation_data.tw_anti_scam.is_limited_delivery` | `boolean` | Delivery is limited under Taiwan's anti-scam rules. | | `searchResults[].report_count` | `number`, nullable | Times the ad was reported, when disclosed; null in every response we've seen. | | `searchResults[].fev_info` | `any` | Facebook internal field; null in every response we've seen. | | `searchResults[].state_media_run_label` | `any` | Label for ads run by state-controlled media; null otherwise. | | `searchResultsCount` | `number`, nullable | Total matching ads, as Facebook estimates it. Only on the first page; null on cursor pages. | | `cursor` | `string`, nullable | Pass as `cursor` for the next page. null on the last page. | ## Pagination Pass the response's `cursor` back as `cursor`, with the other parameters unchanged, for the next page. It's `null` on the last page. Each page costs 1 credit. The first five pages, in JavaScript: ```js let cursor = null; for (let page = 1; page <= 5; page++) { const params = new URLSearchParams({ query: "running shoes", country: "US", status: "active" }); if (cursor) params.set("cursor", cursor); const res = await fetch(`https://api.lurkapi.com/v1/facebook/adLibrary/search/ads?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.searchResults); cursor = data.cursor; if (!cursor) break; // last page } ``` ## Caching and freshness Responses are cached for up to 30 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `facebook_search_ads` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's facebook_search_ads with query "running shoes", country "US", status "active" and summarize what you find. ``` Claude calls `facebook_search_ads` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "query": "running shoes", "country": "US", "status": "active" } ``` Tool results skip nulls and empty lists to save tokens, and `trim` defaults to `true`. --- Next: [Ad Library ads by company](https://lurkapi.com/docs/facebook/company-ads.md) --- # Ad Library ads by company > Every ad one advertiser is running (or ran) on Facebook and Instagram, from Meta's Ad Library. - **Request:** `GET https://api.lurkapi.com/v1/facebook/adLibrary/company/ads` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `facebook_company_ads` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 30 minutes - **Try it live:** https://lurkapi.com/docs/facebook/company-ads#try (no signup) - **Platform:** [Facebook](https://lurkapi.com/docs/facebook.md) (facebook.com/ads/library) - **Web page:** https://lurkapi.com/docs/facebook/company-ads ## When to use this Use it to study one competitor's creative, offers and landing pages. Pass `pageId` (exact) or `companyName` (we use the top match and return it as `page_id` and `page_name`). `status` defaults to all; pass `active` for ads running now. `pageId` comes from search companies (facebook_search_companies) or the `view_all_page_id` in an Ad Library URL. Returns 404 `not_found` if no page matches `companyName`. The list is under `results` (keyword search uses `searchResults`). **Pagination.** The first page returns up to 30 ads plus `searchResultsCount`. Pass `cursor` back (with the same filters) for the next page: cursor pages return up to 10 ads, which is Facebook's own page size, and `searchResultsCount` is null. `cursor` is null on the last page. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `pageId` | string | no | | | The advertiser's Facebook page id, e.g. `15087023444` (Nike). Required unless you pass `companyName`. | `15087023444` | | `companyName` | string | no | | | Advertiser name, e.g. `Nike`. We use the top match from search companies. Ignored when `pageId` is set. | | | `country` | string | no | `ALL` | | Where the ad ran: a 2-letter ISO country code (US, GB, DE…) or ALL. One country per request. | | | `status` | string | no | `all` | `all`, `active`, `inactive` | Only ads that are running now (active), only stopped ads (inactive), or both (all). | `active` | | `media_type` | string | no | `all` | `all`, `image`, `video`, `meme`, `image_and_meme`, `none` | Creative format. `meme` is Facebook's name for image + text; `none` means text-only. | | | `ad_type` | string | no | `ALL` | `ALL`, `POLITICAL_AND_ISSUE_ADS`, `EMPLOYMENT_ADS`, `HOUSING_ADS`, `CREDIT_ADS` | Ad category. Political ads also carry spend and impressions ranges. | | | `language` | string | no | | | Language of the ad text as an ISO 639-1 code, e.g. `en` or `es`. | | | `sort_by` | string | no | `total_impressions` | `total_impressions`, `relevancy_monthly_grouped` | `total_impressions`: most-seen ads first. `relevancy_monthly_grouped`: most recent first. | | | `start_date` | string | no | | | Only ads shown on or after this date (YYYY-MM-DD). The ad itself may have launched earlier. | | | `end_date` | string | no | | | Only ads shown on or before this date (YYYY-MM-DD). | | | `cursor` | string | no | | | The `cursor` from the previous response, to get the next page. Keep every other param the same. | | | `trim` | boolean | no | `false` | `true`, `false` | `true` drops image and video URLs (inside `snapshot.cards` too) for a much smaller response. All text is kept. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/facebook/adLibrary/company/ads?pageId=15087023444&status=active" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ pageId: "15087023444", status: "active", }); const res = await fetch(`https://api.lurkapi.com/v1/facebook/adLibrary/company/ads?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.results); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/facebook/adLibrary/company/ads", params={ "pageId": "15087023444", "status": "active", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["results"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 998, "credits_charged": 1, "page_id": "15087023444", "page_name": "Nike", "results": [ { "ad_archive_id": "1702938977100376", "ad_id": null, "categories": [ "UNKNOWN" ], "collation_count": null, "collation_id": null, "contains_digital_created_media": false, "contains_sensitive_content": false, "currency": "", "end_date": 1790751600, "end_date_string": null, "fev_info": null, "gated_type": "ELIGIBLE", "has_user_reported": false, "hide_data_status": "NONE", "impressions_with_index": { "impressions_text": null, "impressions_index": -1 }, "is_aaa_eligible": false, "is_active": true, "menu_items": [], "page_id": "15087023444", "page_is_deleted": false, "page_name": "Nike", "publisher_platform": [ "FACEBOOK", "INSTAGRAM", "AUDIENCE_NETWORK", "MESSENGER" ], "reach_estimate": null, "regional_regulation_data": { "finserv": { "is_deemed_finserv": false, "is_limited_delivery": false }, "tw_anti_scam": { "is_limited_delivery": false } }, "report_count": null, "snapshot": { "additional_info": null, "branded_content": null, "brazil_tax_id": null, "byline": null, "caption": "itunes.apple.com", "cards": [ { "body": "Bring sports more fully into your day with the Nike App.", "caption": "itunes.apple.com", "cta_text": "Install Now", "cta_type": "INSTALL_MOBILE_APP", "image_crops": [], "link_description": "₱9,895", "link_url": "https://www.nike.com/ph/t/invincible-3-road-running-shoes-LL61DX/DR2660-001", "original_image_url": "https://scontent-iad3-2.xx.fbcdn.net/v/t39.35426-6/431631323_92872743202…", "resized_image_url": "https://scontent-iad3-2.xx.fbcdn.net/v/t39.35426-6/431631323_92872743202…", "title": "Nike Invincible 3 Women's Road Running Shoes", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null }, { "body": "Bring sports more fully into your day with the Nike App.", "caption": "itunes.apple.com", "cta_text": "Install Now", "cta_type": "INSTALL_MOBILE_APP", "image_crops": [], "link_description": "₱3,995", "link_url": "https://www.nike.com/ph/t/run-swift-3-road-running-shoes-2fHbzp/DR2698-002", "original_image_url": "https://scontent-iad6-1.xx.fbcdn.net/v/t39.35426-6/431679131_94921416663…", "resized_image_url": "https://scontent-iad6-1.xx.fbcdn.net/v/t39.35426-6/431679131_94921416663…", "title": "Nike Run Swift 3 Women's Road Running Shoes", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null }, { "body": "Bring sports more fully into your day with the Nike App.", "caption": "itunes.apple.com", "cta_text": "Install Now", "cta_type": "INSTALL_MOBILE_APP", "image_crops": [], "link_description": "₱5,039", "link_url": "https://www.nike.com/ph/t/cosmic-unity-3-basketball-shoes-hcwmW0/DV2757-100", "original_image_url": "https://scontent-iad6-1.xx.fbcdn.net/v/t39.35426-6/431644642_73750860147…", "resized_image_url": "https://scontent-iad6-1.xx.fbcdn.net/v/t39.35426-6/431644642_73750860147…", "title": "Cosmic Unity 3 Basketball Shoes", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null }, { "body": "Bring sports more fully into your day with the Nike App.", "caption": "itunes.apple.com", "cta_text": "Install Now", "cta_type": "INSTALL_MOBILE_APP", "image_crops": [], "link_description": "₱3,599", "link_url": "https://www.nike.com/ph/t/acg-polartec-wolf-tree-mid-rise-trousers-mVbQBn/CV0615-060", "original_image_url": "https://scontent-iad3-1.xx.fbcdn.net/v/t39.35426-6/431748186_28931604736…", "resized_image_url": "https://scontent-iad3-1.xx.fbcdn.net/v/t39.35426-6/431748186_28931604736…", "title": "Nike ACG Polartec ® 'Wolf Tree' Women's Mid-Rise Trousers", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null }, { "body": "Bring sports more fully into your day with the Nike App.", "caption": "itunes.apple.com", "cta_text": "Install Now", "cta_type": "INSTALL_MOBILE_APP", "image_crops": [], "link_description": "₱1,695", "link_url": "https://www.nike.com/ph/t/dri-fit-icon-basketball-jersey-4Z5f8P/DV9968-010", "original_image_url": "https://scontent-iad3-2.xx.fbcdn.net/v/t39.35426-6/431718739_34998891035…", "resized_image_url": "https://scontent-iad3-2.xx.fbcdn.net/v/t39.35426-6/431718739_34998891035…", "title": "Nike Dri-FIT Icon Men's Basketball Jersey", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null }, { "body": "Bring sports more fully into your day with the Nike App.", "caption": "itunes.apple.com", "cta_text": "Install Now", "cta_type": "INSTALL_MOBILE_APP", "image_crops": [], "link_description": "₱3,595", "link_url": "https://www.nike.com/ph/t/fc-barcelona-2023-24-stadium-fourth-dri-fit-fo…", "original_image_url": "https://scontent-iad3-1.xx.fbcdn.net/v/t39.35426-6/431734766_93023398175…", "resized_image_url": "https://scontent-iad3-1.xx.fbcdn.net/v/t39.35426-6/431734766_93023398175…", "title": "Nike F.C. Barcelona 2023/24 Stadium Fourth Women's Nike Dri-FIT Football Shirt", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null } ], "country_iso_code": null, "cta_text": "Install now", "cta_type": "INSTALL_MOBILE_APP", "disclaimer_label": null, "display_format": "DPA", "ec_certificates": [], "event": null, "extra_images": [], "extra_links": [], "extra_texts": [], "extra_videos": [], "images": [], "is_reshared": false, "link_description": null, "link_url": "http://itunes.apple.com/app/id1095459556", "page_categories": [ "Sportswear", "Product/service" ], "page_id": "15087023444", "page_is_deleted": false, "page_like_count": 39514042, "page_name": "Nike", "page_profile_picture_url": "https://scontent-iad3-1.xx.fbcdn.net/v/t39.35426-6/431756959_71287105437…", "page_profile_uri": "https://facebook.com/nike", "root_reshared_post": null, "title": "{{product.name}}", "videos": [], "body": { "text": "Bring sports more fully into your day with the Nike App." } }, "spend": null, "start_date": 1744009200, "start_date_string": null, "state_media_run_label": null, "targeted_or_reached_countries": [], "total_active_time": null, "url": null }, { "ad_archive_id": "1559738104701476", "ad_id": null, "categories": [ "UNKNOWN" ], "collation_count": null, "collation_id": null, "contains_digital_created_media": false, "contains_sensitive_content": false, "currency": "", "end_date": 1790751600, "end_date_string": null, "fev_info": null, "gated_type": "ELIGIBLE", "has_user_reported": false, "hide_data_status": "NONE", "impressions_with_index": { "impressions_text": null, "impressions_index": -1 }, "is_aaa_eligible": false, "is_active": true, "menu_items": [], "page_id": "15087023444", "page_is_deleted": false, "page_name": "Nike", "publisher_platform": [ "FACEBOOK", "INSTAGRAM", "AUDIENCE_NETWORK", "MESSENGER" ], "reach_estimate": null, "regional_regulation_data": { "finserv": { "is_deemed_finserv": false, "is_limited_delivery": false }, "tw_anti_scam": { "is_limited_delivery": false } }, "report_count": null, "snapshot": { "additional_info": null, "branded_content": null, "brazil_tax_id": null, "byline": null, "caption": "play.google.com", "cards": [ { "body": "Encuentra tu mejor estilo.\nLlegó el momento de que disfrutes los benefic…", "caption": "play.google.com", "cta_text": "Install Now", "cta_type": "INSTALL_MOBILE_APP", "image_crops": [], "link_description": "$2,294", "link_url": "https://www.nike.com/mx/t/calzado-air-force-1-07-WGq1wQ?cp=54413048966_soc_", "original_image_url": "https://scontent-iad3-2.xx.fbcdn.net/v/t39.35426-6/400057885_87665133739…", "resized_image_url": "https://scontent-iad3-2.xx.fbcdn.net/v/t39.35426-6/400057885_87665133739…", "title": "Calzado para mujer Nike Air Force 1 '07 - Blanco", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null }, { "body": "Encuentra tu mejor estilo.\nLlegó el momento de que disfrutes los benefic…", "caption": "play.google.com", "cta_text": "Install Now", "cta_type": "INSTALL_MOBILE_APP", "image_crops": [], "link_description": "$1,409", "link_url": "https://www.nike.com/mx/t/pants-de-tejido-woven-upf-sportswear-tech-pack…", "original_image_url": "https://scontent-iad3-2.xx.fbcdn.net/v/t39.35426-6/399999246_15658089508…", "resized_image_url": "https://scontent-iad3-2.xx.fbcdn.net/v/t39.35426-6/399999246_15658089508…", "title": "Pants de tejido Woven para hombre UPF Nike Sportswear Tech Pack - Negro", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null }, { "body": "Encuentra tu mejor estilo.\nLlegó el momento de que disfrutes los benefic…", "caption": "play.google.com", "cta_text": "Install Now", "cta_type": "INSTALL_MOBILE_APP", "image_crops": [], "link_description": "$854", "link_url": "https://www.nike.com/mx/t/shorts-dri-fit-de-18-900-versátiles-sin-forro-…", "original_image_url": "https://scontent-iad3-2.xx.fbcdn.net/v/t39.35426-6/399960590_10038894207…", "resized_image_url": "https://scontent-iad3-2.xx.fbcdn.net/v/t39.35426-6/399960590_10038894207…", "title": "Shorts Dri-FIT de 18 cm versátiles sin forro para hombre Nike Form - Negro", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null }, { "body": "Encuentra tu mejor estilo.\nLlegó el momento de que disfrutes los benefic…", "caption": "play.google.com", "cta_text": "Install Now", "cta_type": "INSTALL_MOBILE_APP", "image_crops": [], "link_description": "$2,699", "link_url": "https://www.nike.com/mx/t/calzado-blazer-mid-77-se-j99WT3?cp=54413048966_soc_", "original_image_url": "https://scontent-iad3-1.xx.fbcdn.net/v/t39.35426-6/400107091_32468404422…", "resized_image_url": "https://scontent-iad3-1.xx.fbcdn.net/v/t39.35426-6/400107091_32468404422…", "title": "Calzado para hombre Nike Blazer Mid '77 SE - Blanco", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null }, { "body": "Encuentra tu mejor estilo.\nLlegó el momento de que disfrutes los benefic…", "caption": "play.google.com", "cta_text": "Install Now", "cta_type": "INSTALL_MOBILE_APP", "image_crops": [], "link_description": "$584", "link_url": "https://www.nike.com/mx/t/camiseta-de-tirantes-estampada-cropped-dri-fit…", "original_image_url": "https://scontent-iad6-1.xx.fbcdn.net/v/t39.35426-6/399930460_24955988143…", "resized_image_url": "https://scontent-iad6-1.xx.fbcdn.net/v/t39.35426-6/399930460_24955988143…", "title": "Camiseta de tirantes estampada cropped para mujer Nike Dri-FIT One - Negro", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null }, { "body": "Encuentra tu mejor estilo.\nLlegó el momento de que disfrutes los benefic…", "caption": "play.google.com", "cta_text": "Install Now", "cta_type": "INSTALL_MOBILE_APP", "image_crops": [], "link_description": "$2,069", "link_url": "https://www.nike.com/mx/t/dri-fit-adv-aps-chamarra-de-condición-física-7…", "original_image_url": "https://scontent-iad3-1.xx.fbcdn.net/v/t39.35426-6/399982152_69380555947…", "resized_image_url": "https://scontent-iad3-1.xx.fbcdn.net/v/t39.35426-6/399982152_69380555947…", "title": "Nike Dri-FIT ADV A.P.S. Chamarra de condición física para hombre - Negro", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null } ], "country_iso_code": null, "cta_text": "Install now", "cta_type": "INSTALL_MOBILE_APP", "disclaimer_label": null, "display_format": "DPA", "ec_certificates": [], "event": null, "extra_images": [], "extra_links": [], "extra_texts": [], "extra_videos": [], "images": [], "is_reshared": false, "link_description": null, "link_url": "http://play.google.com/store/apps/details?id=com.nike.omega", "page_categories": [ "Sportswear", "Product/service" ], "page_id": "15087023444", "page_is_deleted": false, "page_like_count": 39514042, "page_name": "Nike", "page_profile_picture_url": "https://scontent-iad6-1.xx.fbcdn.net/v/t39.35426-6/400027547_35873985350…", "page_profile_uri": "https://facebook.com/nike", "root_reshared_post": null, "title": "{{product.name}}", "videos": [], "body": { "text": "Encuentra tu mejor estilo.\nLlegó el momento de que disfrutes los benefic…" } }, "spend": null, "start_date": 1745564400, "start_date_string": null, "state_media_run_label": null, "targeted_or_reached_countries": [], "total_active_time": null, "url": null } ], "searchResultsCount": 3198, "cursor": "AQHTSMQB_ilt9UKh88HFxw9XeDw_S-boWBzDyu2zJ501ztCVwRkxXJhPBxpxMJXVjKXd" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `page_id` | `string` | The page these ads are from: `pageId` as given, or the page we matched for `companyName`. | | `page_name` | `string`, nullable | That page's name (the matched page for `companyName`, else from the ads); null when there are no ads. | | `results` | `object[]` | The advertiser's ads. | | `results[].ad_archive_id` | `string`, nullable | The Ad Library id. Pass it as `id` to the ad details endpoint. | | `results[].ad_id` | `string`, nullable | Meta's internal ad id. null in every response we've seen; use the Ad Library id. | | `results[].page_id` | `string`, nullable | The advertiser's Facebook page id. Pass it as `pageId` to the company ads endpoint. | | `results[].page_name` | `string`, nullable | The advertiser's page name. | | `results[].is_active` | `boolean` | Whether the ad is running now. | | `results[].start_date` | `number`, nullable | When the ad started running, Unix seconds (UTC). | | `results[].end_date` | `number`, nullable | When the ad stopped, or its latest delivery date if still running, Unix seconds (UTC). | | `results[].start_date_string` | `string`, nullable | The same date as ISO 8601 text when Facebook includes it; often null, so prefer the Unix seconds field. | | `results[].end_date_string` | `string`, nullable | The same date as ISO 8601 text when Facebook includes it; often null, so prefer the Unix seconds field. | | `results[].total_active_time` | `number`, nullable | Seconds the ad has run, when Facebook reports it; usually null. | | `results[].publisher_platform` | `string[]` | Where it ran, uppercase: FACEBOOK, INSTAGRAM, AUDIENCE_NETWORK, MESSENGER, THREADS. | | `results[].collation_count` | `number`, nullable | How many near-identical versions are grouped under this ad. A high count means the advertiser is scaling it; null for a single version. | | `results[].collation_id` | `string`, nullable | Id shared by the grouped versions of this ad; null for a single version. | | `results[].snapshot` | `object` | The creative: copy, media, landing page and advertiser details. | | `results[].snapshot.page_id` | `string`, nullable | The advertiser's page id (same as the ad's). | | `results[].snapshot.page_name` | `string`, nullable | The advertiser's page name as shown on the ad. | | `results[].snapshot.page_profile_uri` | `string`, nullable | The advertiser's Facebook page URL. | | `results[].snapshot.page_profile_picture_url` | `string`, nullable | The advertiser's profile picture, 60×60 px. | | `results[].snapshot.page_like_count` | `number`, nullable | Followers of the advertiser's page. | | `results[].snapshot.page_categories` | `string[]` | Page categories, e.g. ["Outdoor & Sporting Goods Company"]. | | `results[].snapshot.page_is_deleted` | `boolean` | Whether the advertiser's page has been deleted. | | `results[].snapshot.title` | `string`, nullable | Headline under the creative; null when there is none. | | `results[].snapshot.caption` | `string`, nullable | Display link under the creative, usually the domain (e.g. HOKA.com). | | `results[].snapshot.link_url` | `string`, nullable | Landing page the ad sends people to, UTM tags included. | | `results[].snapshot.link_description` | `string`, nullable | Text under the headline; null when there is none. | | `results[].snapshot.cta_text` | `string`, nullable | Button label, e.g. "Shop now". | | `results[].snapshot.cta_type` | `string`, nullable | Button type, e.g. SHOP_NOW, LEARN_MORE, NO_BUTTON. | | `results[].snapshot.display_format` | `string`, nullable | Creative format, e.g. IMAGE, VIDEO, DCO (dynamic creative), DPA (dynamic product ad) or MULTI_IMAGES. For DCO and DPA ads the creative is in `cards`. | | `results[].snapshot.images` | `object[]` | Image creatives. Empty for video and card-based ads, and with trim=true. | | `results[].snapshot.images[].original_image_url` | `string`, nullable | Full-size image URL. Facebook CDN links expire after a few days; download what you need. | | `results[].snapshot.images[].resized_image_url` | `string`, nullable | 600 px image URL, good for thumbnails. | | `results[].snapshot.images[].watermarked_resized_image_url` | `string` | Watermarked copy; empty or null in every response we've seen. | | `results[].snapshot.images[].image_crops` | `any[]` | Facebook internal field; an empty array in every response we've seen. Crop hints for the image. | | `results[].snapshot.videos` | `object[]` | Video creatives. Empty for image and card-based ads, and with trim=true. | | `results[].snapshot.videos[].video_hd_url` | `string`, nullable | HD video file URL; null if Facebook has no HD version. | | `results[].snapshot.videos[].video_sd_url` | `string`, nullable | SD video file URL. | | `results[].snapshot.videos[].video_preview_image_url` | `string`, nullable | Poster frame (still image) URL. | | `results[].snapshot.videos[].watermarked_video_hd_url` | `string`, nullable | Watermarked copy; empty or null in every response we've seen. | | `results[].snapshot.videos[].watermarked_video_sd_url` | `string`, nullable | Watermarked copy; empty or null in every response we've seen. | | `results[].snapshot.cards` | `object[]` | Carousel, dynamic-creative and product versions, each with its own copy and media. With trim=true the text stays and media URLs are null. | | `results[].snapshot.cards[].title` | `string`, nullable | This version's headline. | | `results[].snapshot.cards[].body` | `string`, nullable | This version's primary text. | | `results[].snapshot.cards[].caption` | `string`, nullable | This version's display link, usually the domain. | | `results[].snapshot.cards[].cta_text` | `string`, nullable | Button label, e.g. "Shop now". | | `results[].snapshot.cards[].cta_type` | `string`, nullable | Button type, e.g. SHOP_NOW, LEARN_MORE. | | `results[].snapshot.cards[].link_url` | `string`, nullable | This version's landing page URL. | | `results[].snapshot.cards[].link_description` | `string`, nullable | Text under the headline; null when there is none. | | `results[].snapshot.cards[].original_image_url` | `string`, nullable | Full-size image URL; null for video versions. | | `results[].snapshot.cards[].resized_image_url` | `string`, nullable | 600 px image URL; null for video versions. | | `results[].snapshot.cards[].watermarked_resized_image_url` | `string` | Watermarked copy; empty or null in every response we've seen. | | `results[].snapshot.cards[].image_crops` | `any[]` | Facebook internal field; an empty array in every response we've seen. Crop hints for the image. | | `results[].snapshot.cards[].video_hd_url` | `string`, nullable | HD video file URL; null for image versions. | | `results[].snapshot.cards[].video_sd_url` | `string`, nullable | SD video file URL; null for image versions. | | `results[].snapshot.cards[].video_preview_image_url` | `string`, nullable | Poster frame URL; null for image versions. | | `results[].snapshot.cards[].watermarked_video_hd_url` | `string`, nullable | Watermarked copy; empty or null in every response we've seen. | | `results[].snapshot.cards[].watermarked_video_sd_url` | `string`, nullable | Watermarked copy; empty or null in every response we've seen. | | `results[].snapshot.byline` | `string`, nullable | Line under the page name, when shown; null or empty in most ads. | | `results[].snapshot.disclaimer_label` | `string`, nullable | "Paid for by" line on political and issue ads; null otherwise. | | `results[].snapshot.country_iso_code` | `string`, nullable | Facebook internal field; null in every response we've seen. A country code on some ads. | | `results[].snapshot.is_reshared` | `boolean` | Whether the ad reshares an existing post (see `root_reshared_post`). | | `results[].snapshot.root_reshared_post` | `any` | The original post when `is_reshared` is true; null otherwise. | | `results[].snapshot.branded_content` | `any` | Paid partnership details when the ad is branded content; null otherwise. | | `results[].snapshot.additional_info` | `any` | Facebook internal field; null in every response we've seen. | | `results[].snapshot.brazil_tax_id` | `any` | Advertiser tax id shown on ads under Brazil's rules; null otherwise. | | `results[].snapshot.ec_certificates` | `any[]` | Facebook internal field; an empty array in every response we've seen. | | `results[].snapshot.event` | `any` | Event details on event ads; null otherwise. | | `results[].snapshot.extra_images` | `any[]` | More images from dynamic creative; usually empty. | | `results[].snapshot.extra_links` | `any[]` | More landing page URLs from dynamic creative; usually empty. | | `results[].snapshot.extra_texts` | `any[]` | More text variations from dynamic creative; usually empty. | | `results[].snapshot.extra_videos` | `any[]` | More videos from dynamic creative; usually empty. | | `results[].snapshot.body` | `object`, nullable | Primary text as `{ text }`; null when the copy is only in `cards`. | | `results[].snapshot.body.text` | `string`, nullable | The ad's primary text. | | `results[].categories` | `string[]` | Special ad categories Facebook assigned. ["UNKNOWN"] for ordinary ads. | | `results[].targeted_or_reached_countries` | `string[]` | Countries the ad targeted or reached, when Facebook discloses them; empty in most results. | | `results[].currency` | `string` | Currency code of `spend` (e.g. USD) when spend is reported; an empty string otherwise. | | `results[].spend` | `any` | Spend range, reported for political and issue ads only; null otherwise. | | `results[].impressions_with_index` | `object` | Impressions range, reported for political and issue ads only. | | `results[].impressions_with_index.impressions_text` | `string`, nullable | The range as Facebook shows it; null when not reported. | | `results[].impressions_with_index.impressions_index` | `number` | Facebook's bucket number for the range; -1 when not reported. | | `results[].reach_estimate` | `any` | Estimated reach, which Facebook reports for some EU-delivered ads; null otherwise. | | `results[].url` | `string`, nullable | Ad Library link for this ad. Often null; build it as https://www.facebook.com/ads/library/?id=. | | `results[].contains_digital_created_media` | `boolean` | Whether Facebook labels the creative as AI-generated or digitally altered. | | `results[].contains_sensitive_content` | `boolean` | Facebook internal field; false in every response we've seen. Facebook's sensitive-content flag. | | `results[].gated_type` | `string` | Facebook internal field; ELIGIBLE (lowercase in ad details) in every response we've seen. | | `results[].has_user_reported` | `boolean` | Whether the viewing user reported the ad. Always false: we read the Ad Library logged out. | | `results[].hide_data_status` | `string` | Facebook internal field; NONE in every response we've seen. | | `results[].is_aaa_eligible` | `boolean` | Facebook flag, true on a few ads; meaning undocumented. Ad details' `aaa_info` holds the related data when present. | | `results[].menu_items` | `any[]` | Facebook internal field; an empty array in every response we've seen. | | `results[].page_is_deleted` | `boolean` | Whether the advertiser's page has been deleted. | | `results[].regional_regulation_data` | `object` | Delivery limits under regional ad rules. | | `results[].regional_regulation_data.finserv` | `object` | Financial-services ad rules. | | `results[].regional_regulation_data.finserv.is_deemed_finserv` | `boolean` | Facebook treats the ad as a financial-services ad. | | `results[].regional_regulation_data.finserv.is_limited_delivery` | `boolean` | Delivery is limited under financial-services rules. | | `results[].regional_regulation_data.tw_anti_scam` | `object` | Taiwan's anti-scam ad rules. | | `results[].regional_regulation_data.tw_anti_scam.is_limited_delivery` | `boolean` | Delivery is limited under Taiwan's anti-scam rules. | | `results[].report_count` | `number`, nullable | Times the ad was reported, when disclosed; null in every response we've seen. | | `results[].fev_info` | `any` | Facebook internal field; null in every response we've seen. | | `results[].state_media_run_label` | `any` | Label for ads run by state-controlled media; null otherwise. | | `searchResultsCount` | `number`, nullable | Total matching ads, as Facebook estimates it. Only on the first page; null on cursor pages. | | `cursor` | `string`, nullable | Pass as `cursor` for the next page. null on the last page. | ## Pagination Pass the response's `cursor` back as `cursor`, with the other parameters unchanged, for the next page. It's `null` on the last page. Each page costs 1 credit. The first five pages, in JavaScript: ```js let cursor = null; for (let page = 1; page <= 5; page++) { const params = new URLSearchParams({ pageId: "15087023444", status: "active" }); if (cursor) params.set("cursor", cursor); const res = await fetch(`https://api.lurkapi.com/v1/facebook/adLibrary/company/ads?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.results); cursor = data.cursor; if (!cursor) break; // last page } ``` ## Caching and freshness Responses are cached for up to 30 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `facebook_company_ads` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's facebook_company_ads with pageId "15087023444", status "active" and summarize what you find. ``` Claude calls `facebook_company_ads` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "pageId": "15087023444", "status": "active" } ``` Tool results skip nulls and empty lists to save tokens, and `trim` defaults to `true`. --- Previous: [Search Ad Library ads](https://lurkapi.com/docs/facebook/search-ads.md) · Next: [Ad Library ad details](https://lurkapi.com/docs/facebook/ad.md) --- # Ad Library ad details > Full detail for one ad from Meta's Ad Library, by its id or URL. - **Request:** `GET https://api.lurkapi.com/v1/facebook/adLibrary/ad` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `facebook_ad` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 6 hours - **Try it live:** https://lurkapi.com/docs/facebook/ad#try (no signup) - **Platform:** [Facebook](https://lurkapi.com/docs/facebook.md) (facebook.com/ads/library) - **Web page:** https://lurkapi.com/docs/facebook/ad ## When to use this Use it for one ad's full creative after a search: pass its `ad_archive_id` as `id`, or an Ad Library link as `url`. Top-level keys here are camelCase (`adArchiveID`, `isActive`). Everything about a single ad: copy, every image and video, landing page, platforms and run dates. Unlike the list endpoints, top-level keys are camelCase (`adArchiveID`, `pageName`, `isActive`), `publisherPlatform` values are lowercase and `snapshot.body` is a plain string. Ads with several versions keep their creative in `snapshot.cards`. Returns 404 `not_found` if the ad doesn't exist. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `id` | string | no | | | The ad's Ad Library id (`ad_archive_id`), e.g. `1016451784037642`. Required unless you pass `url`. | `1016451784037642` | | `url` | string | no | | | An Ad Library link containing `?id=…`, e.g. `https://www.facebook.com/ads/library/?id=1016451784037642`. | | | `trim` | boolean | no | `false` | `true`, `false` | `true` drops image and video URLs (inside `snapshot.cards` too). All text is kept. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/facebook/adLibrary/ad?id=1016451784037642" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ id: "1016451784037642", }); const res = await fetch(`https://api.lurkapi.com/v1/facebook/adLibrary/ad?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.publisherPlatform); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/facebook/adLibrary/ad", params={ "id": "1016451784037642", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["publisherPlatform"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 997, "credits_charged": 1, "adArchiveID": "1016451784037642", "adid": null, "categories": [ "UNKNOWN" ], "collationCount": null, "collationID": null, "containsDigitallyCreatedMedia": false, "containsSensitiveContent": false, "currency": "", "endDate": 1756364400, "endDateString": null, "fevInfo": null, "gatedType": "eligible", "hasUserReported": false, "hideDataStatus": "NONE", "impressionsWithIndex": { "impressionsText": null, "impressionsIndex": -1 }, "isAAAEligible": false, "isActive": true, "menuItems": [], "pageID": "15087023444", "pageIsDeleted": false, "pageName": "Nike", "publisherPlatform": [ "facebook", "instagram" ], "reachEstimate": null, "regionalRegulationData": { "finserv": { "is_deemed_finserv": false, "is_limited_delivery": false }, "tw_anti_scam": { "is_limited_delivery": false } }, "reportCount": null, "snapshot": { "additional_info": null, "branded_content": null, "brazil_tax_id": null, "byline": null, "caption": "NIKE.COM", "cards": [ { "body": "Get the gear that goes hard on and off the field.", "caption": "nike.com", "cta_text": "Shop Now", "cta_type": "SHOP_NOW", "image_crops": [], "link_description": "", "link_url": "https://www.nike.com/t/air-monarch-iv-mens-workout-shoes-extra-wide-p8qN…", "original_image_url": "https://scontent-sjc3-1.xx.fbcdn.net/v/t39.35426-6/509269099_74052692168…", "resized_image_url": "https://scontent-sjc3-1.xx.fbcdn.net/v/t39.35426-6/509269099_74052692168…", "title": "Nike Air Monarch IV ", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null }, { "body": "Get the gear that goes hard on and off the field.", "caption": "nike.com", "cta_text": "Shop Now", "cta_type": "SHOP_NOW", "image_crops": [], "link_description": "", "link_url": "https://www.nike.com/t/tempo-shorts-toddler-shorts-Gx0CH3/267358-019?dplnk=member", "original_image_url": "https://scontent-sjc6-1.xx.fbcdn.net/v/t39.35426-6/508839160_17958205780…", "resized_image_url": "https://scontent-sjc6-1.xx.fbcdn.net/v/t39.35426-6/508839160_17958205780…", "title": "Nike Tempo Shorts ", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null }, { "body": "Get the gear that goes hard on and off the field.", "caption": "nike.com", "cta_text": "Shop Now", "cta_type": "SHOP_NOW", "image_crops": [], "link_description": "", "link_url": "https://www.nike.com/t/air-monarch-iv-mens-workout-shoes-p8qNlT/415445-001?dplnk=member", "original_image_url": "https://scontent-sjc6-1.xx.fbcdn.net/v/t39.35426-6/509290535_73016820272…", "resized_image_url": "https://scontent-sjc6-1.xx.fbcdn.net/v/t39.35426-6/509290535_73016820272…", "title": "Nike Air Monarch IV ", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null }, { "body": "Get the gear that goes hard on and off the field.", "caption": "nike.com", "cta_text": "Shop Now", "cta_type": "SHOP_NOW", "image_crops": [], "link_description": "", "link_url": "https://www.nike.com/t/air-monarch-iv-mens-workout-shoes-extra-wide-p8qN…", "original_image_url": "https://scontent-sjc6-1.xx.fbcdn.net/v/t39.35426-6/509359383_12428901607…", "resized_image_url": "https://scontent-sjc6-1.xx.fbcdn.net/v/t39.35426-6/509359383_12428901607…", "title": "Nike Air Monarch IV ", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null }, { "body": "Get the gear that goes hard on and off the field.", "caption": "nike.com", "cta_text": "Shop Now", "cta_type": "SHOP_NOW", "image_crops": [], "link_description": "", "link_url": "https://www.nike.com/t/air-monarch-iv-mens-workout-shoes-p8qNlT/415445-102?dplnk=member", "original_image_url": "https://scontent-sjc6-1.xx.fbcdn.net/v/t39.35426-6/508595289_38944231908…", "resized_image_url": "https://scontent-sjc6-1.xx.fbcdn.net/v/t39.35426-6/508595289_38944231908…", "title": "Nike Air Monarch IV ", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null }, { "body": "Get the gear that goes hard on and off the field.", "caption": "nike.com", "cta_text": "Shop Now", "cta_type": "SHOP_NOW", "image_crops": [], "link_description": "", "link_url": "https://www.nike.com/t/air-monarch-iv-mens-workout-shoes-extra-wide-p8qN…", "original_image_url": "https://scontent-sjc3-1.xx.fbcdn.net/v/t39.35426-6/509094198_14858974427…", "resized_image_url": "https://scontent-sjc3-1.xx.fbcdn.net/v/t39.35426-6/509094198_14858974427…", "title": "Nike Air Monarch IV ", "video_hd_url": null, "video_preview_image_url": null, "video_sd_url": null, "watermarked_resized_image_url": "", "watermarked_video_hd_url": null, "watermarked_video_sd_url": null } ], "country_iso_code": null, "cta_text": "Shop now", "cta_type": "SHOP_NOW", "disclaimer_label": null, "display_format": "DPA", "ec_certificates": [], "event": null, "extra_images": [], "extra_links": [], "extra_texts": [], "extra_videos": [], "images": [], "is_reshared": false, "link_description": null, "link_url": "https://www.nike.com/us/en_us", "page_categories": [ "Sportswear" ], "page_id": "15087023444", "page_is_deleted": false, "page_like_count": 39514042, "page_name": "Nike", "page_profile_picture_url": "https://scontent-sjc6-1.xx.fbcdn.net/v/t39.35426-6/509357337_40881708814…", "page_profile_uri": "https://facebook.com/nike", "root_reshared_post": null, "title": "{{product.name}}", "videos": [], "body": "Get the gear that goes hard on and off the field.", "final_url": null }, "spend": null, "startDate": 1750402800, "startDateString": null, "stateMediaRunLabel": null, "totalActiveTime": null, "url": null, "aaa_info": null } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `adArchiveID` | `string`, nullable | The Ad Library id. Pass it as `id` to the ad details endpoint. | | `adid` | `string`, nullable | Meta's internal ad id. null in every response we've seen; use the Ad Library id. | | `pageID` | `string`, nullable | The advertiser's Facebook page id. Pass it as `pageId` to the company ads endpoint. | | `pageName` | `string`, nullable | The advertiser's page name. | | `isActive` | `boolean` | Whether the ad is running now. | | `startDate` | `number`, nullable | When the ad started running, Unix seconds (UTC). | | `endDate` | `number`, nullable | When the ad stopped, or its latest delivery date if still running, Unix seconds (UTC). | | `startDateString` | `string`, nullable | The same date as ISO 8601 text when Facebook includes it; often null, so prefer the Unix seconds field. | | `endDateString` | `string`, nullable | The same date as ISO 8601 text when Facebook includes it; often null, so prefer the Unix seconds field. | | `totalActiveTime` | `number`, nullable | Seconds the ad has run, when Facebook reports it; usually null. | | `publisherPlatform` | `string[]` | Where it ran, lowercase: facebook, instagram, audience_network, messenger, threads. | | `collationCount` | `number`, nullable | How many near-identical versions are grouped under this ad. A high count means the advertiser is scaling it; null for a single version. | | `collationID` | `string`, nullable | Id shared by the grouped versions of this ad; null for a single version. | | `snapshot` | `object` | The creative: copy, media, landing page and advertiser details (snake_case inside). | | `snapshot.page_id` | `string`, nullable | The advertiser's page id (same as the ad's). | | `snapshot.page_name` | `string`, nullable | The advertiser's page name as shown on the ad. | | `snapshot.page_profile_uri` | `string`, nullable | The advertiser's Facebook page URL. | | `snapshot.page_profile_picture_url` | `string`, nullable | The advertiser's profile picture, 60×60 px. | | `snapshot.page_like_count` | `number`, nullable | Followers of the advertiser's page. | | `snapshot.page_categories` | `string[]` | Page categories, e.g. ["Outdoor & Sporting Goods Company"]. | | `snapshot.page_is_deleted` | `boolean` | Whether the advertiser's page has been deleted. | | `snapshot.title` | `string`, nullable | Headline under the creative; null when there is none. | | `snapshot.caption` | `string`, nullable | Display link under the creative, usually the domain (e.g. HOKA.com). | | `snapshot.link_url` | `string`, nullable | Landing page the ad sends people to, UTM tags included. | | `snapshot.link_description` | `string`, nullable | Text under the headline; null when there is none. | | `snapshot.cta_text` | `string`, nullable | Button label, e.g. "Shop now". | | `snapshot.cta_type` | `string`, nullable | Button type, e.g. SHOP_NOW, LEARN_MORE, NO_BUTTON. | | `snapshot.display_format` | `string`, nullable | Creative format, e.g. IMAGE, VIDEO, DCO (dynamic creative), DPA (dynamic product ad) or MULTI_IMAGES. For DCO and DPA ads the creative is in `cards`. | | `snapshot.images` | `object[]` | Image creatives. Empty for video and card-based ads, and with trim=true. | | `snapshot.images[].original_image_url` | `string`, nullable | Full-size image URL. Facebook CDN links expire after a few days; download what you need. | | `snapshot.images[].resized_image_url` | `string`, nullable | 600 px image URL, good for thumbnails. | | `snapshot.images[].watermarked_resized_image_url` | `string` | Watermarked copy; empty or null in every response we've seen. | | `snapshot.images[].image_crops` | `any[]` | Facebook internal field; an empty array in every response we've seen. Crop hints for the image. | | `snapshot.videos` | `object[]` | Video creatives. Empty for image and card-based ads, and with trim=true. | | `snapshot.videos[].video_hd_url` | `string`, nullable | HD video file URL; null if Facebook has no HD version. | | `snapshot.videos[].video_sd_url` | `string`, nullable | SD video file URL. | | `snapshot.videos[].video_preview_image_url` | `string`, nullable | Poster frame (still image) URL. | | `snapshot.videos[].watermarked_video_hd_url` | `string`, nullable | Watermarked copy; empty or null in every response we've seen. | | `snapshot.videos[].watermarked_video_sd_url` | `string`, nullable | Watermarked copy; empty or null in every response we've seen. | | `snapshot.cards` | `object[]` | Carousel, dynamic-creative and product versions, each with its own copy and media. With trim=true the text stays and media URLs are null. | | `snapshot.cards[].title` | `string`, nullable | This version's headline. | | `snapshot.cards[].body` | `string`, nullable | This version's primary text. | | `snapshot.cards[].caption` | `string`, nullable | This version's display link, usually the domain. | | `snapshot.cards[].cta_text` | `string`, nullable | Button label, e.g. "Shop now". | | `snapshot.cards[].cta_type` | `string`, nullable | Button type, e.g. SHOP_NOW, LEARN_MORE. | | `snapshot.cards[].link_url` | `string`, nullable | This version's landing page URL. | | `snapshot.cards[].link_description` | `string`, nullable | Text under the headline; null when there is none. | | `snapshot.cards[].original_image_url` | `string`, nullable | Full-size image URL; null for video versions. | | `snapshot.cards[].resized_image_url` | `string`, nullable | 600 px image URL; null for video versions. | | `snapshot.cards[].watermarked_resized_image_url` | `string` | Watermarked copy; empty or null in every response we've seen. | | `snapshot.cards[].image_crops` | `any[]` | Facebook internal field; an empty array in every response we've seen. Crop hints for the image. | | `snapshot.cards[].video_hd_url` | `string`, nullable | HD video file URL; null for image versions. | | `snapshot.cards[].video_sd_url` | `string`, nullable | SD video file URL; null for image versions. | | `snapshot.cards[].video_preview_image_url` | `string`, nullable | Poster frame URL; null for image versions. | | `snapshot.cards[].watermarked_video_hd_url` | `string`, nullable | Watermarked copy; empty or null in every response we've seen. | | `snapshot.cards[].watermarked_video_sd_url` | `string`, nullable | Watermarked copy; empty or null in every response we've seen. | | `snapshot.byline` | `string`, nullable | Line under the page name, when shown; null or empty in most ads. | | `snapshot.disclaimer_label` | `string`, nullable | "Paid for by" line on political and issue ads; null otherwise. | | `snapshot.country_iso_code` | `string`, nullable | Facebook internal field; null in every response we've seen. A country code on some ads. | | `snapshot.is_reshared` | `boolean` | Whether the ad reshares an existing post (see `root_reshared_post`). | | `snapshot.root_reshared_post` | `any` | The original post when `is_reshared` is true; null otherwise. | | `snapshot.branded_content` | `any` | Paid partnership details when the ad is branded content; null otherwise. | | `snapshot.additional_info` | `any` | Facebook internal field; null in every response we've seen. | | `snapshot.brazil_tax_id` | `any` | Advertiser tax id shown on ads under Brazil's rules; null otherwise. | | `snapshot.ec_certificates` | `any[]` | Facebook internal field; an empty array in every response we've seen. | | `snapshot.event` | `any` | Event details on event ads; null otherwise. | | `snapshot.extra_images` | `any[]` | More images from dynamic creative; usually empty. | | `snapshot.extra_links` | `any[]` | More landing page URLs from dynamic creative; usually empty. | | `snapshot.extra_texts` | `any[]` | More text variations from dynamic creative; usually empty. | | `snapshot.extra_videos` | `any[]` | More videos from dynamic creative; usually empty. | | `snapshot.body` | `string`, nullable | The ad's primary text as a plain string; null when the copy is only in `cards`. | | `snapshot.final_url` | `string`, nullable | Final landing URL after redirects, when Facebook reports it; usually null. | | `categories` | `string[]` | Special ad categories Facebook assigned. ["UNKNOWN"] for ordinary ads. | | `currency` | `string` | Currency code of `spend` (e.g. USD) when spend is reported; an empty string otherwise. | | `spend` | `any` | Spend range, reported for political and issue ads only; null otherwise. | | `impressionsWithIndex` | `object` | Impressions range, reported for political and issue ads only. | | `impressionsWithIndex.impressionsText` | `string`, nullable | The range as Facebook shows it; null when not reported. | | `impressionsWithIndex.impressionsIndex` | `number` | Facebook's bucket number for the range; -1 when not reported. | | `reachEstimate` | `any` | Estimated reach, which Facebook reports for some EU-delivered ads; null otherwise. | | `url` | `string`, nullable | Ad Library link for this ad. Often null; build it as https://www.facebook.com/ads/library/?id=. | | `containsDigitallyCreatedMedia` | `boolean` | Whether Facebook labels the creative as AI-generated or digitally altered. | | `containsSensitiveContent` | `boolean` | Facebook internal field; false in every response we've seen. Facebook's sensitive-content flag. | | `gatedType` | `string` | Facebook internal field; ELIGIBLE (lowercase in ad details) in every response we've seen. | | `hasUserReported` | `boolean` | Whether the viewing user reported the ad. Always false: we read the Ad Library logged out. | | `hideDataStatus` | `string` | Facebook internal field; NONE in every response we've seen. | | `isAAAEligible` | `boolean` | Facebook flag, true on a few ads; meaning undocumented. Ad details' `aaa_info` holds the related data when present. | | `menuItems` | `any[]` | Facebook internal field; an empty array in every response we've seen. | | `pageIsDeleted` | `boolean` | Whether the advertiser's page has been deleted. | | `regionalRegulationData` | `object` | Delivery limits under regional ad rules. | | `regionalRegulationData.finserv` | `object` | Financial-services ad rules. | | `regionalRegulationData.finserv.is_deemed_finserv` | `boolean` | Facebook treats the ad as a financial-services ad. | | `regionalRegulationData.finserv.is_limited_delivery` | `boolean` | Delivery is limited under financial-services rules. | | `regionalRegulationData.tw_anti_scam` | `object` | Taiwan's anti-scam ad rules. | | `regionalRegulationData.tw_anti_scam.is_limited_delivery` | `boolean` | Delivery is limited under Taiwan's anti-scam rules. | | `reportCount` | `number`, nullable | Times the ad was reported, when disclosed; null in every response we've seen. | | `fevInfo` | `any` | Facebook internal field; null in every response we've seen. | | `stateMediaRunLabel` | `any` | Label for ads run by state-controlled media; null otherwise. | | `aaa_info` | `any` | Extra transparency data on some ads (see `isAAAEligible`); null in every response we've seen. | ## Caching and freshness Responses are cached for up to 6 hours, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `facebook_ad` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's facebook_ad with id "1016451784037642" and summarize what you find. ``` Claude calls `facebook_ad` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "id": "1016451784037642" } ``` Tool results skip nulls and empty lists to save tokens, and `trim` defaults to `true`. --- Previous: [Ad Library ads by company](https://lurkapi.com/docs/facebook/company-ads.md) · Next: [Search Ad Library companies](https://lurkapi.com/docs/facebook/search-companies.md) --- # Search Ad Library companies > Find an advertiser's Facebook page id by name in Meta's Ad Library, with followers and linked Instagram. - **Request:** `GET https://api.lurkapi.com/v1/facebook/adLibrary/search/companies` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `facebook_search_companies` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 1 day - **Try it live:** https://lurkapi.com/docs/facebook/search-companies#try (no signup) - **Platform:** [Facebook](https://lurkapi.com/docs/facebook.md) (facebook.com/ads/library) - **Web page:** https://lurkapi.com/docs/facebook/search-companies ## When to use this Use it to turn a brand name into the `page_id` that company ads (facebook_company_ads) needs. Big brands have several pages (regional, product lines); pick by `name`, `likes` and `verification`. Typeahead search over advertisers in the Ad Library, like the search box on facebook.com/ads/library. Returns up to about 15 matches, no pagination. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `query` | string | yes | | | Brand or page name, e.g. `nike`. | `nike` | | `country` | string | no | `US` | | 2-letter country code that biases which pages rank first, or ALL. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/facebook/adLibrary/search/companies?query=nike" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ query: "nike", }); const res = await fetch(`https://api.lurkapi.com/v1/facebook/adLibrary/search/companies?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.searchResults); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/facebook/adLibrary/search/companies", params={ "query": "nike", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["searchResults"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 996, "credits_charged": 1, "searchResults": [ { "category": "Sportswear Store", "country": null, "entity_type": "PERSON_PROFILE", "ig_followers": 291075731, "ig_username": "nike", "ig_verification": true, "image_uri": "https://scontent.fbts1-1.fna.fbcdn.net/v/t39.30808-1/284964043_101599038…", "likes": 39514042, "name": "Nike", "page_alias": "nike", "page_id": "15087023444", "page_is_deleted": false, "verification": "BLUE_VERIFIED" }, { "category": "Product/service", "country": null, "entity_type": "PERSON_PROFILE", "ig_followers": 44794371, "ig_username": "nikefootball", "ig_verification": true, "image_uri": "https://scontent.fbts1-1.fna.fbcdn.net/v/t39.30808-1/387181570_863325755…", "likes": 40028551, "name": "Nike Football", "page_alias": "nikefootball", "page_id": "51212153078", "page_is_deleted": false, "verification": "BLUE_VERIFIED" } ] } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `searchResults` | `object[]` | Matching advertiser pages, best match first. | | `searchResults[].page_id` | `string`, nullable | The page id. Pass it as `pageId` to the company ads endpoint. | | `searchResults[].name` | `string`, nullable | The page's name. | | `searchResults[].page_alias` | `string`, nullable | Vanity name: facebook.com/. | | `searchResults[].category` | `string`, nullable | Page category, e.g. "Sportswear Store". | | `searchResults[].likes` | `number`, nullable | Page followers. | | `searchResults[].verification` | `string`, nullable | BLUE_VERIFIED or NOT_VERIFIED. | | `searchResults[].image_uri` | `string`, nullable | Profile picture URL. | | `searchResults[].ig_username` | `string`, nullable | Linked Instagram username, without @; null when none is linked. | | `searchResults[].ig_followers` | `number`, nullable | Followers of the linked Instagram account; null when none is linked. | | `searchResults[].ig_verification` | `boolean` | Whether the linked Instagram account is verified; false when none is linked. | | `searchResults[].country` | `string`, nullable | The page's country; null in every response we've seen. | | `searchResults[].entity_type` | `string`, nullable | Facebook's page type; PERSON_PROFILE for most pages, brands included. | | `searchResults[].page_is_deleted` | `boolean` | Whether the page has been deleted. | ## Caching and freshness Responses are cached for up to 1 day, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `facebook_search_companies` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's facebook_search_companies with query "nike" and summarize what you find. ``` Claude calls `facebook_search_companies` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "query": "nike" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Ad Library ad details](https://lurkapi.com/docs/facebook/ad.md) · Next: [Public profile](https://lurkapi.com/docs/facebook/profile.md) --- # Public profile > A Facebook Page's or public person's name, pictures, verification and follower counts. - **Request:** `GET https://api.lurkapi.com/v1/facebook/profile` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `facebook_profile` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 10 minutes - **Try it live:** https://lurkapi.com/docs/facebook/profile#try (no signup) - **Platform:** [Facebook](https://lurkapi.com/docs/facebook.md) (facebook.com/ads/library) - **Web page:** https://lurkapi.com/docs/facebook/profile ## When to use this Pass any Facebook profile URL: a Page or a person, by username, profile.php?id= or /people/ link. `type` says whether it's a Page or a personal profile. Pages and personal profiles in public or professional mode return what logged-out visitors see; fields that don't apply stay null. A private personal profile returns 403 `not_public` and an unknown username 404 `not_found`; both are charged 1 credit. Facebook shows logged-out visitors the same page for a private profile and a deleted or unused profile.php id, so those return `not_public` too. This endpoint returns metadata, not a feed. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `url` | string | yes | | | A Facebook Page or personal profile URL: a username (https://www.facebook.com/NASA), profile.php?id=, /people// or /pages//. Groups, share links and other paths are rejected. | `https://www.facebook.com/NASA` | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/facebook/profile?url=https%3A%2F%2Fwww.facebook.com%2FNASA" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ url: "https://www.facebook.com/NASA", }); const res = await fetch(`https://api.lurkapi.com/v1/facebook/profile?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/facebook/profile", params={ "url": "https://www.facebook.com/NASA", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999999, "credits_charged": 1, "id": "100044561550831", "type": "page", "page_id": "54971236771", "url": "https://www.facebook.com/NASA", "name": "NASA - National Aeronautics and Space Administration", "description": "NASA - National Aeronautics and Space Administration. 28,727,665 followe…", "is_verified": true, "followers_count": 28727665, "followers_text": "28M followers", "following_count": 52, "talking_about_count": 101631, "profile_picture_url": "https://scontent-iad3-1.xx.fbcdn.net/v/t39.30808-1/243095782_41666103649…", "cover_photo_url": "https://scontent-iad3-1.xx.fbcdn.net/v/t39.99422-6/809146614_44840139285…" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `id` | `string` | Facebook's profile id. New-style Pages and professional-mode personal profiles also have a separate page_id. | | `type` | `string`, nullable | `page` for a business Page, `personal` for a person's profile (including professional mode); null if Facebook doesn't say. | | `page_id` | `string`, nullable | The delegate Page id, useful for Ad Library company ads. Pages always have one; personal profiles only in professional mode, otherwise null. | | `url` | `string` | The canonical public Facebook profile URL. | | `name` | `string` | The Page or person's display name. | | `description` | `string`, nullable | The exact public description, which can include the Page name, follower counts and biography. | | `is_verified` | `boolean`, nullable | Whether the public header shows Facebook's verification badge; null if omitted. | | `followers_count` | `number`, nullable | Exact follower count from public page metadata; rounded display counts are not converted into exact counts; null when Facebook does not disclose it to public visitors. | | `followers_text` | `string`, nullable | The follower count label shown in the header, which is often rounded (for example 28M followers). | | `following_count` | `number`, nullable | Exact following count, when the public header shows an unrounded number; null when Facebook does not disclose it to public visitors. | | `talking_about_count` | `number`, nullable | The talking-about count reported in Facebook's public page metadata. Pages and professional-mode profiles only; null for other personal profiles; null when Facebook does not disclose it to public visitors. | | `profile_picture_url` | `string`, nullable | The public profile-picture CDN URL. CDN links expire. | | `cover_photo_url` | `string`, nullable | The public cover-photo CDN URL; null when there is none. CDN links expire. | ## Caching and freshness Responses are cached for up to 10 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 403 | `not_public` | The platform confirmed this account or content isn't available to logged-out visitors. Use a publicly visible target. Charged when the platform was checked. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `facebook_profile` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's facebook_profile with url "https://www.facebook.com/NASA" and summarize what you find. ``` Claude calls `facebook_profile` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "url": "https://www.facebook.com/NASA" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Search Ad Library companies](https://lurkapi.com/docs/facebook/search-companies.md) · Next: [Public post](https://lurkapi.com/docs/facebook/post.md) --- # Public post > A public post's text, author, engagement counts and attached media. - **Request:** `GET https://api.lurkapi.com/v1/facebook/post` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `facebook_post` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 5 minutes - **Try it live:** https://lurkapi.com/docs/facebook/post#try (no signup) - **Platform:** [Facebook](https://lurkapi.com/docs/facebook.md) (facebook.com/ads/library) - **Web page:** https://lurkapi.com/docs/facebook/post ## When to use this Pass a public Facebook post permalink or video URL. Public counts can be hidden or delayed; missing fields stay null. The response includes media exposed to logged-out visitors, but does not fetch comments or follow share links. CDN media URLs expire. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `url` | string | yes | | | A public Facebook post or video URL. Supports posts, permalink.php, story.php, videos and watch URLs; share links and groups are unavailable. | `https://www.facebook.com/attn/posts/pfbid0j1Czf2gGDVqeQ8KiMLFm3pWN8GxsQmeRrVhimWDzMuKQoR8r4b1knNsejELmUgyhl` | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/facebook/post?url=https%3A%2F%2Fwww.facebook.com%2Fattn%2Fposts%2Fpfbid0j1Czf2gGDVqeQ8KiMLFm3pWN8GxsQmeRrVhimWDzMuKQoR8r4b1knNsejELmUgyhl" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ url: "https://www.facebook.com/attn/posts/pfbid0j1Czf2gGDVqeQ8KiMLFm3pWN8GxsQmeRrVhimWDzMuKQoR8r4b1knNsejELmUgyhl", }); const res = await fetch(`https://api.lurkapi.com/v1/facebook/post?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.reactions); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/facebook/post", params={ "url": "https://www.facebook.com/attn/posts/pfbid0j1Czf2gGDVqeQ8KiMLFm3pWN8GxsQmeRrVhimWDzMuKQoR8r4b1knNsejELmUgyhl", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["reactions"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999998, "credits_charged": 1, "post_id": "740190678139306", "url": "https://www.facebook.com/attn/videos/6968553779868435/", "text": "Learning new problem-solving skills is hard for students…and parents. Ne…", "created_at": 1701975646, "author": { "id": "100064451419378", "name": "ATTN:", "url": "https://www.facebook.com/attn" }, "reaction_count": 3207, "comment_count": 399, "share_count": 730, "view_count": 603570, "reactions": [ { "name": "Like", "count": 1999 }, { "name": "Haha", "count": 855 }, { "name": "Love", "count": 312 }, { "name": "Wow", "count": 16 }, { "name": "Angry", "count": 15 }, { "name": "Care", "count": 6 }, { "name": "Sad", "count": 4 } ], "media": [ { "id": "6968553779868435", "type": "video", "url": "https://www.facebook.com/attn/videos/6968553779868435/", "image_url": "https://scontent-iad6-1.xx.fbcdn.net/v/t15.5256-10/386476950_83614653830…", "width": 1080, "height": 1080, "sd_url": "https://video-iad3-2.xx.fbcdn.net/o1/v/t2/f2/m412/AQOwPfru6RYxPhcMZ2m2r_…", "hd_url": "https://video-iad3-2.xx.fbcdn.net/o1/v/t2/f2/m266/AQPuJagXj7pCjEWxEfAYdX…", "duration_s": 132.675 } ] } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `post_id` | `string` | Numeric post id, separate from Facebook's opaque GraphQL story id. | | `url` | `string` | The post's canonical Facebook permalink. | | `text` | `string`, nullable | Public post text, preserving line breaks; null for a media-only post. | | `created_at` | `number`, nullable | Post publication time as Unix seconds; null when Facebook does not disclose it to public visitors. | | `author` | `object` | The author of this post, rather than an author mentioned in its text or comments. | | `author.id` | `string` | Author profile id. | | `author.name` | `string` | Author display name. | | `author.url` | `string`, nullable | Author's public Facebook URL. | | `reaction_count` | `number`, nullable | Total reactions to the post; null when Facebook does not disclose it to public visitors. | | `comment_count` | `number`, nullable | Total comments Facebook reports for the public comment list; null when Facebook does not disclose it to public visitors. | | `share_count` | `number`, nullable | Share count; null when Facebook does not disclose it to public visitors. | | `view_count` | `number`, nullable | Video view count; null when Facebook does not disclose it to public visitors. | | `reactions` | `object[]` | The reaction breakdown Facebook includes; an empty list means no breakdown was exposed. | | `reactions[].name` | `string`, nullable | Facebook's localized reaction name, such as Like or Love. | | `reactions[].count` | `number`, nullable | Number of reactions of this type; null when Facebook does not disclose it to public visitors. | | `media` | `object[]` | Publicly exposed attached images and videos; delivery URLs can expire. | | `media[].id` | `string`, nullable | Media id. | | `media[].type` | `string` | Media format. | | `media[].url` | `string`, nullable | Facebook media permalink. | | `media[].image_url` | `string`, nullable | Image or video thumbnail CDN URL; CDN links expire. | | `media[].width` | `number`, nullable | Media width in pixels; null when Facebook does not disclose it to public visitors. | | `media[].height` | `number`, nullable | Media height in pixels; null when Facebook does not disclose it to public visitors. | | `media[].sd_url` | `string`, nullable | Standard-definition progressive video CDN URL; null for images. CDN links expire. | | `media[].hd_url` | `string`, nullable | High-definition progressive video CDN URL; null for images. CDN links expire. | | `media[].duration_s` | `number`, nullable | Video duration in seconds; null for images; null when Facebook does not disclose it to public visitors. | ## Caching and freshness Responses are cached for up to 5 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 403 | `not_public` | The platform confirmed this account or content isn't available to logged-out visitors. Use a publicly visible target. Charged when the platform was checked. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `facebook_post` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's facebook_post with url "https://www.facebook.com/attn/posts/pfbid0j1Czf2gGDVqeQ8KiMLFm3pWN8GxsQmeRrVhimWDzMuKQoR8r4b1knNsejELmUgyhl" and summarize what you find. ``` Claude calls `facebook_post` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "url": "https://www.facebook.com/attn/posts/pfbid0j1Czf2gGDVqeQ8KiMLFm3pWN8GxsQmeRrVhimWDzMuKQoR8r4b1knNsejELmUgyhl" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Public profile](https://lurkapi.com/docs/facebook/profile.md) · Next: [Subreddit posts](https://lurkapi.com/docs/reddit/subreddit-posts.md) --- # Reddit API > Posts, comments, search and community stats from any public subreddit. - **Platform:** [Reddit](https://lurkapi.com/docs/reddit.md) (reddit.com) - **Web page:** https://lurkapi.com/docs/reddit ## Overview Read Reddit the way people see it on reddit.com: subreddit feeds, full comment threads, site-wide and in-subreddit search, and community stats. Useful for audience research, brand and competitor monitoring, and finding the questions your customers ask. Field names follow Reddit's own API, so code written for Reddit's JSON keeps working; fields reddit.com's pages don't carry are left out rather than faked. Source: reddit.com. ## Try it live Pick an endpoint and run it: real data, no signup, 5 free requests a day. Live playground: https://lurkapi.com/docs/reddit#try ## Endpoints - [Subreddit posts](https://lurkapi.com/docs/reddit/subreddit-posts.md): A subreddit's feed (hot, new, top, rising or best), with titles, text, scores and comment counts. (GET /v1/reddit/subreddit · 1 credit) - [Subreddit details](https://lurkapi.com/docs/reddit/subreddit-details.md): A community's size, activity, description, rules and images. (GET /v1/reddit/subreddit/details · 1 credit) - [Subreddit search](https://lurkapi.com/docs/reddit/subreddit-search.md): Search one subreddit's posts by keyword, returned as compact result cards. (GET /v1/reddit/subreddit/search · 1 credit) - [Search Reddit](https://lurkapi.com/docs/reddit/search.md): Search all of Reddit for posts matching a keyword. (GET /v1/reddit/search · 1 credit) - [Post comments](https://lurkapi.com/docs/reddit/post-comments.md): A Reddit post and its comment thread, with nested replies. (GET /v1/reddit/post/comments · 1 credit) ## Call it Every endpoint is a `GET` with your key in the `x-api-key` header. For example, [Subreddit posts](https://lurkapi.com/docs/reddit/subreddit-posts.md): curl: ```bash curl "https://api.lurkapi.com/v1/reddit/subreddit?subreddit=marketing" \ -H "x-api-key: YOUR_API_KEY" ``` [Get a free API key](https://lurkapi.com/login?next=/dashboard) ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), each endpoint is a tool Claude can call: | Tool | What it does | | --- | --- | | `reddit_subreddit_posts` | [Subreddit posts](https://lurkapi.com/docs/reddit/subreddit-posts.md): A subreddit's feed (hot, new, top, rising or best), with titles, text, scores and comment counts. | | `reddit_subreddit_details` | [Subreddit details](https://lurkapi.com/docs/reddit/subreddit-details.md): A community's size, activity, description, rules and images. | | `reddit_subreddit_search` | [Subreddit search](https://lurkapi.com/docs/reddit/subreddit-search.md): Search one subreddit's posts by keyword, returned as compact result cards. | | `reddit_search` | [Search Reddit](https://lurkapi.com/docs/reddit/search.md): Search all of Reddit for posts matching a keyword. | | `reddit_post_comments` | [Post comments](https://lurkapi.com/docs/reddit/post-comments.md): A Reddit post and its comment thread, with nested replies. | --- # Subreddit posts > A subreddit's feed (hot, new, top, rising or best), with titles, text, scores and comment counts. - **Request:** `GET https://api.lurkapi.com/v1/reddit/subreddit` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `reddit_subreddit_posts` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 1 minute - **Try it live:** https://lurkapi.com/docs/reddit/subreddit-posts#try (no signup; free tool: [Reddit scraper](https://lurkapi.com/free-tools/reddit-scraper)) - **Platform:** [Reddit](https://lurkapi.com/docs/reddit.md) (reddit.com) - **Web page:** https://lurkapi.com/docs/reddit/subreddit-posts ## When to use this Use it to track what a community talks about. About 25 posts per page; pass `after` for more. `timeframe` only applies with `sort=top`. Missing, private and banned subreddits return an empty `posts` list. One page of a subreddit's feed as sorted on reddit.com, with titles, text, scores and comment counts. **Pagination.** Pass the response's `after` back as `after` for the next page; `after` is null at the end. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `subreddit` | string | yes | | | Subreddit name without r/, e.g. `marketing`. | `marketing` | | `sort` | string | no | `hot` | `best`, `hot`, `new`, `top`, `rising` | Feed order, as on reddit.com. | | | `timeframe` | string | no | `all` | `all`, `hour`, `day`, `week`, `month`, `year` | Time window for `sort=top`: hour, day, week, month, year or all. | | | `after` | string | no | | | The `after` from the previous response, to get the next page. | | | `trim` | boolean | no | `false` | `true`, `false` | `true` omits `selftext_html` for a smaller response. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/reddit/subreddit?subreddit=marketing" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ subreddit: "marketing", }); const res = await fetch(`https://api.lurkapi.com/v1/reddit/subreddit?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.posts); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/reddit/subreddit", params={ "subreddit": "marketing", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["posts"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 995, "credits_charged": 1, "posts": [ { "id": "1wtsnuh", "name": "t3_1wtsnuh", "title": "Networking group recs?", "author": "Party4Chai", "author_fullname": "t2_49qg9o4t", "subreddit": "marketing", "subreddit_id": "t5_2qhmg", "subreddit_name_prefixed": "r/marketing", "permalink": "/r/marketing/comments/1wtsnuh/networking_group_recs/", "url": "https://www.reddit.com/r/marketing/comments/1wtsnuh/networking_group_recs/", "domain": "self.marketing", "score": 3, "ups": 3, "upvote_ratio": 0.8, "num_comments": 10, "created": 1790733391, "created_utc": 1790733391, "is_self": true, "is_video": false, "over_18": false, "spoiler": false, "stickied": false, "locked": false, "link_flair_text": "Question", "link_flair_background_color": "#94E044", "selftext": "I was in Monday Girl and unfortunately they just don't offer enough in t…", "selftext_html": "

I was in Monday Girl and unfortunately they just don't offer enou…", "thumbnail": null, "total_awards_received": 0 }, { "id": "1wtfnyr", "name": "t3_1wtfnyr", "title": "How to make my booth more interesting?", "author": "Fantastic_Shoe_3189", "author_fullname": "t2_dvo38ecf", "subreddit": "marketing", "subreddit_id": "t5_2qhmg", "subreddit_name_prefixed": "r/marketing", "permalink": "/r/marketing/comments/1wtfnyr/how_to_make_my_booth_more_interesting/", "url": "https://www.reddit.com/r/marketing/comments/1wtfnyr/how_to_make_my_booth_more_interesting/", "domain": "self.marketing", "score": 10, "ups": 10, "upvote_ratio": 0.92, "num_comments": 42, "created": 1790701262, "created_utc": 1790701262, "is_self": true, "is_video": false, "over_18": false, "spoiler": false, "stickied": false, "locked": false, "link_flair_text": "Question", "link_flair_background_color": "#94E044", "selftext": "Hey all,\n\nRecently my team has been attending a conference circuit in wh…", "selftext_html": "

Hey all,

Recently my team has been attending a conference circu…", "thumbnail": null, "total_awards_received": 0 } ], "after": "t3_1wnhso4" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `posts` | `object[]` | One page of posts, in feed order. | | `posts[].id` | `string` | Post id, e.g. `1wtfnyr`. | | `posts[].name` | `string` | Fullname, `t3_` + id. Subreddit listings paginate with it. | | `posts[].title` | `string` | Post title. | | `posts[].author` | `string`, nullable | Username, without u/; null if unknown. | | `posts[].author_fullname` | `string`, nullable | Author's account fullname, `t2_` + id; null if unknown. | | `posts[].subreddit` | `string` | Subreddit name without r/, e.g. `marketing`. | | `posts[].subreddit_id` | `string`, nullable | Subreddit fullname, `t5_` + id. | | `posts[].subreddit_name_prefixed` | `string`, nullable | e.g. `r/marketing`. | | `posts[].permalink` | `string` | Path on reddit.com; prepend https://www.reddit.com. | | `posts[].url` | `string` | The link a link post points to, or the post itself for text posts. | | `posts[].url_overridden_by_dest` | `string`, optional | Outbound link, on link posts only. | | `posts[].domain` | `string`, nullable | `self.` for text posts, else the linked site. | | `posts[].selftext` | `string` | Post body as plain text. Empty for link posts. | | `posts[].selftext_html` | `string`, nullable, optional | Post body as HTML. Omitted with trim=true. | | `posts[].score` | `number` | Upvotes minus downvotes. | | `posts[].ups` | `number` | Same as score. | | `posts[].upvote_ratio` | `number`, nullable | Share of votes that are upvotes, 0–1. | | `posts[].num_comments` | `number` | Comment count when fetched. | | `posts[].created` | `number` | Posted at, Unix seconds. | | `posts[].created_utc` | `number` | Posted at, Unix seconds. | | `posts[].is_self` | `boolean` | A text post rather than a link. | | `posts[].is_video` | `boolean` | A video post. | | `posts[].is_gallery` | `boolean`, optional | Present (true) on image galleries; absent otherwise. | | `posts[].over_18` | `boolean` | Marked NSFW. | | `posts[].spoiler` | `boolean` | Marked as a spoiler. | | `posts[].stickied` | `boolean` | Pinned by moderators. | | `posts[].locked` | `boolean` | Locked: no new comments allowed. | | `posts[].link_flair_text` | `string`, nullable | Post flair, e.g. "Question"; null without flair. | | `posts[].link_flair_background_color` | `string`, nullable | Flair color as a hex code, e.g. #94E044; null without flair. | | `posts[].thumbnail` | `string`, nullable | Preview image URL; null for posts without media. | | `posts[].total_awards_received` | `number` | Awards the post received. | | `after` | `string`, nullable | Pass as `after` for the next page. null on the last page. | ## Pagination Pass the response's `after` back as `after`, with the other parameters unchanged, for the next page. It's `null` on the last page. Each page costs 1 credit. The first five pages, in JavaScript: ```js let after = null; for (let page = 1; page <= 5; page++) { const params = new URLSearchParams({ subreddit: "marketing" }); if (after) params.set("after", after); const res = await fetch(`https://api.lurkapi.com/v1/reddit/subreddit?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.posts); after = data.after; if (!after) break; // last page } ``` ## Caching and freshness Responses are cached for up to 1 minute, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `reddit_subreddit_posts` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's reddit_subreddit_posts with subreddit "marketing" and summarize what you find. ``` Claude calls `reddit_subreddit_posts` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "subreddit": "marketing" } ``` Tool results skip nulls and empty lists to save tokens, and `trim` defaults to `true`. --- Previous: [Public post](https://lurkapi.com/docs/facebook/post.md) · Next: [Subreddit details](https://lurkapi.com/docs/reddit/subreddit-details.md) --- # Subreddit details > A community's size, activity, description, rules and images. - **Request:** `GET https://api.lurkapi.com/v1/reddit/subreddit/details` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `reddit_subreddit_details` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 1 hour - **Try it live:** https://lurkapi.com/docs/reddit/subreddit-details#try (no signup) - **Platform:** [Reddit](https://lurkapi.com/docs/reddit.md) (reddit.com) - **Web page:** https://lurkapi.com/docs/reddit/subreddit-details ## When to use this Use it to size up a community before posting: weekly visitors and contributions, rules and description. There's no subscriber count (reddit.com no longer shows it); `weekly_active_users` is the closest measure. Stats and settings for one subreddit: the numbers reddit.com shows on the community page, numbered rules, icon, banner and creation date. Pass `subreddit` or a Reddit `url`. Unknown or private subreddits return 404 `not_found`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `subreddit` | string | no | | | Subreddit name (e.g. `marketing`) or URL. Required unless you pass `url`. | `marketing` | | `url` | string | no | | | Any Reddit URL containing /r/, e.g. `https://www.reddit.com/r/marketing/`. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/reddit/subreddit/details?subreddit=marketing" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ subreddit: "marketing", }); const res = await fetch(`https://api.lurkapi.com/v1/reddit/subreddit/details?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/reddit/subreddit/details", params={ "subreddit": "marketing", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 994, "credits_charged": 1, "subreddit_id": "t5_2qhmg", "display_name": "marketing", "weekly_active_users": 70309, "weekly_contributions": 965, "rules": "1. Advertising, Self-Promotion, & Spam (Permanent Ban)\nIf you want to ad…", "description": "For marketing communications + advertising industry professionals to dis…", "header_img": "https://styles.redditmedia.com/t5_2qhmg/styles/bannerBackgroundImage_y1x…", "icon_img": "https://styles.redditmedia.com/t5_2qhmg/styles/communityIcon_amdb6rj8w5p…", "created_at": "2008-03-23T18:17:57.000Z" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `subreddit_id` | `string` | Fullname, `t5_…`. | | `display_name` | `string` | Name with Reddit's capitalization. | | `description` | `string` | The public one-paragraph description. | | `rules` | `string` | Community rules as numbered plain text: "1. Title\nDetails\n\n2. …". | | `weekly_active_users` | `number`, nullable | Weekly visitors, as shown on the community page. | | `weekly_contributions` | `number`, nullable | Posts plus comments in the last week. | | `icon_img` | `string`, nullable | Community icon URL; null if none. | | `header_img` | `string`, nullable | Banner image URL; null if none. | | `created_at` | `string` | When the subreddit was created, ISO 8601. | ## Caching and freshness Responses are cached for up to 1 hour, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `reddit_subreddit_details` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's reddit_subreddit_details with subreddit "marketing" and summarize what you find. ``` Claude calls `reddit_subreddit_details` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "subreddit": "marketing" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Subreddit posts](https://lurkapi.com/docs/reddit/subreddit-posts.md) · Next: [Subreddit search](https://lurkapi.com/docs/reddit/subreddit-search.md) --- # Subreddit search > Search one subreddit's posts by keyword, returned as compact result cards. - **Request:** `GET https://api.lurkapi.com/v1/reddit/subreddit/search` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `reddit_subreddit_search` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 1 minute - **Try it live:** https://lurkapi.com/docs/reddit/subreddit-search#try (no signup) - **Platform:** [Reddit](https://lurkapi.com/docs/reddit.md) (reddit.com) - **Web page:** https://lurkapi.com/docs/reddit/subreddit-search ## When to use this Use it to find what one community says about a product, competitor or problem. Results are compact cards without the body; pass a result's `url` to post comments (reddit_post_comments) for the text and replies. Keyword search inside one subreddit, like the search box on a community page. Results carry `votes`, `num_comments`, `created_at` and subreddit info. **Pagination.** Pass the response's `cursor` back as `cursor`; it's null at the end. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `subreddit` | string | yes | | | Subreddit name without r/, e.g. `marketing`. | `marketing` | | `query` | string | no | | | Keywords. Leave empty to list the subreddit's posts in `sort` order. | `AI video` | | `sort` | string | no | `relevance` | `relevance`, `hot`, `top`, `new`, `comments` | Result order. `comments` = most commented. | | | `timeframe` | string | no | `all` | `all`, `hour`, `day`, `week`, `month`, `year` | Only posts from the last hour, day, week, month, year, or all time. | | | `cursor` | string | no | | | The `cursor` from the previous response, to get the next page. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/reddit/subreddit/search?subreddit=marketing&query=AI+video" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ subreddit: "marketing", query: "AI video", }); const res = await fetch(`https://api.lurkapi.com/v1/reddit/subreddit/search?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.posts); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/reddit/subreddit/search", params={ "subreddit": "marketing", "query": "AI video", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["posts"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 993, "credits_charged": 1, "posts": [ { "id": "t3_1t673is", "post_id": "t3_1t673is", "title": "Have any of you made a documentary style video using an AI tool?", "url": "https://www.reddit.com/r/marketing/comments/1t673is/have_any_of_you_made…", "permalink": "/r/marketing/comments/1t673is/have_any_of_you_made_a_documentary_style_video/", "nsfw": false, "spoiler": false, "is_crosspost": false, "subreddit": { "id": "t5_2qhmg", "name": "marketing", "nsfw": false, "quarantined": false, "icon": null, "banner": null, "description": null, "weekly_visitors": null, "weekly_contributions": null }, "votes": 0, "num_comments": 37, "created_at": "2026-05-07T10:59:35.000+0000", "created_at_iso": "2026-05-07T10:59:35.000Z", "thumbnail": null, "thumbnail_blurred": false, "position": 0, "relative_position": 0 }, { "id": "t3_1woq8xa", "post_id": "t3_1woq8xa", "title": "YouTube to start allowing A/B testing of video content", "url": "https://www.reddit.com/r/marketing/comments/1woq8xa/youtube_to_start_all…", "permalink": "/r/marketing/comments/1woq8xa/youtube_to_start_allowing_ab_testing_of_video/", "nsfw": false, "spoiler": false, "is_crosspost": false, "subreddit": { "id": "t5_2qhmg", "name": "marketing", "nsfw": false, "quarantined": false, "icon": null, "banner": null, "description": null, "weekly_visitors": null, "weekly_contributions": null }, "votes": 32, "num_comments": 9, "created_at": "2026-09-24T02:58:53.000+0000", "created_at_iso": "2026-09-24T02:58:53.000Z", "thumbnail": null, "thumbnail_blurred": false, "position": 1, "relative_position": 1 } ], "cursor": "eyJjYW5kaWRhdGVzX3JldHVybmVkIjoie1wic2VjdGlvbl8xX3BpcGVsaW5lXzBfZ2xvYmFs…" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `posts` | `object[]` | Matching posts, best match first (or in `sort` order). | | `posts[].id` | `string` | Fullname, e.g. `t3_1t673is`. | | `posts[].post_id` | `string` | Same as id. | | `posts[].title` | `string` | Post title. | | `posts[].url` | `string` | The post on reddit.com. Pass it to post comments for the body and replies. | | `posts[].permalink` | `string` | Path on reddit.com. | | `posts[].votes` | `number` | Score (upvotes minus downvotes). | | `posts[].num_comments` | `number` | Comment count when fetched. | | `posts[].created_at` | `string` | Posted at, e.g. `2026-05-07T10:59:35.000+0000`. | | `posts[].created_at_iso` | `string` | Posted at, ISO 8601. | | `posts[].nsfw` | `boolean` | Marked NSFW. | | `posts[].spoiler` | `boolean` | Marked as a spoiler. | | `posts[].is_crosspost` | `boolean` | Always false: search results don't say whether a post is a crosspost. | | `posts[].thumbnail` | `string`, nullable | Preview image URL; null for posts without media. | | `posts[].thumbnail_blurred` | `boolean` | Always false: search results don't include it. | | `posts[].position` | `number` | Rank in the results, from 0. | | `posts[].relative_position` | `number` | Rank as Reddit reports it; usually equal to position. | | `posts[].subreddit` | `object` | The subreddit the post is in. | | `posts[].subreddit.id` | `string`, nullable | Subreddit fullname, `t5_` + id. | | `posts[].subreddit.name` | `string` | Subreddit name without r/. | | `posts[].subreddit.nsfw` | `boolean` | The subreddit is NSFW. | | `posts[].subreddit.quarantined` | `boolean` | The subreddit is quarantined. | | `posts[].subreddit.icon` | `string`, nullable | Subreddit icon URL, when the result shows one. | | `posts[].subreddit.banner` | `string`, nullable | Always null here; get it from subreddit details. | | `posts[].subreddit.description` | `string`, nullable | Always null here; get it from subreddit details. | | `posts[].subreddit.weekly_visitors` | `number`, nullable | Always null here; get it from subreddit details. | | `posts[].subreddit.weekly_contributions` | `number`, nullable | Always null here; get it from subreddit details. | | `cursor` | `string`, nullable | Pass as `cursor` for the next page. null on the last page. | ## Pagination Pass the response's `cursor` back as `cursor`, with the other parameters unchanged, for the next page. It's `null` on the last page. Each page costs 1 credit. The first five pages, in JavaScript: ```js let cursor = null; for (let page = 1; page <= 5; page++) { const params = new URLSearchParams({ subreddit: "marketing", query: "AI video" }); if (cursor) params.set("cursor", cursor); const res = await fetch(`https://api.lurkapi.com/v1/reddit/subreddit/search?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.posts); cursor = data.cursor; if (!cursor) break; // last page } ``` ## Caching and freshness Responses are cached for up to 1 minute, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `reddit_subreddit_search` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's reddit_subreddit_search with subreddit "marketing", query "AI video" and summarize what you find. ``` Claude calls `reddit_subreddit_search` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "subreddit": "marketing", "query": "AI video" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Subreddit details](https://lurkapi.com/docs/reddit/subreddit-details.md) · Next: [Search Reddit](https://lurkapi.com/docs/reddit/search.md) --- # Search Reddit > Search all of Reddit for posts matching a keyword. - **Request:** `GET https://api.lurkapi.com/v1/reddit/search` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `reddit_search` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 1 minute - **Try it live:** https://lurkapi.com/docs/reddit/search#try (no signup) - **Platform:** [Reddit](https://lurkapi.com/docs/reddit.md) (reddit.com) - **Web page:** https://lurkapi.com/docs/reddit/search ## When to use this Use it for brand monitoring and finding where a topic is discussed. Results have no body text or link; pass https://www.reddit.com + `permalink` as `url` to post comments (reddit_post_comments) for the full post. Posts only, no comment search. Site-wide post search, like reddit.com/search: title, author, subreddit, score, comment count and date. **Pagination.** Pass the response's `after` back as `after`; it's null at the end. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `query` | string | yes | | | Keywords, e.g. `claude mcp`. | `claude mcp` | | `filter` | string | no | `posts` | `posts` | What to search. Only `posts` for now; comment search isn't supported yet. | | | `sort` | string | no | `relevance` | `relevance`, `new`, `top`, `comment_count` | Result order. | | | `timeframe` | string | no | `all` | `all`, `hour`, `day`, `week`, `month`, `year` | Only posts from the last hour, day, week, month, year, or all time. | | | `after` | string | no | | | The `after` from the previous response, to get the next page. | | | `trim` | boolean | no | `false` | `true`, `false` | Has no effect here: search results have no HTML body to drop. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/reddit/search?query=claude+mcp" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ query: "claude mcp", }); const res = await fetch(`https://api.lurkapi.com/v1/reddit/search?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.posts); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/reddit/search", params={ "query": "claude mcp", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["posts"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 992, "credits_charged": 1, "posts": [ { "id": "1v9n747", "name": "t3_1v9n747", "title": "Are people actually using MCP? For what?", "author": "ksyp21", "author_fullname": "t2_2rjo8r0b", "subreddit": "mcp", "subreddit_id": "t5_2s5cc", "subreddit_name_prefixed": "r/mcp", "quarantine": false, "permalink": "/r/mcp/comments/1v9n747/are_people_actually_using_mcp_for_what/", "score": 93, "ups": 93, "num_comments": 275, "created": 1785304145, "created_utc": 1785304145, "created_at_iso": "2026-07-29T05:49:05.000Z", "over_18": false, "spoiler": false, "thumbnail": null }, { "id": "1w2grux", "name": "t3_1w2grux", "title": "What’s a good useful MCP you connected to that brings you real value?", "author": "Dense-Map-406", "author_fullname": "t2_zbqg0zcxt", "subreddit": "ClaudeAI", "subreddit_id": "t5_7t8hvt", "subreddit_name_prefixed": "r/ClaudeAI", "quarantine": false, "permalink": "/r/ClaudeAI/comments/1w2grux/whats_a_good_useful_mcp_you_connected_to_that/", "score": 412, "ups": 412, "num_comments": 249, "created": 1788094320, "created_utc": 1788094320, "created_at_iso": "2026-08-30T12:52:00.000Z", "over_18": false, "spoiler": false, "thumbnail": null } ], "after": "eyJjYW5kaWRhdGVzX3JldHVybmVkIjoie1wic2VjdGlvbl8xX3BpcGVsaW5lXzBfZ2xvYmFs…" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `posts` | `object[]` | Matching posts, in `sort` order. | | `posts[].id` | `string` | Post id. Pass the post's URL to post comments for the full post. | | `posts[].name` | `string` | Fullname, `t3_` + id. | | `posts[].title` | `string` | Post title. | | `posts[].author` | `string`, nullable | Username, without u/; null if unknown. | | `posts[].author_fullname` | `string`, nullable | Author's account fullname, `t2_` + id. | | `posts[].subreddit` | `string` | Subreddit name without r/. | | `posts[].subreddit_id` | `string`, nullable | Subreddit fullname, `t5_` + id. | | `posts[].subreddit_name_prefixed` | `string` | e.g. `r/ClaudeAI`. | | `posts[].quarantine` | `boolean` | The subreddit is quarantined. | | `posts[].permalink` | `string`, nullable | Path on reddit.com; prepend https://www.reddit.com. | | `posts[].score` | `number` | Upvotes minus downvotes. | | `posts[].ups` | `number` | Same as score. | | `posts[].num_comments` | `number` | Comment count when fetched. | | `posts[].created` | `number` | Posted at, Unix seconds. | | `posts[].created_utc` | `number` | Posted at, Unix seconds. | | `posts[].created_at_iso` | `string` | Posted at, ISO 8601. | | `posts[].over_18` | `boolean` | Marked NSFW. | | `posts[].spoiler` | `boolean` | Marked as a spoiler. | | `posts[].thumbnail` | `string`, nullable | Preview image URL; null for posts without media. | | `after` | `string`, nullable | Pass as `after` for the next page. null on the last page. | ## Pagination Pass the response's `after` back as `after`, with the other parameters unchanged, for the next page. It's `null` on the last page. Each page costs 1 credit. The first five pages, in JavaScript: ```js let after = null; for (let page = 1; page <= 5; page++) { const params = new URLSearchParams({ query: "claude mcp" }); if (after) params.set("after", after); const res = await fetch(`https://api.lurkapi.com/v1/reddit/search?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.posts); after = data.after; if (!after) break; // last page } ``` ## Caching and freshness Responses are cached for up to 1 minute, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `reddit_search` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's reddit_search with query "claude mcp" and summarize what you find. ``` Claude calls `reddit_search` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "query": "claude mcp" } ``` Tool results skip nulls and empty lists to save tokens, and `trim` defaults to `true`. --- Previous: [Subreddit search](https://lurkapi.com/docs/reddit/subreddit-search.md) · Next: [Post comments](https://lurkapi.com/docs/reddit/post-comments.md) --- # Post comments > A Reddit post and its comment thread, with nested replies. - **Request:** `GET https://api.lurkapi.com/v1/reddit/post/comments` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `reddit_post_comments` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 1 minute - **Try it live:** https://lurkapi.com/docs/reddit/post-comments#try (no signup) - **Platform:** [Reddit](https://lurkapi.com/docs/reddit.md) (reddit.com) - **Web page:** https://lurkapi.com/docs/reddit/post-comments ## When to use this Use it to read a thread: the post plus comments, best first, with replies nested under `replies.items`. Where `more.has_more` is true, pass `more.next_cursor` as `cursor` with the same `url` to load more. Good for what people really think: objections, feature requests, the words customers use. **Loading more.** Reddit loads long threads in batches. Wherever `more.has_more` is true (at the top level, or inside any comment's `replies`), pass that `more.next_cursor` as `cursor` with the same `url` to load the batch. Cursor responses have `post: null`. Unknown or removed posts return 404 `not_found`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `url` | string | yes | | | The post's URL, e.g. `https://www.reddit.com/r/marketing/comments/1wsx466/`. A /r/… path works too. | `https://www.reddit.com/r/marketing/comments/1wsx466/want_to_become_an_llm_expert_in_marketing_where/` | | `cursor` | string | no | | | A `more.next_cursor` from a previous response, to load that batch of comments. | | | `trim` | boolean | no | `false` | `true`, `false` | `true` omits the post's `selftext_html`. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/reddit/post/comments?url=https%3A%2F%2Fwww.reddit.com%2Fr%2Fmarketing%2Fcomments%2F1wsx466%2Fwant_to_become_an_llm_expert_in_marketing_where%2F" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ url: "https://www.reddit.com/r/marketing/comments/1wsx466/want_to_become_an_llm_expert_in_marketing_where/", }); const res = await fetch(`https://api.lurkapi.com/v1/reddit/post/comments?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.comments); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/reddit/post/comments", params={ "url": "https://www.reddit.com/r/marketing/comments/1wsx466/want_to_become_an_llm_expert_in_marketing_where/", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["comments"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 991, "credits_charged": 1, "post": { "id": "1wsx466", "name": "t3_1wsx466", "title": "Want to become an LLM expert in marketing , where do I start?", "author": "Due-Doughnut1818", "author_fullname": "t2_1vwbrgv4me", "subreddit": "marketing", "subreddit_id": "t5_2qhmg", "subreddit_name_prefixed": "r/marketing", "permalink": "/r/marketing/comments/1wsx466/want_to_become_an_llm_expert_in_marketing_where/", "url": "https://www.reddit.com/r/marketing/comments/1wsx466/want_to_become_an_ll…", "domain": "self.marketing", "score": 0, "ups": 0, "upvote_ratio": 0.27, "num_comments": 17, "created": 1790645795, "created_utc": 1790645795, "is_self": true, "is_video": false, "over_18": false, "spoiler": false, "stickied": false, "locked": false, "link_flair_text": "Question", "link_flair_background_color": "#94E044", "selftext": "I want to be LLM expert but in marketing. so i working in marketing soci…", "selftext_html": "

I want to be LLM expert but in marketing. so i working in marketing s…", "thumbnail": null, "total_awards_received": 0 }, "comments": [ { "id": "pcrhmoe", "name": "t1_pcrhmoe", "author": "thesupermikey", "author_fullname": "t2_4ipwa", "body": "Too late. We’ve all moved on to hand written postcards and sound powered landlines.", "body_html": "

Too late. We’ve all moved on to hand written postcards and sound powered landlines.

", "score": 14, "ups": 14, "created": 1790678974, "created_utc": 1790678974, "created_at_iso": "2026-09-29T10:49:34.000Z", "depth": 0, "parent_id": "t3_1wsx466", "link_id": "t3_1wsx466", "permalink": "/r/marketing/comments/1wsx466/want_to_become_an_llm_expert_in_marketing_where/pcrhmoe/", "url": "https://www.reddit.com/r/marketing/comments/1wsx466/want_to_become_an_ll…", "subreddit": "marketing", "subreddit_id": "t5_2qhmg", "subreddit_name_prefixed": "r/marketing", "collapsed": false, "is_submitter": false, "replies": { "items": [], "more": { "has_more": false, "next_cursor": null } } }, { "id": "pcqrrwb", "name": "t1_pcqrrwb", "author": "[deleted]", "author_fullname": null, "body": "", "body_html": null, "score": 1, "ups": 1, "created": 1790666431, "created_utc": 1790666431, "created_at_iso": "2026-09-29T07:20:31.000Z", "depth": 0, "parent_id": "t3_1wsx466", "link_id": "t3_1wsx466", "permalink": "/r/marketing/comments/1wsx466/want_to_become_an_llm_expert_in_marketing_where/pcqrrwb/", "url": "https://www.reddit.com/r/marketing/comments/1wsx466/want_to_become_an_ll…", "subreddit": "marketing", "subreddit_id": "t5_2qhmg", "subreddit_name_prefixed": "r/marketing", "collapsed": true, "is_submitter": false, "replies": { "items": [ { "id": "pcqrryc", "name": "t1_pcqrryc", "author": "AutoModerator", "author_fullname": "t2_6l4z3", "body": "Your account must be 30+ days old and it must have 300+ karma to post in…", "body_html": "

Your account must be 30+ days old and it must have 300+ karma to post…", "score": 1, "ups": 1, "created": 1790666432, "created_utc": 1790666432, "created_at_iso": "2026-09-29T07:20:32.000Z", "depth": 1, "parent_id": "t1_pcqrrwb", "link_id": "t3_1wsx466", "permalink": "/r/marketing/comments/1wsx466/want_to_become_an_llm_expert_in_marketing_where/pcqrryc/", "url": "https://www.reddit.com/r/marketing/comments/1wsx466/want_to_become_an_ll…", "subreddit": "marketing", "subreddit_id": "t5_2qhmg", "subreddit_name_prefixed": "r/marketing", "collapsed": false, "is_submitter": false, "replies": { "items": [], "more": { "has_more": false, "next_cursor": null } } } ], "more": { "has_more": false, "next_cursor": null } } } ], "more": { "has_more": false, "next_cursor": null } } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `post` | `object`, nullable | The post. null on cursor responses. | | `post.id` | `string` | Post id, e.g. `1wtfnyr`. | | `post.name` | `string` | Fullname, `t3_` + id. Subreddit listings paginate with it. | | `post.title` | `string` | Post title. | | `post.author` | `string`, nullable | Username, without u/; null if unknown. | | `post.author_fullname` | `string`, nullable | Author's account fullname, `t2_` + id; null if unknown. | | `post.subreddit` | `string` | Subreddit name without r/, e.g. `marketing`. | | `post.subreddit_id` | `string`, nullable | Subreddit fullname, `t5_` + id. | | `post.subreddit_name_prefixed` | `string`, nullable | e.g. `r/marketing`. | | `post.permalink` | `string` | Path on reddit.com; prepend https://www.reddit.com. | | `post.url` | `string` | The link a link post points to, or the post itself for text posts. | | `post.url_overridden_by_dest` | `string`, optional | Outbound link, on link posts only. | | `post.domain` | `string`, nullable | `self.` for text posts, else the linked site. | | `post.selftext` | `string` | Post body as plain text. Empty for link posts. | | `post.selftext_html` | `string`, nullable, optional | Post body as HTML. Omitted with trim=true. | | `post.score` | `number` | Upvotes minus downvotes. | | `post.ups` | `number` | Same as score. | | `post.upvote_ratio` | `number`, nullable | Share of votes that are upvotes, 0–1. | | `post.num_comments` | `number` | Comment count when fetched. | | `post.created` | `number` | Posted at, Unix seconds. | | `post.created_utc` | `number` | Posted at, Unix seconds. | | `post.is_self` | `boolean` | A text post rather than a link. | | `post.is_video` | `boolean` | A video post. | | `post.is_gallery` | `boolean`, optional | Present (true) on image galleries; absent otherwise. | | `post.over_18` | `boolean` | Marked NSFW. | | `post.spoiler` | `boolean` | Marked as a spoiler. | | `post.stickied` | `boolean` | Pinned by moderators. | | `post.locked` | `boolean` | Locked: no new comments allowed. | | `post.link_flair_text` | `string`, nullable | Post flair, e.g. "Question"; null without flair. | | `post.link_flair_background_color` | `string`, nullable | Flair color as a hex code, e.g. #94E044; null without flair. | | `post.thumbnail` | `string`, nullable | Preview image URL; null for posts without media. | | `post.total_awards_received` | `number` | Awards the post received. | | `comments` | `object[]` | Top-level comments, each with nested `replies`. | | `comments[].id` | `string` | Comment id, e.g. `pcrhmoe`. | | `comments[].name` | `string` | Fullname, `t1_` + id. | | `comments[].author` | `string`, nullable | Username, without u/; null if unknown. | | `comments[].author_fullname` | `string`, nullable | Author's account fullname, `t2_` + id. | | `comments[].body` | `string` | Comment text, plain. | | `comments[].body_html` | `string`, nullable | Comment text as HTML; null when the comment has no body. | | `comments[].score` | `number` | Upvotes minus downvotes. | | `comments[].ups` | `number` | Same as score. | | `comments[].created` | `number` | Posted at, Unix seconds. | | `comments[].created_utc` | `number` | Posted at, Unix seconds. | | `comments[].created_at_iso` | `string` | Posted at, ISO 8601. | | `comments[].depth` | `number` | 0 for top-level comments, 1 for their replies, and so on. | | `comments[].parent_id` | `string`, nullable | `t3_…` for top-level comments, else the parent comment's `t1_…`. | | `comments[].link_id` | `string`, nullable | The post's fullname. | | `comments[].permalink` | `string` | Path to this comment on reddit.com. | | `comments[].url` | `string` | This comment on reddit.com. | | `comments[].subreddit` | `string` | Subreddit name without r/. | | `comments[].subreddit_id` | `string`, nullable | Subreddit fullname, `t5_` + id. | | `comments[].subreddit_name_prefixed` | `string` | e.g. `r/marketing`. | | `comments[].collapsed` | `boolean` | Collapsed by default on Reddit (low score or moderated). | | `comments[].is_submitter` | `boolean` | Written by the post's author. | | `comments[].replies` | `object` | Replies to this comment. | | `comments[].replies.items` | `RedditComment[]` | Direct replies, each with its own replies. | | `comments[].replies.more` | `object` | More comments that weren't loaded yet. | | `comments[].replies.more.has_more` | `boolean` | Whether there are more comments to load here. | | `comments[].replies.more.next_cursor` | `string`, nullable | Pass as `cursor` (with the same url) to load them; null when has_more is false. | | `more` | `object` | More comments that weren't loaded yet. | | `more.has_more` | `boolean` | Whether there are more comments to load here. | | `more.next_cursor` | `string`, nullable | Pass as `cursor` (with the same url) to load them; null when has_more is false. | ## Pagination Pass a cursor from a previous response as `cursor` to load more (see [When to use this](#when-to-use)). Each call costs 1 credit. ## Caching and freshness Responses are cached for up to 1 minute, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `reddit_post_comments` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's reddit_post_comments with url "https://www.reddit.com/r/marketing/comments/1wsx466/want_to_become_an_llm_expert_in_marketing_where/" and summarize what you find. ``` Claude calls `reddit_post_comments` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "url": "https://www.reddit.com/r/marketing/comments/1wsx466/want_to_become_an_llm_expert_in_marketing_where/" } ``` Tool results skip nulls and empty lists to save tokens, and `trim` defaults to `true`. --- Previous: [Search Reddit](https://lurkapi.com/docs/reddit/search.md) · Next: [Video details](https://lurkapi.com/docs/youtube/video.md) --- # YouTube API > Videos, Shorts, transcripts, comments and channel stats from any public YouTube channel. - **Platform:** [YouTube](https://lurkapi.com/docs/youtube.md) (youtube.com) - **Web page:** https://lurkapi.com/docs/youtube ## Overview Read YouTube the way a signed-out viewer sees it: video details with exact view and like counts, transcripts, comment threads, channel stats and uploads, and search. Useful for creator research, content planning, competitor tracking and social listening. Counts YouTube only shows rounded (subscribers) are returned both as a number and as the text YouTube shows. Source: youtube.com. ## Try it live Pick an endpoint and run it: real data, no signup, 5 free requests a day. Live playground: https://lurkapi.com/docs/youtube#try ## Endpoints - [Video details](https://lurkapi.com/docs/youtube/video.md): A video's or Short's title, description, exact views and likes, publish date, length, tags, chapters and channel. (GET /v1/youtube/video · 1 credit) - [Video transcript](https://lurkapi.com/docs/youtube/transcript.md): What's said in a video or Short: its captions as timed segments and as one block of text. (GET /v1/youtube/video/transcript · 1 credit) - [Video comments](https://lurkapi.com/docs/youtube/comments.md): A video's comments, top or newest first, with likes, reply counts and who wrote them. (GET /v1/youtube/video/comments · 1 credit) - [Comment replies](https://lurkapi.com/docs/youtube/comment-replies.md): The replies to one YouTube comment. (GET /v1/youtube/video/comment/replies · 1 credit) - [Channel details](https://lurkapi.com/docs/youtube/channel.md): A channel's subscribers, total views, video count, join date, country, links and description. (GET /v1/youtube/channel · 1 credit) - [Channel videos](https://lurkapi.com/docs/youtube/channel-videos.md): A channel's videos, newest or most popular first, with views, length and upload time. (GET /v1/youtube/channel-videos · 1 credit) - [Channel Shorts](https://lurkapi.com/docs/youtube/channel-shorts.md): A channel's Shorts, newest or most popular first, with titles and view counts. (GET /v1/youtube/channel/shorts · 1 credit) - [Search YouTube](https://lurkapi.com/docs/youtube/search.md): Search YouTube by keyword for videos, Shorts, channels and playlists, with date, length and type filters. (GET /v1/youtube/search · 1 credit) ## Call it Every endpoint is a `GET` with your key in the `x-api-key` header. For example, [Video details](https://lurkapi.com/docs/youtube/video.md): curl: ```bash curl "https://api.lurkapi.com/v1/youtube/video?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DdQw4w9WgXcQ" \ -H "x-api-key: YOUR_API_KEY" ``` [Get a free API key](https://lurkapi.com/login?next=/dashboard) ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), each endpoint is a tool Claude can call: | Tool | What it does | | --- | --- | | `youtube_video` | [Video details](https://lurkapi.com/docs/youtube/video.md): A video's or Short's title, description, exact views and likes, publish date, length, tags, chapters and channel. | | `youtube_transcript` | [Video transcript](https://lurkapi.com/docs/youtube/transcript.md): What's said in a video or Short: its captions as timed segments and as one block of text. | | `youtube_comments` | [Video comments](https://lurkapi.com/docs/youtube/comments.md): A video's comments, top or newest first, with likes, reply counts and who wrote them. | | `youtube_comment_replies` | [Comment replies](https://lurkapi.com/docs/youtube/comment-replies.md): The replies to one YouTube comment. | | `youtube_channel` | [Channel details](https://lurkapi.com/docs/youtube/channel.md): A channel's subscribers, total views, video count, join date, country, links and description. | | `youtube_channel_videos` | [Channel videos](https://lurkapi.com/docs/youtube/channel-videos.md): A channel's videos, newest or most popular first, with views, length and upload time. | | `youtube_channel_shorts` | [Channel Shorts](https://lurkapi.com/docs/youtube/channel-shorts.md): A channel's Shorts, newest or most popular first, with titles and view counts. | | `youtube_search` | [Search YouTube](https://lurkapi.com/docs/youtube/search.md): Search YouTube by keyword for videos, Shorts, channels and playlists, with date, length and type filters. | --- # Video details > A video's or Short's title, description, exact views and likes, publish date, length, tags, chapters and channel. - **Request:** `GET https://api.lurkapi.com/v1/youtube/video` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `youtube_video` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 1 hour - **Try it live:** https://lurkapi.com/docs/youtube/video#try (no signup) - **Platform:** [YouTube](https://lurkapi.com/docs/youtube.md) (youtube.com) - **Web page:** https://lurkapi.com/docs/youtube/video ## When to use this Use it to track how a video performs or to pull its metadata: exact view and like counts, the full description and its links, tags, category, chapters, the "Most replayed" graph and the videos YouTube recommends next. Works for Shorts too (`type: "short"`). The comment count is rounded the way YouTube shows it; video comments returns the exact total. For what's said in the video, use video transcript. Once in a while YouTube's player refuses us: then `type`, `durationMs`, `durationFormatted`, `keywords`, `genre`, `publishDate`, `uploadDate`, `isFamilySafe` and `isUnlisted` are null and everything else is still filled in. Private, removed and unknown videos return 404 `not_found`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `url` | string | yes | | | The video's URL (watch, youtu.be, /shorts/, /live/ or /embed/ links all work) or its 11-character id, e.g. `https://www.youtube.com/watch?v=dQw4w9WgXcQ`. | `https://www.youtube.com/watch?v=dQw4w9WgXcQ` | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/youtube/video?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DdQw4w9WgXcQ" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", }); const res = await fetch(`https://api.lurkapi.com/v1/youtube/video?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.descriptionLinks); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/youtube/video", params={ "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["descriptionLinks"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999986, "credits_charged": 1, "id": "dQw4w9WgXcQ", "type": "video", "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)", "description": "The official video for “Never Gonna Give You Up” by Rick Astley. \n\nNever…", "descriptionLinks": [ { "text": "https://linktr.ee/rickastleynever", "url": "https://linktr.ee/rickastleynever" }, { "text": "https://RickAstley.lnk.to/YTSubID", "url": "https://RickAstley.lnk.to/YTSubID" }, { "text": "https://RickAstley.lnk.to/FBFollowID", "url": "https://RickAstley.lnk.to/FBFollowID" }, { "text": "https://RickAstley.lnk.to/TwitterID", "url": "https://RickAstley.lnk.to/TwitterID" }, { "text": "https://RickAstley.lnk.to/InstagramID", "url": "https://RickAstley.lnk.to/InstagramID" }, { "text": "https://RickAstley.lnk.to/storeID", "url": "https://RickAstley.lnk.to/storeID" }, { "text": "https://RickAstley.lnk.to/TikTokID", "url": "https://RickAstley.lnk.to/TikTokID" }, { "text": "https://RickAstley.lnk.to/SpotifyID", "url": "https://RickAstley.lnk.to/SpotifyID" }, { "text": "https://RickAstley.lnk.to/AppleMusicID", "url": "https://RickAstley.lnk.to/AppleMusicID" }, { "text": "https://RickAstley.lnk.to/AmazonMusicID", "url": "https://RickAstley.lnk.to/AmazonMusicID" }, { "text": "https://RickAstley.lnk.to/DeezerID", "url": "https://RickAstley.lnk.to/DeezerID" } ], "thumbnail": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg", "viewCountText": "1,821,634,661 views", "viewCountInt": 1821634661, "likeCountText": "19M", "likeCountInt": 19439173, "commentCountText": "2.4M", "commentCountInt": 2400000, "publishDate": "2009-10-24T23:57:33-07:00", "uploadDate": "2009-10-24T23:57:33-07:00", "publishDateText": "Oct 24, 2009", "channel": { "id": "UCuAXFkgsw1L7xaCfnd5JJOw", "url": "https://www.youtube.com/@RickAstleyYT", "handle": "RickAstleyYT", "title": "Rick Astley", "subscriberCountText": "4.55M subscribers", "subscriberCount": 4550000, "isVerified": true, "avatar": "https://yt3.ggpht.com/_ShEGOdg-t5YGJse14Ooq6FYBZqX_QSlhaTNjG06miYusm5lWm…" }, "chapters": [], "keywords": [ "rick astley", "Never Gonna Give You Up", "nggyu", "never gonna give you up lyrics", "rick rolled", "Rick Roll", "rick astley official", "rickrolled", "Fortnite song", "Fortnite event", "Fortnite dance", "fortnite never gonna give you up", "rick roll", "rickrolling", "rick rolling", "never gonna give you up", "80s music", "rick astley new", "animated video", "rickroll", "meme songs", "never gonna give u up lyrics", "Rick Astley 2022", "never gonna let you down", "animated", "rick rolls 2022", "never gonna give you up karaoke" ], "genre": "Music", "durationMs": 213000, "durationFormatted": "00:03:33", "isFamilySafe": true, "isUnlisted": false, "most_replayed": { "markers": [ { "startMillis": 0, "durationMillis": 2140, "intensityScoreNormalized": 1 }, { "startMillis": 2140, "durationMillis": 2140, "intensityScoreNormalized": 0.4217676376522385 } ], "ranges": [ { "visibleTimeRangeStartMillis": 0, "visibleTimeRangeEndMillis": 6420, "decorationTimeMillis": 2140, "label": "Most replayed" } ] }, "watchNextVideos": [ { "id": "NWItxGY5NNk", "title": "80'S MUSIC ON THE ROAD | 2 Hours of Classic '80s Hits", "thumbnail": "https://i.ytimg.com/vi/NWItxGY5NNk/hqdefault.jpg?sqp=-oaymwEcCNACELwBSFX…", "channel": { "id": "UCkUSPjRL8md2hlE8bNXAT-A", "url": "https://www.youtube.com/@everysongisascar", "handle": "everysongisascar", "title": "every song is a scar" }, "publishedTimeText": "3 months ago", "viewCountText": "3.9M", "viewCountInt": 3900000, "lengthText": "2:16:49", "lengthInSeconds": 8209, "videoUrl": "https://www.youtube.com/watch?v=NWItxGY5NNk" }, { "id": "yPYZpwSpKmA", "title": "Rick Astley - Together Forever (Official Video) [4K Remaster]", "thumbnail": "https://i.ytimg.com/vi/yPYZpwSpKmA/hqdefault.jpg?sqp=-oaymwEcCNACELwBSFX…", "channel": { "id": "UCuAXFkgsw1L7xaCfnd5JJOw", "url": "https://www.youtube.com/channel/UCuAXFkgsw1L7xaCfnd5JJOw", "handle": null, "title": "Rick Astley" }, "publishedTimeText": "16 years ago", "viewCountText": "206M", "viewCountInt": 206000000, "lengthText": "3:24", "lengthInSeconds": 204, "videoUrl": "https://www.youtube.com/watch?v=yPYZpwSpKmA" } ] } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `id` | `string` | Video id, e.g. `dQw4w9WgXcQ`. | | `type` | `string`, nullable | `short` for YouTube Shorts, else `video`. null in the rare case the player details were unavailable (see `durationMs`). | | `url` | `string` | Canonical link: /shorts/ for Shorts, /watch?v= otherwise. | | `title` | `string` | Video title. | | `description` | `string` | Full description, plain text. | | `descriptionLinks` | `object[]` | Outbound links in the description, in order. | | `descriptionLinks[].text` | `string` | The link as it appears in the description. | | `descriptionLinks[].url` | `string` | Where it goes (YouTube's redirect wrapper removed). | | `thumbnail` | `string` | Largest thumbnail URL. | | `viewCountText` | `string`, nullable | Views as YouTube shows them, e.g. "1,821,613,271 views". | | `viewCountInt` | `number`, nullable | Exact view count. | | `likeCountText` | `string`, nullable | Likes as YouTube shows them, e.g. "19M"; null when the creator hides likes. | | `likeCountInt` | `number`, nullable | Exact like count. null when the creator hides likes. | | `commentCountText` | `string`, nullable | Comments as YouTube shows them, e.g. "2.4M"; null when comments are off. | | `commentCountInt` | `number`, nullable | Comment count from that text, so rounded above 1,000 (2.4M → 2400000). The comments endpoint returns the exact total. | | `publishDate` | `string`, nullable | Published at, ISO 8601 with YouTube's offset, e.g. `2009-10-24T23:57:33-07:00`. null if unavailable. | | `uploadDate` | `string`, nullable | Uploaded at, ISO 8601 (differs from publishDate for scheduled videos and premieres). null if unavailable. | | `publishDateText` | `string`, nullable | The date line under the video, e.g. "Oct 24, 2009" or "Premiered 3 hours ago". | | `channel` | `object` | The channel that posted it. | | `channel.id` | `string`, nullable | Channel id, `UC…`. | | `channel.url` | `string`, nullable | Channel page, e.g. `https://www.youtube.com/@RickAstleyYT`. | | `channel.handle` | `string`, nullable | Handle without the @, e.g. `RickAstleyYT`; null if YouTube shows none. | | `channel.title` | `string`, nullable | Channel name. | | `channel.subscriberCountText` | `string`, nullable | Subscribers as YouTube shows them, e.g. "4.55M subscribers". | | `channel.subscriberCount` | `number`, nullable | Subscribers as a number, from that rounded text (4.55M → 4550000). | | `channel.isVerified` | `boolean` | Has a verified or official artist badge. | | `channel.avatar` | `string`, nullable | Channel avatar URL. | | `chapters` | `object[]` | Chapters from the description (or YouTube's automatic ones), in order. Empty if the video has none. | | `chapters[].title` | `string` | Chapter title. | | `chapters[].startMs` | `number` | Where the chapter starts, ms from the start of the video. | | `chapters[].thumbnail` | `string`, nullable | Chapter thumbnail URL. | | `keywords` | `string[]`, nullable | The creator's tags. null if unavailable. | | `genre` | `string`, nullable | YouTube category, e.g. "Music" or "Education". null if unavailable. | | `durationMs` | `number`, nullable | Length in ms. null if unavailable: this and the other fields marked "if unavailable" come from YouTube's player, which occasionally refuses us; the rest of the response is still complete. | | `durationFormatted` | `string`, nullable | Length as `HH:MM:SS`, e.g. "00:03:33". null if unavailable. | | `isFamilySafe` | `boolean`, nullable | YouTube considers it family safe. null if unavailable. | | `isUnlisted` | `boolean`, nullable | Unlisted (only people with the link can find it). null if unavailable. | | `most_replayed` | `object`, nullable | The "Most replayed" graph. null when YouTube doesn't show one (newer or less-watched videos). | | `most_replayed.markers` | `object[]` | The replay graph over the timeline, usually 100 equal slices. | | `most_replayed.markers[].startMillis` | `number` | Start of this slice of the video, ms. | | `most_replayed.markers[].durationMillis` | `number` | Length of the slice, ms. | | `most_replayed.markers[].intensityScoreNormalized` | `number` | How often this slice is replayed, 0–1 (1 = the most replayed slice). | | `most_replayed.ranges` | `object[]` | Ranges YouTube highlights as most replayed. | | `most_replayed.ranges[].visibleTimeRangeStartMillis` | `number` | Start of the highlighted range, ms. | | `most_replayed.ranges[].visibleTimeRangeEndMillis` | `number` | End of the highlighted range, ms. | | `most_replayed.ranges[].decorationTimeMillis` | `number` | The peak YouTube points at, ms. | | `most_replayed.ranges[].label` | `string`, nullable | YouTube's label, e.g. "Most replayed". | | `watchNextVideos` | `object[]` | The "Up next" videos YouTube recommends beside this one (videos only, no mixes or playlists). | | `watchNextVideos[].id` | `string` | Video id. | | `watchNextVideos[].title` | `string`, nullable | Video title. | | `watchNextVideos[].thumbnail` | `string`, nullable | Thumbnail URL. | | `watchNextVideos[].channel` | `object` | The channel that posted it. | | `watchNextVideos[].channel.id` | `string`, nullable | Channel id, `UC…`. | | `watchNextVideos[].channel.url` | `string`, nullable | Channel page, e.g. `https://www.youtube.com/@RickAstleyYT`. | | `watchNextVideos[].channel.handle` | `string`, nullable | Handle without the @, e.g. `RickAstleyYT`; null if YouTube shows none. | | `watchNextVideos[].channel.title` | `string`, nullable | Channel name. | | `watchNextVideos[].publishedTimeText` | `string`, nullable | When it was posted, relative, e.g. "3 months ago". | | `watchNextVideos[].viewCountText` | `string`, nullable | Views as YouTube shows them, e.g. "3.9M". | | `watchNextVideos[].viewCountInt` | `number`, nullable | Views from that rounded text (3.9M → 3900000). | | `watchNextVideos[].lengthText` | `string`, nullable | Length as shown on the thumbnail, e.g. "4:05". | | `watchNextVideos[].lengthInSeconds` | `number`, nullable | Length in seconds. | | `watchNextVideos[].videoUrl` | `string` | Link to the video. | ## Caching and freshness Responses are cached for up to 1 hour, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `youtube_video` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's youtube_video with url "https://www.youtube.com/watch?v=dQw4w9WgXcQ" and summarize what you find. ``` Claude calls `youtube_video` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Post comments](https://lurkapi.com/docs/reddit/post-comments.md) · Next: [Video transcript](https://lurkapi.com/docs/youtube/transcript.md) --- # Video transcript > What's said in a video or Short: its captions as timed segments and as one block of text. - **Request:** `GET https://api.lurkapi.com/v1/youtube/video/transcript` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `youtube_transcript` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 30 days - **Try it live:** https://lurkapi.com/docs/youtube/transcript#try (no signup; free tool: [YouTube transcript](https://lurkapi.com/free-tools/youtube-transcript)) - **Platform:** [YouTube](https://lurkapi.com/docs/youtube.md) (youtube.com) - **Web page:** https://lurkapi.com/docs/youtube/transcript ## When to use this Use it to turn a video into text for summaries, search, research or repurposing. It reads the video's captions: the creator's own or YouTube's auto-generated ones (`isGenerated` tells them apart). Pick a language with `language` (e.g. `es`); without it you get English when the video has it, else the video's main language. `availableLanguages` lists every caption track. YouTube's machine translation isn't available, so a language the video has no captions in returns 404. Videos without captions, and private or removed ones, return 404 `not_found`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `url` | string | yes | | | The video's URL (watch, youtu.be, /shorts/, /live/ or /embed/ links all work) or its 11-character id, e.g. `https://www.youtube.com/watch?v=dQw4w9WgXcQ`. | `https://www.youtube.com/watch?v=dQw4w9WgXcQ` | | `language` | string | no | | | Language code, e.g. `en`, `es` or `pt-BR`. `pt` also matches `pt-BR`. Default: English if the video has it, else its main language. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/youtube/video/transcript?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DdQw4w9WgXcQ" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", }); const res = await fetch(`https://api.lurkapi.com/v1/youtube/video/transcript?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.transcript); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/youtube/video/transcript", params={ "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["transcript"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999983, "credits_charged": 1, "videoId": "dQw4w9WgXcQ", "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "language": "English", "languageCode": "en", "isGenerated": false, "transcript": [ { "text": "♪ We're no strangers to love ♪", "startMs": 18640, "endMs": 21880, "startTimeText": "0:18" }, { "text": "♪ You know the rules and so do I ♪", "startMs": 22640, "endMs": 26960, "startTimeText": "0:22" } ], "transcript_only_text": "♪ We're no strangers to love ♪ ♪ You know the rules and so do I ♪", "availableLanguages": [ { "code": "en", "name": "English", "isGenerated": false }, { "code": "en", "name": "English", "isGenerated": true }, { "code": "de-DE", "name": "German (Germany)", "isGenerated": false }, { "code": "ja", "name": "Japanese", "isGenerated": false }, { "code": "pt-BR", "name": "Portuguese (Brazil)", "isGenerated": false }, { "code": "es-419", "name": "Spanish (Latin America)", "isGenerated": false } ] } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `videoId` | `string` | Video id. | | `url` | `string` | Link to the video. | | `language` | `string` | Language of the transcript, e.g. "English". | | `languageCode` | `string` | Its code as YouTube names the track, e.g. `en` or `pt-BR`. | | `isGenerated` | `boolean` | Auto-generated by YouTube's speech recognition, rather than captions the creator uploaded. | | `transcript` | `object[]` | Timed segments, in order. | | `transcript[].text` | `string` | What's said in this segment. | | `transcript[].startMs` | `number` | Segment start, ms from the start of the video. | | `transcript[].endMs` | `number` | Segment end, ms; never past the next segment's start. | | `transcript[].startTimeText` | `string` | Segment start as a timestamp, e.g. "1:23". | | `transcript_only_text` | `string` | The whole transcript as one string, without timestamps. | | `availableLanguages` | `object[]` | Every caption track the video has. | | `availableLanguages[].code` | `string` | Language code; pass it as `language`. | | `availableLanguages[].name` | `string` | Language name, e.g. "English". | | `availableLanguages[].isGenerated` | `boolean` | Auto-generated rather than uploaded by the creator. | ## Caching and freshness Responses are cached for up to 30 days, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `youtube_transcript` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's youtube_transcript with url "https://www.youtube.com/watch?v=dQw4w9WgXcQ" and summarize what you find. ``` Claude calls `youtube_transcript` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Video details](https://lurkapi.com/docs/youtube/video.md) · Next: [Video comments](https://lurkapi.com/docs/youtube/comments.md) --- # Video comments > A video's comments, top or newest first, with likes, reply counts and who wrote them. - **Request:** `GET https://api.lurkapi.com/v1/youtube/video/comments` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `youtube_comments` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 10 minutes - **Try it live:** https://lurkapi.com/docs/youtube/comments#try (no signup) - **Platform:** [YouTube](https://lurkapi.com/docs/youtube.md) (youtube.com) - **Web page:** https://lurkapi.com/docs/youtube/comments ## When to use this Use it for audience research and sentiment: what viewers say, which comments get the most likes, and what the creator pinned or hearted. About 20 comments per page; the first page also has the exact `commentCount`. **Pagination.** Pass the response's `continuationToken` back as `continuationToken` (with the same `url`) for the next page; it's null at the end. YouTube stops at about 1,000 top comments, or a few thousand newest ones. **Replies.** Comments with replies have a `repliesContinuationToken`; pass it to comment replies. Videos with comments turned off, and unknown videos, return an empty `comments` list. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `url` | string | yes | | | The video's URL (watch, youtu.be, /shorts/, /live/ or /embed/ links all work) or its 11-character id, e.g. `https://www.youtube.com/watch?v=dQw4w9WgXcQ`. | `https://www.youtube.com/watch?v=dQw4w9WgXcQ` | | `order` | string | no | `top` | `top`, `newest` | `top` (YouTube's ranking) or `newest` first. | | | `continuationToken` | string | no | | | The `continuationToken` from the previous response, to get the next page. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/youtube/video/comments?url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DdQw4w9WgXcQ" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ", }); const res = await fetch(`https://api.lurkapi.com/v1/youtube/video/comments?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.comments); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/youtube/video/comments", params={ "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["comments"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999985, "credits_charged": 1, "comments": [ { "id": "Ugzge340dBgB75hWBm54AaABAg", "content": "can confirm: he never gave us up", "publishedTimeText": "1 year ago", "publishedTime": "2025-09-30T14:33:52.979Z", "replyLevel": 0, "author": { "name": "@YouTube", "channelId": "UCBR8-60-B28hp2BmDPdntcQ", "isVerified": true, "isCreator": false, "avatarUrl": "https://yt3.ggpht.com/3s6evpqAiDU9tQR4sC2siJippbH2RWVPnwHgyl4V0th2iuQz0V…", "channelUrl": "https://www.youtube.com/@YouTube" }, "engagement": { "likes": 321000, "replies": 963 }, "repliesContinuationToken": "Eg0SC2RRdzR3OVdnWGNRGAYygwEaUBIaVWd6Z2UzNDBkQmdCNzVoV0JtNTRBYUFCQWciAggA…", "isPinned": true, "isHearted": true }, { "id": "UgyEnXfdC-umwvTt8JF4AaABAg", "content": "Gonna flag this for nudity so I can rick roll the YouTube staff", "publishedTimeText": "6 years ago", "publishedTime": "2020-10-01T14:33:52.979Z", "replyLevel": 0, "author": { "name": "@Oatman69", "channelId": "UCWf34D_s8K_m2vwBISIDWtg", "isVerified": false, "isCreator": false, "avatarUrl": "https://yt3.ggpht.com/ghenbV7T5VMOA3iqp3PThC82exqcu7iVng_iWNx1Ujak72Ti4o…", "channelUrl": "https://www.youtube.com/@Oatman69" }, "engagement": { "likes": 567000, "replies": 707 }, "repliesContinuationToken": "Eg0SC2RRdzR3OVdnWGNRGAYygwEaUBIaVWd5RW5YZmRDLXVtd3ZUdDhKRjRBYUFCQWciAggA…", "isPinned": false, "isHearted": false } ], "continuationToken": "Eg0SC2RRdzR3OVdnWGNRGAYyggMK2AJnZXRfcmFua2VkX3N0cmVhbXMtLUNxY0JDSUFFRlJl…", "commentCount": 2457944 } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `comments` | `object[]` | One page of comments, in the requested order. | | `comments[].id` | `string` | Comment id. Replies are `.`. | | `comments[].content` | `string` | Comment text. | | `comments[].publishedTimeText` | `string` | When it was posted, as YouTube shows it, e.g. "3 days ago" or "6 years ago (edited)". | | `comments[].publishedTime` | `string`, nullable | Approximate post time, ISO 8601, worked out from publishedTimeText: only as precise as that text ("6 years ago" is ±6 months). | | `comments[].replyLevel` | `number` | 0 for a comment, 1 for a reply. | | `comments[].author` | `object` | Who wrote it. | | `comments[].author.name` | `string` | Author's handle, e.g. `@YouTube`. | | `comments[].author.channelId` | `string`, nullable | Author's channel id, `UC…`. | | `comments[].author.isVerified` | `boolean` | The author has a verified badge. | | `comments[].author.isCreator` | `boolean` | The author is the video's creator. | | `comments[].author.avatarUrl` | `string`, nullable | Author's avatar URL. | | `comments[].author.channelUrl` | `string`, nullable | Author's channel page. | | `comments[].engagement` | `object` | Likes and replies. | | `comments[].engagement.likes` | `number` | Likes; YouTube rounds above 1,000 (321K → 321000). | | `comments[].engagement.replies` | `number` | Number of replies. | | `comments[].repliesContinuationToken` | `string`, nullable | Pass as `continuationToken` to comment replies to read the replies. null when there are none (and always on replies). | | `comments[].isPinned` | `boolean` | Pinned by the creator. | | `comments[].isHearted` | `boolean` | Hearted by the creator. | | `continuationToken` | `string`, nullable | Pass as `continuationToken` for the next page. null on the last page. | | `commentCount` | `number`, nullable | Exact number of comments on the video (replies included). First page only; null on later pages. | ## Caching and freshness Responses are cached for up to 10 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `youtube_comments` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's youtube_comments with url "https://www.youtube.com/watch?v=dQw4w9WgXcQ" and summarize what you find. ``` Claude calls `youtube_comments` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Video transcript](https://lurkapi.com/docs/youtube/transcript.md) · Next: [Comment replies](https://lurkapi.com/docs/youtube/comment-replies.md) --- # Comment replies > The replies to one YouTube comment. - **Request:** `GET https://api.lurkapi.com/v1/youtube/video/comment/replies` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `youtube_comment_replies` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 10 minutes - **Try it live:** https://lurkapi.com/docs/youtube/comment-replies#try (no signup) - **Platform:** [YouTube](https://lurkapi.com/docs/youtube.md) (youtube.com) - **Web page:** https://lurkapi.com/docs/youtube/comment-replies ## When to use this Use it to read the conversation under a comment. Pass a comment's `repliesContinuationToken` from video comments as `continuationToken`. About 10 replies on the first page and up to 50 on later ones, oldest first. **Pagination.** Pass the response's `continuationToken` back as `continuationToken` for more replies; it's null at the end. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `continuationToken` | string | yes | | | A comment's `repliesContinuationToken` from video comments, or the `continuationToken` from a previous replies page. | `Eg0SC2RRdzR3OVdnWGNRGAYygwEaUBIaVWd6Z2UzNDBkQmdCNzVoV0JtNTRBYUFCQWciAggAKhhVQ3VBWEZrZ3N3MUw3eGFDZm5kNUpKT3cyC2RRdzR3OVdnWGNRQABICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3pnZTM0MGRCZ0I3NWhXQm01NEFhQUJBZw%3D%3D` | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/youtube/video/comment/replies?continuationToken=Eg0SC2RRdzR3OVdnWGNRGAYygwEaUBIaVWd6Z2UzNDBkQmdCNzVoV0JtNTRBYUFCQWciAggAKhhVQ3VBWEZrZ3N3MUw3eGFDZm5kNUpKT3cyC2RRdzR3OVdnWGNRQABICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3pnZTM0MGRCZ0I3NWhXQm01NEFhQUJBZw%253D%253D" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ continuationToken: "Eg0SC2RRdzR3OVdnWGNRGAYygwEaUBIaVWd6Z2UzNDBkQmdCNzVoV0JtNTRBYUFCQWciAggAKhhVQ3VBWEZrZ3N3MUw3eGFDZm5kNUpKT3cyC2RRdzR3OVdnWGNRQABICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3pnZTM0MGRCZ0I3NWhXQm01NEFhQUJBZw%3D%3D", }); const res = await fetch(`https://api.lurkapi.com/v1/youtube/video/comment/replies?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.comments); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/youtube/video/comment/replies", params={ "continuationToken": "Eg0SC2RRdzR3OVdnWGNRGAYygwEaUBIaVWd6Z2UzNDBkQmdCNzVoV0JtNTRBYUFCQWciAggAKhhVQ3VBWEZrZ3N3MUw3eGFDZm5kNUpKT3cyC2RRdzR3OVdnWGNRQABICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3pnZTM0MGRCZ0I3NWhXQm01NEFhQUJBZw%3D%3D", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["comments"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999983, "credits_charged": 1, "comments": [ { "id": "Ugzge340dBgB75hWBm54AaABAg.AHE8_QAWJx9AHE9eIiztxR", "content": "YOUTUBE AND ONE LIKE WOOHAAAAH", "publishedTimeText": "1 year ago", "publishedTime": "2025-09-30T14:34:02.154Z", "replyLevel": 1, "author": { "name": "@linganguliguliwatcha", "channelId": "UCjFRISlX-LPxiqViJAE3h6Q", "isVerified": false, "isCreator": false, "avatarUrl": "https://yt3.ggpht.com/AbGqKNjK9k5tyOqdV7cdXx-GgnGuuGQ5wj8RN42U5YCDvYHT0v…", "channelUrl": "https://www.youtube.com/@linganguliguliwatcha" }, "engagement": { "likes": 7500, "replies": 5 }, "repliesContinuationToken": null, "isPinned": false, "isHearted": false }, { "id": "Ugzge340dBgB75hWBm54AaABAg.AHE8_QAWJx9AHEAB_-JmDA", "content": "HEY YOUTUBE", "publishedTimeText": "1 year ago", "publishedTime": "2025-09-30T14:34:02.154Z", "replyLevel": 1, "author": { "name": "@_bugrabilgin", "channelId": "UCg9tPtxMOieUEyhSv63uJ4g", "isVerified": false, "isCreator": false, "avatarUrl": "https://yt3.ggpht.com/LvMpN24GYYr8w43sGoMeYZYejDPJz_skehI6jm_XGGhfM5YeRa…", "channelUrl": "https://www.youtube.com/@_bugrabilgin" }, "engagement": { "likes": 3100, "replies": 2 }, "repliesContinuationToken": null, "isPinned": false, "isHearted": false } ], "continuationToken": "Eg0SC2RRdzR3OVdnWGNRGAYy2AEKUWdldF9jb21tZW50X3dpdGhfcmVwbGllc19zdHJlYW0t…" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `comments` | `object[]` | One page of replies (`replyLevel: 1`). | | `comments[].id` | `string` | Comment id. Replies are `.`. | | `comments[].content` | `string` | Comment text. | | `comments[].publishedTimeText` | `string` | When it was posted, as YouTube shows it, e.g. "3 days ago" or "6 years ago (edited)". | | `comments[].publishedTime` | `string`, nullable | Approximate post time, ISO 8601, worked out from publishedTimeText: only as precise as that text ("6 years ago" is ±6 months). | | `comments[].replyLevel` | `number` | 0 for a comment, 1 for a reply. | | `comments[].author` | `object` | Who wrote it. | | `comments[].author.name` | `string` | Author's handle, e.g. `@YouTube`. | | `comments[].author.channelId` | `string`, nullable | Author's channel id, `UC…`. | | `comments[].author.isVerified` | `boolean` | The author has a verified badge. | | `comments[].author.isCreator` | `boolean` | The author is the video's creator. | | `comments[].author.avatarUrl` | `string`, nullable | Author's avatar URL. | | `comments[].author.channelUrl` | `string`, nullable | Author's channel page. | | `comments[].engagement` | `object` | Likes and replies. | | `comments[].engagement.likes` | `number` | Likes; YouTube rounds above 1,000 (321K → 321000). | | `comments[].engagement.replies` | `number` | Number of replies. | | `comments[].repliesContinuationToken` | `string`, nullable | Pass as `continuationToken` to comment replies to read the replies. null when there are none (and always on replies). | | `comments[].isPinned` | `boolean` | Pinned by the creator. | | `comments[].isHearted` | `boolean` | Hearted by the creator. | | `continuationToken` | `string`, nullable | Pass as `continuationToken` for more replies. null on the last page. | ## Caching and freshness Responses are cached for up to 10 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `youtube_comment_replies` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's youtube_comment_replies with continuationToken "Eg0SC2RRdzR3OVdnWGNRGAYygwEaUBIaVWd6Z2UzNDBkQmdCNzVoV0JtNTRBYUFCQWciAggAKhhVQ3VBWEZrZ3N3MUw3eGFDZm5kNUpKT3cyC2RRdzR3OVdnWGNRQABICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3pnZTM0MGRCZ0I3NWhXQm01NEFhQUJBZw%3D%3D" and summarize what you find. ``` Claude calls `youtube_comment_replies` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "continuationToken": "Eg0SC2RRdzR3OVdnWGNRGAYygwEaUBIaVWd6Z2UzNDBkQmdCNzVoV0JtNTRBYUFCQWciAggAKhhVQ3VBWEZrZ3N3MUw3eGFDZm5kNUpKT3cyC2RRdzR3OVdnWGNRQABICoIBAggBQi9jb21tZW50LXJlcGxpZXMtaXRlbS1VZ3pnZTM0MGRCZ0I3NWhXQm01NEFhQUJBZw%3D%3D" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Video comments](https://lurkapi.com/docs/youtube/comments.md) · Next: [Channel details](https://lurkapi.com/docs/youtube/channel.md) --- # Channel details > A channel's subscribers, total views, video count, join date, country, links and description. - **Request:** `GET https://api.lurkapi.com/v1/youtube/channel` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `youtube_channel` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 6 hours - **Try it live:** https://lurkapi.com/docs/youtube/channel#try (no signup; free tool: [YouTube subscriber count](https://lurkapi.com/free-tools/youtube-subscriber-count)) - **Platform:** [YouTube](https://lurkapi.com/docs/youtube.md) (youtube.com) - **Web page:** https://lurkapi.com/docs/youtube/channel ## When to use this Use it to size up a creator: subscribers, lifetime views, number of videos, when they joined, where they're based and the links on their profile. Pass `channelId`, `handle` or a channel `url` (/@handle, /channel/UC…, /c/… and /user/… links all work). YouTube rounds subscriber counts (519M), so `subscriberCount` is rounded too; `viewCount` and `videoCount` are exact from the about panel. If that panel is unavailable, the header supplies rounded `videoCount`, while views, join date, country and links can be missing. `email` is always null: YouTube only shows business emails to signed-in viewers. Unknown and terminated channels return 404 `not_found`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `channelId` | string | no | | | Channel id, e.g. `UCX6OQ3DkcsbYNE6H8uQQuVA`. Pass this or `handle`. | | | `handle` | string | no | | | Channel handle, with or without the @, e.g. `MrBeast`. Pass this or `channelId`. | `MrBeast` | | `url` | string | no | | | Channel URL, e.g. `https://www.youtube.com/@MrBeast`. Instead of `channelId` or `handle`. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/youtube/channel?handle=MrBeast" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ handle: "MrBeast", }); const res = await fetch(`https://api.lurkapi.com/v1/youtube/channel?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.links); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/youtube/channel", params={ "handle": "MrBeast", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["links"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999995, "credits_charged": 1, "channelId": "UCX6OQ3DkcsbYNE6H8uQQuVA", "channel": "https://www.youtube.com/@MrBeast", "handle": "MrBeast", "name": "MrBeast", "description": "SUBSCRIBE FOR A COOKIE!\nNew MrBeast or MrBeast Gaming video every single…", "avatar": { "image": { "sources": [ { "url": "https://yt3.googleusercontent.com/nxYrc_1_2f77DoBadyxMTmv7ZpRZapHR5jbuYe…", "width": 72, "height": 72 }, { "url": "https://yt3.googleusercontent.com/nxYrc_1_2f77DoBadyxMTmv7ZpRZapHR5jbuYe…", "width": 120, "height": 120 }, { "url": "https://yt3.googleusercontent.com/nxYrc_1_2f77DoBadyxMTmv7ZpRZapHR5jbuYe…", "width": 160, "height": 160 }, { "url": "https://yt3.googleusercontent.com/nxYrc_1_2f77DoBadyxMTmv7ZpRZapHR5jbuYe…", "width": 900, "height": 900 } ] } }, "avatarUrl": "https://yt3.googleusercontent.com/nxYrc_1_2f77DoBadyxMTmv7ZpRZapHR5jbuYe…", "banner": "https://yt3.googleusercontent.com/mHMO_eEMp0dPvh0ADwXhPXNYb_GnjSVsLI8biq…", "isVerified": true, "isFamilySafe": true, "subscriberCount": 519000000, "subscriberCountText": "519M subscribers", "videoCount": 1004, "videoCountText": "1,004 videos", "viewCount": 141227305543, "viewCountText": "141,227,305,543 views", "joinedDate": "2012-02-19", "joinedDateText": "Joined Feb 19, 2012", "country": "United States", "email": null, "links": [ "https://www.themostdangerousgames.com/", "https://www.instagram.com/mrbeast/", "https://twitter.com/MrBeast", "https://facebook.com/mrbeast/" ], "twitter": "https://twitter.com/MrBeast", "instagram": "https://www.instagram.com/mrbeast/", "tags": "mrbeast6000, beast, mrbeast, Mr.Beast, mr", "keywords": [ "mrbeast6000", "beast", "mrbeast", "Mr.Beast", "mr" ] } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `channelId` | `string` | Channel id, `UC…`. | | `channel` | `string` | Channel page, e.g. `https://www.youtube.com/@MrBeast` (or /channel/UC… without a handle). | | `handle` | `string`, nullable | Handle without the @, e.g. `MrBeast`; null if the channel has none. | | `name` | `string` | Channel name. | | `description` | `string` | The full channel description. | | `avatar` | `object`, nullable | Channel picture in several sizes (YouTube's own shape). `avatarUrl` is the largest. | | `avatar.image` | `object` | The picture. | | `avatar.image.sources` | `object[]` | The same picture in several sizes, smallest first. | | `avatar.image.sources[].url` | `string` | Image URL. | | `avatar.image.sources[].width` | `number` | Width in pixels. | | `avatar.image.sources[].height` | `number` | Height in pixels. | | `avatarUrl` | `string`, nullable | Largest channel picture URL (900 px). | | `banner` | `string`, nullable | Largest banner image URL; null without a banner. | | `isVerified` | `boolean` | Has YouTube's verified or official artist badge. | | `isFamilySafe` | `boolean` | YouTube marks the channel family safe. | | `subscriberCount` | `number`, nullable | Subscribers, rounded as YouTube shows them (519M → 519000000). null when hidden. | | `subscriberCountText` | `string`, nullable | e.g. "519M subscribers". | | `videoCount` | `number`, nullable | Public videos, Shorts and past streams. Exact from the about panel; rounded as videoCountText if that panel is unavailable. | | `videoCountText` | `string`, nullable | e.g. "1,004 videos". | | `viewCount` | `number`, nullable | Lifetime views across the channel, exact; null if the about panel is unavailable. | | `viewCountText` | `string`, nullable | e.g. "141,227,305,543 views". | | `joinedDate` | `string`, nullable | When the channel was created, `YYYY-MM-DD`. | | `joinedDateText` | `string`, nullable | e.g. "Joined Feb 19, 2012". | | `country` | `string`, nullable | Country the channel lists, e.g. "United States"; null if it lists none. | | `email` | `null` | Always null: YouTube only shows a channel's business email to signed-in viewers. | | `links` | `string[]` | The links on the channel's profile (website, socials, store), in the channel's order. | | `twitter` | `string`, nullable | The X / Twitter link from `links`, if any. | | `instagram` | `string`, nullable | The Instagram link from `links`, if any. | | `tags` | `string`, nullable | The channel's keywords, comma-separated, e.g. "mrbeast6000, beast, mrbeast"; null if none. | | `keywords` | `string[]` | The same keywords as a list. | ## Caching and freshness Responses are cached for up to 6 hours, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `youtube_channel` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's youtube_channel with handle "MrBeast" and summarize what you find. ``` Claude calls `youtube_channel` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "handle": "MrBeast" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Comment replies](https://lurkapi.com/docs/youtube/comment-replies.md) · Next: [Channel videos](https://lurkapi.com/docs/youtube/channel-videos.md) --- # Channel videos > A channel's videos, newest or most popular first, with views, length and upload time. - **Request:** `GET https://api.lurkapi.com/v1/youtube/channel-videos` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `youtube_channel_videos` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 1 hour - **Try it live:** https://lurkapi.com/docs/youtube/channel-videos#try (no signup) - **Platform:** [YouTube](https://lurkapi.com/docs/youtube.md) (youtube.com) - **Web page:** https://lurkapi.com/docs/youtube/channel-videos ## When to use this Use it to see what a creator publishes and what performs: about 30 videos per page from the channel's Videos tab, newest first, or most viewed first with `sort=popular`. Shorts are on their own tab (channel Shorts). Views are rounded as the channel page shows them (82M views). For exact views, likes, comments and the full description, pass a video's `url` to the video endpoint. **Pagination.** Pass the response's `continuationToken` back as `continuationToken` for the next page; it's null on the last page. Unknown channels return 404 `not_found`; a channel without a Videos tab returns an empty list. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `channelId` | string | no | | | Channel id, e.g. `UCX6OQ3DkcsbYNE6H8uQQuVA`. Pass this or `handle`. | | | `handle` | string | no | | | Channel handle, with or without the @, e.g. `MrBeast`. Pass this or `channelId`. | `MrBeast` | | `sort` | string | no | `latest` | `latest`, `popular`, `oldest` | Order: `latest` (newest first), `popular` (most viewed) or `oldest`. | | | `continuationToken` | string | no | | | The `continuationToken` from the previous response, for the next page. The channel can be left out then. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/youtube/channel-videos?handle=MrBeast" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ handle: "MrBeast", }); const res = await fetch(`https://api.lurkapi.com/v1/youtube/channel-videos?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.videos); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/youtube/channel-videos", params={ "handle": "MrBeast", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["videos"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999994, "credits_charged": 1, "videos": [ { "type": "video", "id": "v9QtM6qnG50", "url": "https://www.youtube.com/watch?v=v9QtM6qnG50", "title": "I Built A City To Save Kids From Illegal Labor", "thumbnail": "https://i.ytimg.com/vi/v9QtM6qnG50/hq720_custom_2.jpg?sqp=COi59NUG-oaymw…", "viewCountText": "82M views", "viewCountInt": 82000000, "publishedTimeText": "10 days ago", "publishedTime": "2026-09-20T14:32:03.216Z", "lengthText": "19:03", "lengthSeconds": 1143, "badges": [] }, { "type": "video", "id": "gTKS8SAwUzE", "url": "https://www.youtube.com/watch?v=gTKS8SAwUzE", "title": "I Survived The Most Extreme Places On Earth", "thumbnail": "https://i.ytimg.com/vi/gTKS8SAwUzE/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4q…", "viewCountText": "117M views", "viewCountInt": 117000000, "publishedTimeText": "3 weeks ago", "publishedTime": "2026-09-09T14:32:03.216Z", "lengthText": "23:28", "lengthSeconds": 1408, "badges": [] } ], "continuationToken": "4qmFsgLdCBIYVUNYNk9RM0RrY3NiWU5FNkg4dVFRdVZBGsAIOGdhbkJocWtCbnFoQmpxZUJn…" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `videos` | `object[]` | One page of videos, in `sort` order. | | `videos[].type` | `"video"` | Always `video`. | | `videos[].id` | `string` | Video id, e.g. `v9QtM6qnG50`. | | `videos[].url` | `string` | Watch link. Pass it to the video endpoint for likes, comments, the full description and exact views. | | `videos[].title` | `string` | Video title. | | `videos[].thumbnail` | `string`, nullable | Largest thumbnail URL. | | `videos[].viewCountText` | `string`, nullable | Views as YouTube shows them, e.g. "82M views", or "1,204 watching" while live. | | `videos[].viewCountInt` | `number`, nullable | viewCountText as a number, rounded whenever YouTube abbreviates it (82M views → 82000000). Use video details for exact views. | | `videos[].publishedTimeText` | `string`, nullable | When it went up, as YouTube shows it, e.g. "10 days ago". null for live and upcoming videos. | | `videos[].publishedTime` | `string`, nullable | publishedTimeText as an estimated ISO 8601 time, counted back from when we fetched the page. Only as precise as the text: "1 year ago" means sometime that year. | | `videos[].lengthText` | `string`, nullable | Length as shown, e.g. "19:03". null for live and upcoming videos. | | `videos[].lengthSeconds` | `number`, nullable | Length in seconds. | | `videos[].badges` | `string[]` | Labels YouTube shows on the result, e.g. "4K", "CC", "New" or "LIVE". Often empty. | | `continuationToken` | `string`, nullable | Pass as `continuationToken` for the next page. null on the last page. | ## Caching and freshness Responses are cached for up to 1 hour, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `youtube_channel_videos` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's youtube_channel_videos with handle "MrBeast" and summarize what you find. ``` Claude calls `youtube_channel_videos` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "handle": "MrBeast" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Channel details](https://lurkapi.com/docs/youtube/channel.md) · Next: [Channel Shorts](https://lurkapi.com/docs/youtube/channel-shorts.md) --- # Channel Shorts > A channel's Shorts, newest or most popular first, with titles and view counts. - **Request:** `GET https://api.lurkapi.com/v1/youtube/channel/shorts` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `youtube_channel_shorts` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 1 hour - **Try it live:** https://lurkapi.com/docs/youtube/channel-shorts#try (no signup) - **Platform:** [YouTube](https://lurkapi.com/docs/youtube.md) (youtube.com) - **Web page:** https://lurkapi.com/docs/youtube/channel-shorts ## When to use this Use it to track a creator's short-form output: about 48 Shorts per page from the channel's Shorts tab, newest first, or most viewed first with `sort=popular`. The Shorts tab only shows title, thumbnail and views. For likes, comments, upload date and length, pass a Short's `url` to the video endpoint; we don't fetch those for every Short, so a page stays one fast request. **Pagination.** Pass the response's `continuationToken` back as `continuationToken` for the next page; it's null on the last page. Unknown channels return 404 `not_found`; a channel without Shorts returns an empty list. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `channelId` | string | no | | | Channel id, e.g. `UCX6OQ3DkcsbYNE6H8uQQuVA`. Pass this or `handle`. | | | `handle` | string | no | | | Channel handle, with or without the @, e.g. `MrBeast`. Pass this or `channelId`. | `MrBeast` | | `sort` | string | no | `newest` | `newest`, `popular`, `oldest` | Order: `newest` first, `popular` (most viewed) or `oldest`. | | | `continuationToken` | string | no | | | The `continuationToken` from the previous response, for the next page. The channel can be left out then. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/youtube/channel/shorts?handle=MrBeast" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ handle: "MrBeast", }); const res = await fetch(`https://api.lurkapi.com/v1/youtube/channel/shorts?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.shorts); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/youtube/channel/shorts", params={ "handle": "MrBeast", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["shorts"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999993, "credits_charged": 1, "shorts": [ { "type": "short", "id": "CEJXqm2eiJ0", "url": "https://www.youtube.com/shorts/CEJXqm2eiJ0", "title": "What’s Inside My Briefcase?", "thumbnail": "https://i.ytimg.com/vi/CEJXqm2eiJ0/sardefault.jpg?sqp=-oaymwEgCJUDEOAESF…", "viewCountText": "23M views", "viewCountInt": 23000000 }, { "type": "short", "id": "T_SMf9j50uc", "url": "https://www.youtube.com/shorts/T_SMf9j50uc", "title": "Can We Build an Entire Village?", "thumbnail": "https://i.ytimg.com/vi/T_SMf9j50uc/sardefault.jpg?sqp=-oaymwEgCJUDEOAESF…", "viewCountText": "21M views", "viewCountInt": 21000000 } ], "continuationToken": "4qmFsgK_CxIYVUNYNk9RM0RrY3NiWU5FNkg4dVFRdVZBGqILOGdhekNCcXdDRkt0Q0RLcUNB…" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `shorts` | `object[]` | One page of Shorts, in `sort` order. | | `shorts[].type` | `"short"` | Always `short`. | | `shorts[].id` | `string` | The Short's video id. | | `shorts[].url` | `string` | `https://www.youtube.com/shorts/`. Pass it to the video endpoint for likes, comments, date and length. | | `shorts[].title` | `string` | The Short's title. | | `shorts[].thumbnail` | `string`, nullable | Largest (vertical) thumbnail URL. | | `shorts[].viewCountText` | `string`, nullable | Views as YouTube shows them, e.g. "22M views". | | `shorts[].viewCountInt` | `number`, nullable | viewCountText as a number, rounded as shown (22M views → 22000000). | | `continuationToken` | `string`, nullable | Pass as `continuationToken` for the next page. null on the last page. | ## Caching and freshness Responses are cached for up to 1 hour, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `youtube_channel_shorts` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's youtube_channel_shorts with handle "MrBeast" and summarize what you find. ``` Claude calls `youtube_channel_shorts` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "handle": "MrBeast" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Channel videos](https://lurkapi.com/docs/youtube/channel-videos.md) · Next: [Search YouTube](https://lurkapi.com/docs/youtube/search.md) --- # Search YouTube > Search YouTube by keyword for videos, Shorts, channels and playlists, with date, length and type filters. - **Request:** `GET https://api.lurkapi.com/v1/youtube/search` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `youtube_search` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 15 minutes - **Try it live:** https://lurkapi.com/docs/youtube/search#try (no signup) - **Platform:** [YouTube](https://lurkapi.com/docs/youtube.md) (youtube.com) - **Web page:** https://lurkapi.com/docs/youtube/search ## When to use this Use it for trend, topic and competitor research: what YouTube's search page shows for a keyword, split into `videos`, `shorts`, `channels`, `playlists` and `lives` (streaming now). About 20 results per page. Narrow it with `type`, `uploadDate`, `duration` (videos only) and `sortBy=popular`. Video results carry views as YouTube shows them, a description snippet and the channel. Counts may be rounded; video details returns exact views. Shorts carry title and views only; pass a Short's `url` to the video endpoint for the rest. **Pagination.** Pass the response's `continuationToken` back as `continuationToken` (with the same query) for the next page; it's null on the last page. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `query` | string | yes | | | Keywords, e.g. `running shoes`. YouTube's operators work, e.g. `intitle:"air max"`. | `running shoes` | | `type` | string | no | `all` | `all`, `videos`, `shorts`, `channels`, `playlists` | What to search for. `all` mixes them like youtube.com. | | | `uploadDate` | string | no | `any` | `any`, `today`, `this_week`, `this_month`, `this_year` | Only results uploaded in this window. | | | `duration` | string | no | `any` | `any`, `under_3_min`, `between_3_and_20_min`, `over_20_min` | Video length. Doesn't apply to Shorts. | | | `sortBy` | string | no | `relevance` | `relevance`, `popular` | `relevance` (YouTube's default) or `popular` (most viewed first). | | | `continuationToken` | string | no | | | The `continuationToken` from the previous response, for the next page. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/youtube/search?query=running+shoes" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ query: "running shoes", }); const res = await fetch(`https://api.lurkapi.com/v1/youtube/search?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.videos); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/youtube/search", params={ "query": "running shoes", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["videos"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999992, "credits_charged": 1, "videos": [ { "type": "video", "id": "z5mWgBMOFJk", "url": "https://www.youtube.com/watch?v=z5mWgBMOFJk", "title": "My Top 3 Running Shoes | Marathon Prep Training", "thumbnail": "https://i.ytimg.com/vi/z5mWgBMOFJk/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4q…", "viewCountText": "682,786 views", "viewCountInt": 682786, "publishedTimeText": "6 years ago", "publishedTime": "2020-10-01T14:32:05.335Z", "lengthText": "17:18", "lengthSeconds": 1038, "badges": [], "description": "Subscribe: http://bit.ly/subNickBare Follow Nick Bare: Facebook: http://…", "channel": { "id": "UCbVEx9qG_hLrb6FrWQJz_Tg", "title": "Nick Bare", "handle": "nickbarefitness", "url": "https://www.youtube.com/@nickbarefitness", "thumbnail": "https://yt3.ggpht.com/ytc/AIdro_mVx6fQ8UNrMx2sjhQ5JwJyQTCfSXAoznqRdRSZLo…" } }, { "type": "video", "id": "GDL5GVlpuko", "url": "https://www.youtube.com/watch?v=GDL5GVlpuko", "title": "My Complete Guide To Running Shoes! (Everything You Need To Know)", "thumbnail": "https://i.ytimg.com/vi/GDL5GVlpuko/hq720.jpg?sqp=-oaymwEcCNAFEJQDSFXyq4q…", "viewCountText": "282,242 views", "viewCountInt": 282242, "publishedTimeText": "10 months ago", "publishedTime": "2025-12-04T14:32:05.335Z", "lengthText": "20:25", "lengthSeconds": 1225, "badges": [ "1 product", "4K" ], "description": "If you enjoyed the video, please like, comment and subscribe! Thank you …", "channel": { "id": "UCZPqG0yh_xPm2AyLjffbDvw", "title": "Ben Parkes", "handle": "BenParkes", "url": "https://www.youtube.com/@BenParkes", "thumbnail": "https://yt3.ggpht.com/ytc/AIdro_mDy4ZoQqcj7DiO_-1z6j2Y6Nu_PvFESGAsJagEM5…" } } ], "shorts": [ { "type": "short", "id": "SVyIXZQABS8", "url": "https://www.youtube.com/shorts/SVyIXZQABS8", "title": "Which shoes to get for your Hyrox training? Boldfit Running Shoes", "thumbnail": "https://i.ytimg.com/vi/SVyIXZQABS8/oar2.jpg?sqp=-oaymwEgCJUDEOAESFWQAgHy…", "viewCountText": "44 views", "viewCountInt": 44 }, { "type": "short", "id": "rKuu1bK6qQY", "url": "https://www.youtube.com/shorts/rKuu1bK6qQY", "title": "Best Running Shoes for Long Runs 🏃", "thumbnail": "https://i.ytimg.com/vi/rKuu1bK6qQY/oar2.jpg?sqp=-oaymwEgCJUDEOAESFWQAgHy…", "viewCountText": "7M views", "viewCountInt": 7000000 } ], "channels": [], "playlists": [], "lives": [], "continuationToken": "EugBEg1ydW5uaW5nIHNob2VzGtYBU0JTQ0FRdDZOVzFYWjBKTlQwWkthNElCQzBkRVREVkhW…" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `videos` | `object[]` | Videos, in result order. | | `videos[].type` | `"video"` | Always `video`. | | `videos[].id` | `string` | Video id, e.g. `v9QtM6qnG50`. | | `videos[].url` | `string` | Watch link. Pass it to the video endpoint for likes, comments, the full description and exact views. | | `videos[].title` | `string` | Video title. | | `videos[].thumbnail` | `string`, nullable | Largest thumbnail URL. | | `videos[].viewCountText` | `string`, nullable | Views as YouTube shows them, e.g. "82M views", or "1,204 watching" while live. | | `videos[].viewCountInt` | `number`, nullable | viewCountText as a number, rounded whenever YouTube abbreviates it (82M views → 82000000). Use video details for exact views. | | `videos[].publishedTimeText` | `string`, nullable | When it went up, as YouTube shows it, e.g. "10 days ago". null for live and upcoming videos. | | `videos[].publishedTime` | `string`, nullable | publishedTimeText as an estimated ISO 8601 time, counted back from when we fetched the page. Only as precise as the text: "1 year ago" means sometime that year. | | `videos[].lengthText` | `string`, nullable | Length as shown, e.g. "19:03". null for live and upcoming videos. | | `videos[].lengthSeconds` | `number`, nullable | Length in seconds. | | `videos[].badges` | `string[]` | Labels YouTube shows on the result, e.g. "4K", "CC", "New" or "LIVE". Often empty. | | `videos[].description` | `string`, nullable | The description snippet shown under the result, usually its first line or two. | | `videos[].channel` | `object`, nullable | The channel behind it. | | `videos[].channel.id` | `string`, nullable | Channel id, `UC…`. | | `videos[].channel.title` | `string`, nullable | Channel name. | | `videos[].channel.handle` | `string`, nullable | Handle without the @, e.g. `BenParkes`; null if YouTube shows none. | | `videos[].channel.url` | `string`, nullable | Channel page, e.g. `https://www.youtube.com/@BenParkes`. | | `videos[].channel.thumbnail` | `string`, nullable | Channel picture URL; null when the result doesn't show one. | | `shorts` | `object[]` | Shorts, in result order. | | `shorts[].type` | `"short"` | Always `short`. | | `shorts[].id` | `string` | The Short's video id. | | `shorts[].url` | `string` | `https://www.youtube.com/shorts/`. Pass it to the video endpoint for likes, comments, date and length. | | `shorts[].title` | `string` | The Short's title. | | `shorts[].thumbnail` | `string`, nullable | Largest (vertical) thumbnail URL. | | `shorts[].viewCountText` | `string`, nullable | Views as YouTube shows them, e.g. "22M views". | | `shorts[].viewCountInt` | `number`, nullable | viewCountText as a number, rounded as shown (22M views → 22000000). | | `channels` | `object[]` | Channels, in result order. | | `channels[].type` | `"channel"` | Always `channel`. | | `channels[].id` | `string` | Channel id, `UC…`. Pass it to channel details for the full stats. | | `channels[].url` | `string` | Channel page. | | `channels[].title` | `string` | Channel name. | | `channels[].handle` | `string`, nullable | Handle without the @; null if YouTube shows none. | | `channels[].thumbnail` | `string`, nullable | Channel picture URL. | | `channels[].description` | `string`, nullable | The description snippet shown in the result. | | `channels[].subscriberCountText` | `string`, nullable | Subscribers as YouTube shows them, e.g. "128K subscribers". | | `channels[].subscriberCount` | `number`, nullable | subscriberCountText as a number, rounded as shown. | | `channels[].isVerified` | `boolean` | Has YouTube's verified or official artist badge. | | `playlists` | `object[]` | Playlists and mixes, in result order. | | `playlists[].type` | `"playlist"` | Always `playlist`. | | `playlists[].id` | `string` | Playlist id, e.g. `PLB50X6wE7gSWNQJElg3NX4AH42QoouHOk` (mixes start with `RD`). | | `playlists[].url` | `string` | Playlist page, `https://www.youtube.com/playlist?list=`. | | `playlists[].title` | `string` | Playlist title. | | `playlists[].thumbnail` | `string`, nullable | Cover thumbnail URL (its first video). | | `playlists[].videoCountText` | `string`, nullable | Size as YouTube shows it, e.g. "30 videos". | | `playlists[].videoCount` | `number`, nullable | videoCountText as a number. | | `playlists[].channel` | `object`, nullable | The channel behind it. | | `playlists[].channel.id` | `string`, nullable | Channel id, `UC…`. | | `playlists[].channel.title` | `string`, nullable | Channel name. | | `playlists[].channel.handle` | `string`, nullable | Handle without the @, e.g. `BenParkes`; null if YouTube shows none. | | `playlists[].channel.url` | `string`, nullable | Channel page, e.g. `https://www.youtube.com/@BenParkes`. | | `playlists[].channel.thumbnail` | `string`, nullable | Channel picture URL; null when the result doesn't show one. | | `lives` | `object[]` | Streams that are live right now. | | `lives[].type` | `"video"` | Always `video`. | | `lives[].id` | `string` | Video id, e.g. `v9QtM6qnG50`. | | `lives[].url` | `string` | Watch link. Pass it to the video endpoint for likes, comments, the full description and exact views. | | `lives[].title` | `string` | Video title. | | `lives[].thumbnail` | `string`, nullable | Largest thumbnail URL. | | `lives[].viewCountText` | `string`, nullable | Views as YouTube shows them, e.g. "82M views", or "1,204 watching" while live. | | `lives[].viewCountInt` | `number`, nullable | viewCountText as a number, rounded whenever YouTube abbreviates it (82M views → 82000000). Use video details for exact views. | | `lives[].publishedTimeText` | `string`, nullable | When it went up, as YouTube shows it, e.g. "10 days ago". null for live and upcoming videos. | | `lives[].publishedTime` | `string`, nullable | publishedTimeText as an estimated ISO 8601 time, counted back from when we fetched the page. Only as precise as the text: "1 year ago" means sometime that year. | | `lives[].lengthText` | `string`, nullable | Length as shown, e.g. "19:03". null for live and upcoming videos. | | `lives[].lengthSeconds` | `number`, nullable | Length in seconds. | | `lives[].badges` | `string[]` | Labels YouTube shows on the result, e.g. "4K", "CC", "New" or "LIVE". Often empty. | | `lives[].description` | `string`, nullable | The description snippet shown under the result, usually its first line or two. | | `lives[].channel` | `object`, nullable | The channel behind it. | | `lives[].channel.id` | `string`, nullable | Channel id, `UC…`. | | `lives[].channel.title` | `string`, nullable | Channel name. | | `lives[].channel.handle` | `string`, nullable | Handle without the @, e.g. `BenParkes`; null if YouTube shows none. | | `lives[].channel.url` | `string`, nullable | Channel page, e.g. `https://www.youtube.com/@BenParkes`. | | `lives[].channel.thumbnail` | `string`, nullable | Channel picture URL; null when the result doesn't show one. | | `continuationToken` | `string`, nullable | Pass as `continuationToken` for the next page. null on the last page. | ## Caching and freshness Responses are cached for up to 15 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `youtube_search` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's youtube_search with query "running shoes" and summarize what you find. ``` Claude calls `youtube_search` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "query": "running shoes" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Channel Shorts](https://lurkapi.com/docs/youtube/channel-shorts.md) · Next: [Profile](https://lurkapi.com/docs/tiktok/profile.md) --- # TikTok API > Profiles, videos, transcripts and comments from any public TikTok account. - **Platform:** [TikTok](https://lurkapi.com/docs/tiktok.md) (tiktok.com) - **Web page:** https://lurkapi.com/docs/tiktok ## Overview Read public TikTok the way tiktok.com shows it: creator profiles with exact follower counts, their latest videos, full video details, caption transcripts, and comment threads. Useful for creator research, UGC and competitor monitoring, and turning videos into text. Field names follow TikTok's own web JSON; fields tiktok.com doesn't carry are left out rather than faked. Source: tiktok.com. ## Try it live Pick an endpoint and run it: real data, no signup, 5 free requests a day. Live playground: https://lurkapi.com/docs/tiktok#try ## Endpoints - [Profile](https://lurkapi.com/docs/tiktok/profile.md): A creator's bio, avatar, verification and exact follower, like and video counts. (GET /v1/tiktok/profile · 1 credit) - [Video details](https://lurkapi.com/docs/tiktok/video.md): One video or photo post: caption, stats, author, sound, hashtags and a playable MP4 URL. (GET /v2/tiktok/video · 1 credit) - [Video transcript](https://lurkapi.com/docs/tiktok/transcript.md): What's said in a video, from TikTok's captions: WEBVTT with timestamps plus plain text. (GET /v1/tiktok/video/transcript · 1 credit; 5 if speech-to-text runs) - [Profile videos](https://lurkapi.com/docs/tiktok/profile-videos.md): A creator's videos and photo posts, newest first, up to 15 per page. (GET /v3/tiktok/profile/videos · 1 credit) - [Video comments](https://lurkapi.com/docs/tiktok/comments.md): A video's top-level comments, most relevant first, with like and reply counts. (GET /v1/tiktok/video/comments · 1 credit) - [Comment replies](https://lurkapi.com/docs/tiktok/comment-replies.md): The replies under one comment, oldest first. (GET /v1/tiktok/video/comment/replies · 1 credit) - [Hashtag videos](https://lurkapi.com/docs/tiktok/hashtag-videos.md): Videos posted with a hashtag, plus how big the hashtag is: total posts and views. (GET /v1/tiktok/search/hashtag · 1 credit) - [Song](https://lurkapi.com/docs/tiktok/song.md): A TikTok sound: title, artist, album, length, cover, audio URL and how many videos use it. (GET /v1/tiktok/song · 1 credit) - [Song videos](https://lurkapi.com/docs/tiktok/song-videos.md): Videos that use a sound, 30 per page, with stats and creators. (GET /v1/tiktok/song/videos · 1 credit) - [Search TikTok Ad Library](https://lurkapi.com/docs/tiktok/ad-library-search.md): Ads shown in the EU, the UK, Switzerland and Turkey that match a keyword or an advertiser, 12 per page. (GET /v1/tiktok/ad-library/search · 1 credit) - [TikTok Ad Library ad](https://lurkapi.com/docs/tiktok/ad-library-ad.md): One ad from TikTok's EU Ad Library: creative, landing page, advertiser, who paid, targeting and reach by country, age and gender. (GET /v1/tiktok/ad-library/ad · 1 credit) - [Live status](https://lurkapi.com/docs/tiktok/live.md): Whether a public creator is live, with the current room title, viewers and start time. (GET /v1/tiktok/user/live · 1 credit) - [Public stories](https://lurkapi.com/docs/tiktok/stories.md): A creator's active public stories, with captions, media and expiration times. (GET /v1/tiktok/user/stories · 1 credit) - [TikTok Shop product](https://lurkapi.com/docs/tiktok/product.md): A US TikTok Shop product's price, images, seller, variants, sales and review totals. (GET /v1/tiktok/product · 1 credit) ## Call it Every endpoint is a `GET` with your key in the `x-api-key` header. For example, [Profile](https://lurkapi.com/docs/tiktok/profile.md): curl: ```bash curl "https://api.lurkapi.com/v1/tiktok/profile?handle=nike" \ -H "x-api-key: YOUR_API_KEY" ``` [Get a free API key](https://lurkapi.com/login?next=/dashboard) ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), each endpoint is a tool Claude can call: | Tool | What it does | | --- | --- | | `tiktok_profile` | [Profile](https://lurkapi.com/docs/tiktok/profile.md): A creator's bio, avatar, verification and exact follower, like and video counts. | | `tiktok_video` | [Video details](https://lurkapi.com/docs/tiktok/video.md): One video or photo post: caption, stats, author, sound, hashtags and a playable MP4 URL. | | `tiktok_transcript` | [Video transcript](https://lurkapi.com/docs/tiktok/transcript.md): What's said in a video, from TikTok's captions: WEBVTT with timestamps plus plain text. | | `tiktok_profile_videos` | [Profile videos](https://lurkapi.com/docs/tiktok/profile-videos.md): A creator's videos and photo posts, newest first, up to 15 per page. | | `tiktok_comments` | [Video comments](https://lurkapi.com/docs/tiktok/comments.md): A video's top-level comments, most relevant first, with like and reply counts. | | `tiktok_comment_replies` | [Comment replies](https://lurkapi.com/docs/tiktok/comment-replies.md): The replies under one comment, oldest first. | | `tiktok_hashtag_videos` | [Hashtag videos](https://lurkapi.com/docs/tiktok/hashtag-videos.md): Videos posted with a hashtag, plus how big the hashtag is: total posts and views. | | `tiktok_song` | [Song](https://lurkapi.com/docs/tiktok/song.md): A TikTok sound: title, artist, album, length, cover, audio URL and how many videos use it. | | `tiktok_song_videos` | [Song videos](https://lurkapi.com/docs/tiktok/song-videos.md): Videos that use a sound, 30 per page, with stats and creators. | | `tiktok_ad_library_search` | [Search TikTok Ad Library](https://lurkapi.com/docs/tiktok/ad-library-search.md): Ads shown in the EU, the UK, Switzerland and Turkey that match a keyword or an advertiser, 12 per page. | | `tiktok_ad_library_ad` | [TikTok Ad Library ad](https://lurkapi.com/docs/tiktok/ad-library-ad.md): One ad from TikTok's EU Ad Library: creative, landing page, advertiser, who paid, targeting and reach by country, age and gender. | | `tiktok_live` | [Live status](https://lurkapi.com/docs/tiktok/live.md): Whether a public creator is live, with the current room title, viewers and start time. | | `tiktok_stories` | [Public stories](https://lurkapi.com/docs/tiktok/stories.md): A creator's active public stories, with captions, media and expiration times. | | `tiktok_product` | [TikTok Shop product](https://lurkapi.com/docs/tiktok/product.md): A US TikTok Shop product's price, images, seller, variants, sales and review totals. | --- # Profile > A creator's bio, avatar, verification and exact follower, like and video counts. - **Request:** `GET https://api.lurkapi.com/v1/tiktok/profile` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `tiktok_profile` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 15 minutes - **Try it live:** https://lurkapi.com/docs/tiktok/profile#try (no signup) - **Platform:** [TikTok](https://lurkapi.com/docs/tiktok.md) (tiktok.com) - **Web page:** https://lurkapi.com/docs/tiktok/profile ## When to use this Use it to size up a creator or brand account: followers, total likes and video count as exact numbers, plus bio, link and category. `user.secUid` is TikTok's stable id; `user.roomId` is set while they're live. Unknown, banned and deleted accounts return 404 `not_found`. Private accounts return their profile with `privateAccount: true`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `handle` | string | yes | | | TikTok username, with or without @ (e.g. `nike`), or the profile URL. | `nike` | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/tiktok/profile?handle=nike" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ handle: "nike", }); const res = await fetch(`https://api.lurkapi.com/v1/tiktok/profile?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/tiktok/profile", params={ "handle": "nike", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 99985, "credits_charged": 1, "user": { "id": "208464585232822272", "uniqueId": "nike", "nickname": "Nike", "signature": "Just Do It.", "secUid": "MS4wLjABAAAA_3ndMt8d_tECTdpKgCxcx238tOnQZX-20wqN01aMui5zQ7hsqSdff-jC5qYC-Cl_", "verified": true, "privateAccount": false, "createTime": 1490044565, "avatarLarger": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/e0b0ac7…", "avatarMedium": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/e0b0ac7…", "avatarThumb": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/e0b0ac7…", "bioLink": "http://empli.fi/niketiktok", "language": "en", "category": "Sports, Fitness & Outdoors", "commerceUser": true, "ttSeller": false, "isOrganization": true, "roomId": null }, "stats": { "followerCount": 9253124, "followingCount": 91, "heartCount": 49280939, "videoCount": 1089, "diggCount": 0, "friendCount": 46 } } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `user` | `object` | The account. | | `user.id` | `string` | Numeric user id. | | `user.uniqueId` | `string` | Username (handle), without @. | | `user.nickname` | `string` | Display name. | | `user.signature` | `string` | Bio text. | | `user.secUid` | `string` | TikTok's long, stable user id; other tools ask for it. | | `user.verified` | `boolean` | Has the verified badge. | | `user.privateAccount` | `boolean` | The account is private: videos and follower lists are hidden. | | `user.createTime` | `number` | Account created, Unix seconds. | | `user.avatarLarger` | `string`, nullable | Large avatar URL (expires after a few days); null if TikTok has none. | | `user.avatarMedium` | `string`, nullable | Medium avatar URL (expires after a few days); null if TikTok has none. | | `user.avatarThumb` | `string`, nullable | Small avatar URL (expires after a few days); null if TikTok has none. | | `user.bioLink` | `string`, nullable | The link in the bio; null if none. | | `user.language` | `string`, nullable | Account language, e.g. `en`; null if unknown. | | `user.category` | `string`, nullable | Business category, e.g. "Sports, Fitness & Outdoors"; null for personal accounts. | | `user.commerceUser` | `boolean` | A business account. | | `user.ttSeller` | `boolean` | Sells on TikTok Shop. | | `user.isOrganization` | `boolean` | Registered as an organization. | | `user.roomId` | `string`, nullable | Live room id TikTok reports; may belong to a finished stream. Use live status to verify; null when none. | | `stats` | `object` | Counts when fetched. | | `stats.followerCount` | `number` | Followers, exact. | | `stats.followingCount` | `number` | Accounts they follow. | | `stats.heartCount` | `number` | Total likes received across all videos. | | `stats.videoCount` | `number` | Public videos. | | `stats.diggCount` | `number` | Videos they liked (0 when hidden). | | `stats.friendCount` | `number` | Mutual follows. | ## Caching and freshness Responses are cached for up to 15 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `tiktok_profile` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's tiktok_profile with handle "nike" and summarize what you find. ``` Claude calls `tiktok_profile` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "handle": "nike" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Search YouTube](https://lurkapi.com/docs/youtube/search.md) · Next: [Video details](https://lurkapi.com/docs/tiktok/video.md) --- # Video details > One video or photo post: caption, stats, author, sound, hashtags and a playable MP4 URL. - **Request:** `GET https://api.lurkapi.com/v2/tiktok/video` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `tiktok_video` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 15 minutes - **Try it live:** https://lurkapi.com/docs/tiktok/video#try (no signup) - **Platform:** [TikTok](https://lurkapi.com/docs/tiktok.md) (tiktok.com) - **Web page:** https://lurkapi.com/docs/tiktok/video ## When to use this Use it to track a post's views, likes, comments, shares and saves, or to get its media. `video.playUrl` plays without cookies until `video.expires_at` (about two days); fetch the post again for a fresh one. Photo posts have their slides in `images`. Pass `get_transcript=true` to also get the captions as WEBVTT in `transcript` (same credit). Deleted, private and region-locked posts return 404 `not_found`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `url` | string | yes | | | The video's URL, e.g. `https://www.tiktok.com/@hubspot/video/7671325437229845774`. Photo post URLs and bare video ids work too. | `https://www.tiktok.com/@hubspot/video/7671325437229845774` | | `get_transcript` | boolean | no | `false` | `true`, `false` | `true` adds the captions as WEBVTT in `transcript` (null if the video has none). | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v2/tiktok/video?url=https%3A%2F%2Fwww.tiktok.com%2F%40hubspot%2Fvideo%2F7671325437229845774" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ url: "https://www.tiktok.com/@hubspot/video/7671325437229845774", }); const res = await fetch(`https://api.lurkapi.com/v2/tiktok/video?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.hashtags); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v2/tiktok/video", params={ "url": "https://www.tiktok.com/@hubspot/video/7671325437229845774", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["hashtags"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 99984, "credits_charged": 1, "id": "7671325437229845774", "url": "https://www.tiktok.com/@hubspot/video/7671325437229845774", "desc": "love at first MQL", "createTime": 1786119661, "textLanguage": "en", "locationCreated": "US", "isAd": true, "author": { "id": "6921028957732193285", "uniqueId": "hubspot", "nickname": "HubSpot", "secUid": "MS4wLjABAAAAuY18L_r3HfP3KEtK0txT2RPxDj1uSMSqbHZF33aYcGVAwL3PUA50WeQkR2o53xqr", "verified": true, "privateAccount": false, "avatarThumb": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/7351755…", "avatarLarger": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/7351755…" }, "stats": { "playCount": 1104, "diggCount": 25, "commentCount": 1, "shareCount": 0, "collectCount": 2, "repostCount": 0 }, "video": { "duration": 80, "width": 720, "height": 1280, "definition": "720p", "cover": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-p-85c255-tx/okN7mcf…", "dynamicCover": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-p-85c255-tx/oEDEMCV…", "playUrl": "https://www.tiktok.com/aweme/v1/play/?faid=1988&file_id=30509d3782234485…", "expires_at": 1790947227 }, "music": { "id": "7671325566393486094", "title": "original sound - HubSpot", "authorName": "HubSpot", "original": true, "duration": 80, "playUrl": "https://v16m.tiktokcdn-us.com/1eb8f6572957ab7073bf9056849b1a2c/6abd60fb/…", "cover": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/7351755…" }, "hashtags": [], "mentions": [], "images": [] } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `id` | `string` | Video id, e.g. `7671325437229845774`. | | `url` | `string` | The post on tiktok.com. | | `desc` | `string` | Caption, hashtags included. | | `createTime` | `number` | Posted at, Unix seconds. | | `textLanguage` | `string`, nullable | Caption language as TikTok detects it, e.g. `en`; null if unknown. | | `locationCreated` | `string`, nullable | Country the post was made in, as a 2-letter code; null if unknown. | | `isAd` | `boolean` | A paid ad (Spark Ad or promoted post). | | `author` | `object` | Who posted it. | | `author.id` | `string` | Numeric user id. | | `author.uniqueId` | `string` | Username (handle), without @. | | `author.nickname` | `string` | Display name. | | `author.secUid` | `string` | TikTok's long, stable user id; other tools ask for it. | | `author.verified` | `boolean` | Has the verified badge. | | `author.privateAccount` | `boolean` | The account is private. | | `author.avatarThumb` | `string`, nullable | Small avatar URL (expires after a few days); null if TikTok has none. | | `author.avatarLarger` | `string`, nullable | Large avatar URL (expires after a few days); null if TikTok has none. | | `stats` | `object` | Engagement when fetched. Exact numbers, not rounded. | | `stats.playCount` | `number` | Views. | | `stats.diggCount` | `number` | Likes. | | `stats.commentCount` | `number` | Comments. | | `stats.shareCount` | `number` | Shares. | | `stats.collectCount` | `number`, nullable | Saves (favorites); null where TikTok doesn't show it. | | `stats.repostCount` | `number`, nullable | Reposts; null where TikTok doesn't show it. | | `video` | `object` | The video file. For photo posts see `images`. | | `video.duration` | `number` | Length in seconds; 0 for photo posts. | | `video.width` | `number` | Width in pixels; 0 for photo posts. | | `video.height` | `number` | Height in pixels; 0 for photo posts. | | `video.definition` | `string`, nullable | Quality of the default rendition, e.g. `720p`. | | `video.cover` | `string`, nullable | Cover image URL (expires after a few days); null if TikTok has none. | | `video.dynamicCover` | `string`, nullable | Animated cover URL (expires after a few days); null if TikTok has none. | | `video.playUrl` | `string`, nullable | MP4 URL that plays without cookies (h264, best quality). It redirects to TikTok's CDN and expires at `expires_at`; null if TikTok has none. | | `video.expires_at` | `number`, nullable | When `playUrl` stops working, Unix seconds; null if unknown. | | `music` | `object`, nullable | The sound; null if the post has none. | | `music.id` | `string` | Sound id; pass it to song endpoints. | | `music.title` | `string` | Sound title, e.g. `original sound`. | | `music.authorName` | `string`, nullable | Artist, or the creator for original sounds. | | `music.original` | `boolean` | An original sound made with this post rather than a track. | | `music.duration` | `number`, nullable | Length in seconds. | | `music.playUrl` | `string`, nullable | Audio URL (expires after a few hours); null if TikTok has none. | | `music.cover` | `string`, nullable | Sound cover image URL; null if TikTok has none. | | `hashtags` | `string[]` | Hashtags in the caption, without #. | | `mentions` | `string[]` | Usernames @mentioned in the caption. | | `images` | `object[]` | The slides of a photo post, in order. Empty for videos. | | `images[].url` | `string` | Image URL (expires after a few days). | | `images[].width` | `number` | Width in pixels. | | `images[].height` | `number` | Height in pixels. | | `transcript` | `string`, nullable, optional | With `get_transcript=true`: the captions as WEBVTT in the video's original language; null if it has none. | ## Caching and freshness Responses are cached for up to 15 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `tiktok_video` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's tiktok_video with url "https://www.tiktok.com/@hubspot/video/7671325437229845774" and summarize what you find. ``` Claude calls `tiktok_video` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "url": "https://www.tiktok.com/@hubspot/video/7671325437229845774" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Profile](https://lurkapi.com/docs/tiktok/profile.md) · Next: [Video transcript](https://lurkapi.com/docs/tiktok/transcript.md) --- # Video transcript > What's said in a video, from TikTok's captions: WEBVTT with timestamps plus plain text. - **Request:** `GET https://api.lurkapi.com/v1/tiktok/video/transcript` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call; 5 if speech-to-text runs. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `tiktok_transcript` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 30 days - **Try it live:** https://lurkapi.com/docs/tiktok/transcript#try (no signup; free tool: [TikTok transcript generator](https://lurkapi.com/free-tools/tiktok-transcript)) - **Platform:** [TikTok](https://lurkapi.com/docs/tiktok.md) (tiktok.com) - **Web page:** https://lurkapi.com/docs/tiktok/transcript ## When to use this Use it to turn a TikTok into text: repurpose a script, search what creators say, or feed it to an LLM. `transcript` is WEBVTT with timestamps (the same format other TikTok APIs return); `text` is the same words as plain text. Transcripts come from TikTok's own captions: auto-generated speech captions (`source: asr`), the creator's own captions, or TikTok's translations. Most spoken videos have them. A video without captions (music only, or captions off) returns `transcript: null` with `languages: []`. **No captions?** Pass `use_ai_as_fallback=true` and we transcribe the audio ourselves (`source: ai`) for videos up to 2 minutes. That costs 5 credits in total, and only when it runs; with captions it's still 1. If it hears no speech (music only), you get 404 `transcript_unavailable` for 1 credit, and that answer is cached. By default you get the original language. Pass `language` (e.g. `es`) to pick one of `languages`. Deleted, private and region-locked videos return 404 `not_found`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `url` | string | yes | | | The video's URL, e.g. `https://www.tiktok.com/@hubspot/video/7671325437229845774`. Photo post URLs and bare video ids work too. | `https://www.tiktok.com/@hubspot/video/7671325437229845774` | | `language` | string | no | | | 2-letter language code, e.g. `en` or `es`. Default: the video's original language. | | | `use_ai_as_fallback` | boolean | no | `false` | `true`, `false` | `true`: if the video has no captions, transcribe its audio with speech-to-text (videos up to 2 minutes; 5 credits in total, only when it runs). | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/tiktok/video/transcript?url=https%3A%2F%2Fwww.tiktok.com%2F%40hubspot%2Fvideo%2F7671325437229845774" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ url: "https://www.tiktok.com/@hubspot/video/7671325437229845774", }); const res = await fetch(`https://api.lurkapi.com/v1/tiktok/video/transcript?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.languages); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/tiktok/video/transcript", params={ "url": "https://www.tiktok.com/@hubspot/video/7671325437229845774", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["languages"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 99983, "credits_charged": 1, "id": "7671325437229845774", "url": "https://www.tiktok.com/@hubspot/video/7671325437229845774", "languages": [ { "language": "en", "source": "asr" } ], "transcript": "WEBVTT\n\n\n00:00:00.060 --> 00:00:02.400\nWhen your lead score came across …", "text": "When your lead score came across the threshold,\nI knew there was potenti…", "language": "en", "source": "asr" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `id` | `string` | Video id. | | `url` | `string` | The video on tiktok.com. | | `transcript` | `string`, nullable | The captions as WEBVTT, with timestamps. null if the video has none in that language. | | `text` | `string`, nullable | The same captions as plain text, one cue per line. null if none. | | `language` | `string`, nullable | Language of `transcript`, e.g. `en`. null if none. | | `source` | `string`, nullable | `asr`: TikTok's auto-generated speech captions. `creator`: captions the creator wrote. `translation`: TikTok's machine translation. `ai`: our speech-to-text (`use_ai_as_fallback`). null if none. | | `languages` | `object[]` | Every caption language TikTok has for the video. Empty when it has none. | | `languages[].language` | `string` | Language code, usually 2 letters, e.g. `en`. | | `languages[].source` | `string` | Where these captions come from (see `source`). | ## Caching and freshness Responses are cached for up to 30 days, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 404 | `transcript_unavailable` | The video has no captions and speech-to-text heard no speech in it. Charged the base credit, never the speech-to-text extra; the answer is cached. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `tiktok_transcript` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's tiktok_transcript with url "https://www.tiktok.com/@hubspot/video/7671325437229845774" and summarize what you find. ``` Claude calls `tiktok_transcript` with arguments like these, and each call costs 1 credit; 5 if speech-to-text runs: Tool arguments: ```json { "url": "https://www.tiktok.com/@hubspot/video/7671325437229845774" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Video details](https://lurkapi.com/docs/tiktok/video.md) · Next: [Profile videos](https://lurkapi.com/docs/tiktok/profile-videos.md) --- # Profile videos > A creator's videos and photo posts, newest first, up to 15 per page. - **Request:** `GET https://api.lurkapi.com/v3/tiktok/profile/videos` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `tiktok_profile_videos` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 5 minutes - **Try it live:** https://lurkapi.com/docs/tiktok/profile-videos#try (no signup) - **Platform:** [TikTok](https://lurkapi.com/docs/tiktok.md) (tiktok.com) - **Web page:** https://lurkapi.com/docs/tiktok/profile-videos ## When to use this Use it to audit a creator's recent content: every post with views, likes, comments, shares, saves, caption and media. Posts come newest first, up to 15 per page. Pinned posts appear in date order, like every other post. **Pagination.** Pass the response's `max_cursor` back as `max_cursor` (with the same `handle`) for older posts. `has_more` is false at the end. A creator who hasn't posted for weeks can give a page with no posts but `has_more: true`; keep paging. Unknown, banned and deleted accounts return 404 `not_found`. Private accounts return an empty `videos` list. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `handle` | string | yes | | | TikTok username, with or without @ (e.g. `nike`), or the profile URL. | `hubspot` | | `max_cursor` | string | no | | | The `max_cursor` from the previous response, to get older posts. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v3/tiktok/profile/videos?handle=hubspot" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ handle: "hubspot", }); const res = await fetch(`https://api.lurkapi.com/v3/tiktok/profile/videos?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.videos); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v3/tiktok/profile/videos", params={ "handle": "hubspot", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["videos"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 99982, "credits_charged": 1, "videos": [ { "id": "7688075789086084366", "url": "https://www.tiktok.com/@hubspot/video/7688075789086084366", "desc": "Monday is feeling extra Monday after an incredible week at UNBOUND. See you next year!!", "createTime": 1790019661, "textLanguage": "en", "locationCreated": null, "isAd": true, "author": { "id": "6921028957732193285", "uniqueId": "hubspot", "nickname": "HubSpot", "secUid": "MS4wLjABAAAAuY18L_r3HfP3KEtK0txT2RPxDj1uSMSqbHZF33aYcGVAwL3PUA50WeQkR2o53xqr", "verified": true, "privateAccount": false, "avatarThumb": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/7351755…", "avatarLarger": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/7351755…" }, "stats": { "playCount": 854, "diggCount": 19, "commentCount": 0, "shareCount": 0, "collectCount": 4, "repostCount": 0 }, "video": { "duration": 32, "width": 720, "height": 1280, "definition": "720p", "cover": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-p-85c255-tx/oYgDCkV…", "dynamicCover": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-p-85c255-tx/o8LxmgQ…", "playUrl": "https://www.tiktok.com/aweme/v1/play/?faid=1988&file_id=ba2050668fcb4317…", "expires_at": 1790947173 }, "music": { "id": "7688075873761348366", "title": "original sound", "authorName": "HubSpot", "original": true, "duration": 32, "playUrl": "https://v16-webapp-prime.us.tiktok.com/video/tos/useast5/tos-useast5-v-2…", "cover": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/7351755…" }, "hashtags": [], "mentions": [], "images": [] }, { "id": "7686983062382480654", "url": "https://www.tiktok.com/@hubspot/video/7686983062382480654", "desc": "Stepping into Q4, ready to crush quotas and serve looks", "createTime": 1789765213, "textLanguage": "en", "locationCreated": null, "isAd": false, "author": { "id": "6921028957732193285", "uniqueId": "hubspot", "nickname": "HubSpot", "secUid": "MS4wLjABAAAAuY18L_r3HfP3KEtK0txT2RPxDj1uSMSqbHZF33aYcGVAwL3PUA50WeQkR2o53xqr", "verified": true, "privateAccount": false, "avatarThumb": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/7351755…", "avatarLarger": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/7351755…" }, "stats": { "playCount": 690, "diggCount": 25, "commentCount": 2, "shareCount": 9, "collectCount": 3, "repostCount": 0 }, "video": { "duration": 35, "width": 720, "height": 1280, "definition": "720p", "cover": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-p-85c255-tx/oI6RfJD…", "dynamicCover": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-p-85c255-tx/oA6tD0t…", "playUrl": "https://www.tiktok.com/aweme/v1/play/?faid=1988&file_id=ee0f55c7c0254005…", "expires_at": 1790947176 }, "music": { "id": "7686983088345352973", "title": "original sound", "authorName": "HubSpot", "original": true, "duration": 35, "playUrl": "https://v16-webapp-prime.us.tiktok.com/video/tos/useast5/tos-useast5-v-2…", "cover": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/7351755…" }, "hashtags": [], "mentions": [], "images": [] } ], "has_more": true, "max_cursor": "1783021491000" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `videos` | `object[]` | Posts, newest first. | | `videos[].id` | `string` | Video id, e.g. `7671325437229845774`. | | `videos[].url` | `string` | The post on tiktok.com. | | `videos[].desc` | `string` | Caption, hashtags included. | | `videos[].createTime` | `number` | Posted at, Unix seconds. | | `videos[].textLanguage` | `string`, nullable | Caption language as TikTok detects it, e.g. `en`; null if unknown. | | `videos[].locationCreated` | `string`, nullable | Country the post was made in, as a 2-letter code; null if unknown. | | `videos[].isAd` | `boolean` | A paid ad (Spark Ad or promoted post). | | `videos[].author` | `object` | Who posted it. | | `videos[].author.id` | `string` | Numeric user id. | | `videos[].author.uniqueId` | `string` | Username (handle), without @. | | `videos[].author.nickname` | `string` | Display name. | | `videos[].author.secUid` | `string` | TikTok's long, stable user id; other tools ask for it. | | `videos[].author.verified` | `boolean` | Has the verified badge. | | `videos[].author.privateAccount` | `boolean` | The account is private. | | `videos[].author.avatarThumb` | `string`, nullable | Small avatar URL (expires after a few days); null if TikTok has none. | | `videos[].author.avatarLarger` | `string`, nullable | Large avatar URL (expires after a few days); null if TikTok has none. | | `videos[].stats` | `object` | Engagement when fetched. Exact numbers, not rounded. | | `videos[].stats.playCount` | `number` | Views. | | `videos[].stats.diggCount` | `number` | Likes. | | `videos[].stats.commentCount` | `number` | Comments. | | `videos[].stats.shareCount` | `number` | Shares. | | `videos[].stats.collectCount` | `number`, nullable | Saves (favorites); null where TikTok doesn't show it. | | `videos[].stats.repostCount` | `number`, nullable | Reposts; null where TikTok doesn't show it. | | `videos[].video` | `object` | The video file. For photo posts see `images`. | | `videos[].video.duration` | `number` | Length in seconds; 0 for photo posts. | | `videos[].video.width` | `number` | Width in pixels; 0 for photo posts. | | `videos[].video.height` | `number` | Height in pixels; 0 for photo posts. | | `videos[].video.definition` | `string`, nullable | Quality of the default rendition, e.g. `720p`. | | `videos[].video.cover` | `string`, nullable | Cover image URL (expires after a few days); null if TikTok has none. | | `videos[].video.dynamicCover` | `string`, nullable | Animated cover URL (expires after a few days); null if TikTok has none. | | `videos[].video.playUrl` | `string`, nullable | MP4 URL that plays without cookies (h264, best quality). It redirects to TikTok's CDN and expires at `expires_at`; null if TikTok has none. | | `videos[].video.expires_at` | `number`, nullable | When `playUrl` stops working, Unix seconds; null if unknown. | | `videos[].music` | `object`, nullable | The sound; null if the post has none. | | `videos[].music.id` | `string` | Sound id; pass it to song endpoints. | | `videos[].music.title` | `string` | Sound title, e.g. `original sound`. | | `videos[].music.authorName` | `string`, nullable | Artist, or the creator for original sounds. | | `videos[].music.original` | `boolean` | An original sound made with this post rather than a track. | | `videos[].music.duration` | `number`, nullable | Length in seconds. | | `videos[].music.playUrl` | `string`, nullable | Audio URL (expires after a few hours); null if TikTok has none. | | `videos[].music.cover` | `string`, nullable | Sound cover image URL; null if TikTok has none. | | `videos[].hashtags` | `string[]` | Hashtags in the caption, without #. | | `videos[].mentions` | `string[]` | Usernames @mentioned in the caption. | | `videos[].images` | `object[]` | The slides of a photo post, in order. Empty for videos. | | `videos[].images[].url` | `string` | Image URL (expires after a few days). | | `videos[].images[].width` | `number` | Width in pixels. | | `videos[].images[].height` | `number` | Height in pixels. | | `has_more` | `boolean` | There are older posts. | | `max_cursor` | `string`, nullable | Pass as `max_cursor` for older posts. null on the last page. | ## Caching and freshness Responses are cached for up to 5 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `tiktok_profile_videos` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's tiktok_profile_videos with handle "hubspot" and summarize what you find. ``` Claude calls `tiktok_profile_videos` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "handle": "hubspot" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Video transcript](https://lurkapi.com/docs/tiktok/transcript.md) · Next: [Video comments](https://lurkapi.com/docs/tiktok/comments.md) --- # Video comments > A video's top-level comments, most relevant first, with like and reply counts. - **Request:** `GET https://api.lurkapi.com/v1/tiktok/video/comments` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `tiktok_comments` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 5 minutes - **Try it live:** https://lurkapi.com/docs/tiktok/comments#try (no signup) - **Platform:** [TikTok](https://lurkapi.com/docs/tiktok.md) (tiktok.com) - **Web page:** https://lurkapi.com/docs/tiktok/comments ## When to use this Use it to read what viewers say: objections, questions, praise and the words your audience uses. Each comment has its likes, reply count and author; pass a comment's `cid` as `comment_id` to comment replies for its thread. **Pagination.** 50 comments per page. Pass the response's `cursor` back as `cursor` (with the same `url`) for more; it's null at the end. Deleted or private videos return 404 `not_found`; videos with comments turned off return an empty list. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `url` | string | yes | | | The video's URL, e.g. `https://www.tiktok.com/@hubspot/video/7671325437229845774`. Photo post URLs and bare video ids work too. | `https://www.tiktok.com/@scout2015/video/6718335390845095173` | | `cursor` | string | no | | | The `cursor` from the previous response, to get the next page. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/tiktok/video/comments?url=https%3A%2F%2Fwww.tiktok.com%2F%40scout2015%2Fvideo%2F6718335390845095173" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ url: "https://www.tiktok.com/@scout2015/video/6718335390845095173", }); const res = await fetch(`https://api.lurkapi.com/v1/tiktok/video/comments?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.comments); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/tiktok/video/comments", params={ "url": "https://www.tiktok.com/@scout2015/video/6718335390845095173", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["comments"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 99981, "credits_charged": 1, "comments": [ { "cid": "6718335906996502534", "text": "ok ;)\nodoreta\nhave fun~\n(it's a Serbian name)", "create_time": 1564234475, "digg_count": 166, "reply_comment_total": 49, "comment_language": "un", "author_pin": false, "is_author_digged": false, "reply_id": "0", "reply_to_reply_id": "0", "aweme_id": "6718335390845095173", "user": { "uid": "6650133637952618502", "unique_id": "flamigo_kisses", "nickname": "flamigoo", "sec_uid": "MS4wLjABAAAA2MsvjMy0b24-FHIEBfHzeBhZelr6GpESRqTxE-LAA5ByJY5rSolRWE3rXB4Mfqt6", "avatar_thumb": "https://p16-common-sign.tiktokcdn-us.com/tos-maliva-avt-0068/73363709566…" } }, { "cid": "6722043930156417030", "text": "srii good luck", "create_time": 1565097817, "digg_count": 53, "reply_comment_total": 44, "comment_language": "un", "author_pin": false, "is_author_digged": false, "reply_id": "0", "reply_to_reply_id": "0", "aweme_id": "6718335390845095173", "user": { "uid": "6718326650415203333", "unique_id": "vsco_room_xx", "nickname": "vsco_room_xx", "sec_uid": "MS4wLjABAAAArG6B4beBhFRzOVGzxvDgAKuz2yK0AHG9jfU6PiSjFoIPrtYEASAPir_quhWWPs-4", "avatar_thumb": "https://p16-common-sign.tiktokcdn-us.com/tos-maliva-avt-0068/73314074326…" } } ], "total": 5844, "has_more": true, "cursor": "50" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `comments` | `object[]` | Comments, in TikTok's order. | | `comments[].cid` | `string` | Comment id; pass it as `comment_id` to comment replies. | | `comments[].text` | `string` | Comment text. | | `comments[].create_time` | `number` | Posted at, Unix seconds. | | `comments[].digg_count` | `number` | Likes. | | `comments[].reply_comment_total` | `number` | Replies to this comment. Always 0 on replies. | | `comments[].comment_language` | `string`, nullable | Language as TikTok detects it, e.g. `en`; `un` = undetermined. | | `comments[].author_pin` | `boolean` | Pinned by the video's creator. | | `comments[].is_author_digged` | `boolean` | Liked by the video's creator. | | `comments[].reply_id` | `string` | On replies: the comment it belongs to. `0` on top-level comments. | | `comments[].reply_to_reply_id` | `string` | On replies: the reply it answers, if any. `0` otherwise. | | `comments[].aweme_id` | `string` | The video's id. | | `comments[].user` | `object` | Who wrote it. | | `comments[].user.uid` | `string` | Numeric user id. | | `comments[].user.unique_id` | `string` | Username (handle), without @. | | `comments[].user.nickname` | `string` | Display name. | | `comments[].user.sec_uid` | `string` | TikTok's long, stable user id. | | `comments[].user.avatar_thumb` | `string`, nullable | Small avatar URL (expires after a few days); null if TikTok has none. | | `total` | `number` | Comments TikTok counts in this thread (may include some it no longer shows). | | `has_more` | `boolean` | More comments after this page. | | `cursor` | `string`, nullable | Pass as `cursor` for the next page. null on the last page. | ## Pagination Pass the response's `cursor` back as `cursor`, with the other parameters unchanged, for the next page. It's `null` on the last page. Each page costs 1 credit. The first five pages, in JavaScript: ```js let cursor = null; for (let page = 1; page <= 5; page++) { const params = new URLSearchParams({ url: "https://www.tiktok.com/@scout2015/video/6718335390845095173" }); if (cursor) params.set("cursor", cursor); const res = await fetch(`https://api.lurkapi.com/v1/tiktok/video/comments?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.comments); cursor = data.cursor; if (!cursor) break; // last page } ``` ## Caching and freshness Responses are cached for up to 5 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `tiktok_comments` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's tiktok_comments with url "https://www.tiktok.com/@scout2015/video/6718335390845095173" and summarize what you find. ``` Claude calls `tiktok_comments` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "url": "https://www.tiktok.com/@scout2015/video/6718335390845095173" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Profile videos](https://lurkapi.com/docs/tiktok/profile-videos.md) · Next: [Comment replies](https://lurkapi.com/docs/tiktok/comment-replies.md) --- # Comment replies > The replies under one comment, oldest first. - **Request:** `GET https://api.lurkapi.com/v1/tiktok/video/comment/replies` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `tiktok_comment_replies` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 5 minutes - **Try it live:** https://lurkapi.com/docs/tiktok/comment-replies#try (no signup) - **Platform:** [TikTok](https://lurkapi.com/docs/tiktok.md) (tiktok.com) - **Web page:** https://lurkapi.com/docs/tiktok/comment-replies ## When to use this Use it to read a comment's thread. Get `comment_id` from a comment's `cid` in video comments. `reply_to_reply_id` shows which reply a reply answers. **Pagination.** 50 replies per page. Pass the response's `cursor` back as `cursor` (with the same `url` and `comment_id`); it's null at the end. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `url` | string | yes | | | The video's URL, e.g. `https://www.tiktok.com/@hubspot/video/7671325437229845774`. Photo post URLs and bare video ids work too. | `https://www.tiktok.com/@scout2015/video/6718335390845095173` | | `comment_id` | string | yes | | | The comment's `cid`, from video comments. | `6718335906996502534` | | `cursor` | string | no | | | The `cursor` from the previous response, to get the next page. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/tiktok/video/comment/replies?url=https%3A%2F%2Fwww.tiktok.com%2F%40scout2015%2Fvideo%2F6718335390845095173&comment_id=6718335906996502534" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ url: "https://www.tiktok.com/@scout2015/video/6718335390845095173", comment_id: "6718335906996502534", }); const res = await fetch(`https://api.lurkapi.com/v1/tiktok/video/comment/replies?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.comments); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/tiktok/video/comment/replies", params={ "url": "https://www.tiktok.com/@scout2015/video/6718335390845095173", "comment_id": "6718335906996502534", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["comments"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 99980, "credits_charged": 1, "comments": [ { "cid": "6718605798119555078", "text": "dorotea?", "create_time": 1564297316, "digg_count": 0, "reply_comment_total": 0, "comment_language": "un", "author_pin": false, "is_author_digged": false, "reply_id": "6718335906996502534", "reply_to_reply_id": "0", "aweme_id": "6718335390845095173", "user": { "uid": "6654948959952060422", "unique_id": "marladraws", "nickname": "💫marladraws💫", "sec_uid": "MS4wLjABAAAAzF1KjnfSfE_eWFjkxNFtBs50oCJyfErzibw8CT92-_TP3jMBhgTvxab7VEr7yhhC", "avatar_thumb": "https://p16-common-sign.tiktokcdn-us.com/tos-maliva-avt-0068/0a9840d173e…" } }, { "cid": "6718648089844400133", "text": "yes!", "create_time": 1564307161, "digg_count": 2, "reply_comment_total": 0, "comment_language": "un", "author_pin": false, "is_author_digged": false, "reply_id": "6718335906996502534", "reply_to_reply_id": "6718605798119555078", "aweme_id": "6718335390845095173", "user": { "uid": "6650133637952618502", "unique_id": "flamigo_kisses", "nickname": "flamigoo", "sec_uid": "MS4wLjABAAAA2MsvjMy0b24-FHIEBfHzeBhZelr6GpESRqTxE-LAA5ByJY5rSolRWE3rXB4Mfqt6", "avatar_thumb": "https://p16-common-sign.tiktokcdn-us.com/tos-maliva-avt-0068/73363709566…" } } ], "total": 56, "has_more": true, "cursor": "50" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `comments` | `object[]` | Replies, oldest first. | | `comments[].cid` | `string` | Comment id; pass it as `comment_id` to comment replies. | | `comments[].text` | `string` | Comment text. | | `comments[].create_time` | `number` | Posted at, Unix seconds. | | `comments[].digg_count` | `number` | Likes. | | `comments[].reply_comment_total` | `number` | Replies to this comment. Always 0 on replies. | | `comments[].comment_language` | `string`, nullable | Language as TikTok detects it, e.g. `en`; `un` = undetermined. | | `comments[].author_pin` | `boolean` | Pinned by the video's creator. | | `comments[].is_author_digged` | `boolean` | Liked by the video's creator. | | `comments[].reply_id` | `string` | On replies: the comment it belongs to. `0` on top-level comments. | | `comments[].reply_to_reply_id` | `string` | On replies: the reply it answers, if any. `0` otherwise. | | `comments[].aweme_id` | `string` | The video's id. | | `comments[].user` | `object` | Who wrote it. | | `comments[].user.uid` | `string` | Numeric user id. | | `comments[].user.unique_id` | `string` | Username (handle), without @. | | `comments[].user.nickname` | `string` | Display name. | | `comments[].user.sec_uid` | `string` | TikTok's long, stable user id. | | `comments[].user.avatar_thumb` | `string`, nullable | Small avatar URL (expires after a few days); null if TikTok has none. | | `total` | `number` | Comments TikTok counts in this thread (may include some it no longer shows). | | `has_more` | `boolean` | More comments after this page. | | `cursor` | `string`, nullable | Pass as `cursor` for the next page. null on the last page. | ## Pagination Pass the response's `cursor` back as `cursor`, with the other parameters unchanged, for the next page. It's `null` on the last page. Each page costs 1 credit. The first five pages, in JavaScript: ```js let cursor = null; for (let page = 1; page <= 5; page++) { const params = new URLSearchParams({ url: "https://www.tiktok.com/@scout2015/video/6718335390845095173", comment_id: "6718335906996502534" }); if (cursor) params.set("cursor", cursor); const res = await fetch(`https://api.lurkapi.com/v1/tiktok/video/comment/replies?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.comments); cursor = data.cursor; if (!cursor) break; // last page } ``` ## Caching and freshness Responses are cached for up to 5 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `tiktok_comment_replies` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's tiktok_comment_replies with url "https://www.tiktok.com/@scout2015/video/6718335390845095173", comment_id "6718335906996502534" and summarize what you find. ``` Claude calls `tiktok_comment_replies` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "url": "https://www.tiktok.com/@scout2015/video/6718335390845095173", "comment_id": "6718335906996502534" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Video comments](https://lurkapi.com/docs/tiktok/comments.md) · Next: [Hashtag videos](https://lurkapi.com/docs/tiktok/hashtag-videos.md) --- # Hashtag videos > Videos posted with a hashtag, plus how big the hashtag is: total posts and views. - **Request:** `GET https://api.lurkapi.com/v1/tiktok/search/hashtag` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `tiktok_hashtag_videos` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 10 minutes - **Try it live:** https://lurkapi.com/docs/tiktok/hashtag-videos#try (no signup) - **Platform:** [TikTok](https://lurkapi.com/docs/tiktok.md) (tiktok.com) - **Web page:** https://lurkapi.com/docs/tiktok/hashtag-videos ## When to use this Use it to research a trend, a niche or a campaign hashtag: which creators post under it, what performs, and how big it is (`hashtag.videoCount` and `hashtag.viewCount`, exact). Videos come in the order tiktok.com's hashtag page shows them: neither newest nor most viewed first, and often months old. **Pagination.** Up to 30 videos per page. Pass the response's `cursor` back as `cursor` (with the same `hashtag`) for more; `has_more` is false at the end. A few videos can repeat across pages, so dedupe by `id`. At times TikTok shows logged-out visitors only a handful of a hashtag's videos (with `has_more: false`), even for a big one; it serves full pages again later. Unknown hashtags return 404 `not_found`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `hashtag` | string | yes | | | The hashtag, with or without # (e.g. `skincare`), or its tiktok.com/tag/… URL. | `smallbusiness` | | `cursor` | string | no | | | The `cursor` from the previous response, to get the next page. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/tiktok/search/hashtag?hashtag=smallbusiness" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ hashtag: "smallbusiness", }); const res = await fetch(`https://api.lurkapi.com/v1/tiktok/search/hashtag?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.videos); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/tiktok/search/hashtag", params={ "hashtag": "smallbusiness", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["videos"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 99956, "credits_charged": 1, "hashtag": { "id": "31667995", "title": "SmallBusiness", "desc": "Join the #SmallBusiness community", "videoCount": 44173142, "viewCount": 252781323890, "isCommerce": false }, "videos": [ { "id": "7451403653912677678", "url": "https://www.tiktok.com/@franchisejon/video/7451403653912677678", "desc": "How to own a Nothing Bundt Cakes franchise? #franchise #franchiseopportu…", "createTime": 1734915113, "textLanguage": "en", "locationCreated": null, "isAd": false, "author": { "id": "6609105852464021510", "uniqueId": "franchisejon", "nickname": "franchisejon", "secUid": "MS4wLjABAAAA-ko4urvbwco3xX8NTQjMzh3stLu7nG-SccyZN5Dohp3w70QvgmUIjsqFAn8N-EPm", "verified": false, "privateAccount": false, "avatarThumb": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/92014e7…", "avatarLarger": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/92014e7…" }, "stats": { "playCount": 620900, "diggCount": 13000, "commentCount": 663, "shareCount": 1814, "collectCount": 1112, "repostCount": 0 }, "video": { "duration": 55, "width": 576, "height": 1024, "definition": "540p", "cover": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/oodootgaf…", "dynamicCover": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/oodootgaf…", "playUrl": "https://www.tiktok.com/aweme/v1/play/?faid=1988&file_id=177b34e2f668411f…", "expires_at": 1790947674 }, "music": { "id": "7451403593065925422", "title": "original sound", "authorName": "franchisejon", "original": true, "duration": 55, "playUrl": "https://v16m.tiktokcdn-us.com/bc91a83fbd76f761a46bd585d6c256b4/6abd62ba/…", "cover": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/92014e7…" }, "hashtags": [ "franchise", "franchiseopportunities", "business", "businesscheck", "businesstips", "tiktokbusiness", "fyp", "careeradvice", "careerchange", "beyourownboss", "entrepreneur", "entrepreneurship", "franchisee", "tiktokbusinesscampaign", "smallbusiness", "businessowner", "businesstiktok" ], "mentions": [], "images": [] }, { "id": "7449732446209232170", "url": "https://www.tiktok.com/@rusty_exotics/video/7449732446209232170", "desc": "The birthplace of orchids - RustyExotics #SmallBusiness #TikTokBusinessC…", "createTime": 1734526007, "textLanguage": "en", "locationCreated": null, "isAd": false, "author": { "id": "7352872043438359595", "uniqueId": "rusty_exotics", "nickname": "RustyExotics", "secUid": "MS4wLjABAAAALLqyyXChFpFfg4oaT9FON0smbQ5pHXc29absfjMy-w6HyKDNkr5O29ocVuIV2QmB", "verified": false, "privateAccount": false, "avatarThumb": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/68e2af6…", "avatarLarger": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/68e2af6…" }, "stats": { "playCount": 209200, "diggCount": 17500, "commentCount": 159, "shareCount": 971, "collectCount": 1255, "repostCount": 0 }, "video": { "duration": 88, "width": 576, "height": 1024, "definition": "540p", "cover": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/oMxhD6fDS…", "dynamicCover": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/oMxhD6fDS…", "playUrl": "https://www.tiktok.com/aweme/v1/play/?faid=1988&file_id=d49684dac1cd4a3e…", "expires_at": 1790947707 }, "music": { "id": "7380329705533573136", "title": "growth", "authorName": "Gede Yudis", "original": false, "duration": 215, "playUrl": null, "cover": "https://p19-common-sign.tiktokcdn-us.com/tos-alisg-v-2774/oYgBEoKYkBBMQE…" }, "hashtags": [ "smallbusiness", "tiktokbusinesscampaign", "biology", "science", "orchid", "plants" ], "mentions": [], "images": [] } ], "has_more": true, "cursor": "55" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `hashtag` | `object` | The hashtag. | | `hashtag.id` | `string` | TikTok's id for the hashtag. | | `hashtag.title` | `string` | The hashtag, without #. | | `hashtag.desc` | `string` | TikTok's description of the hashtag; empty for most. | | `hashtag.videoCount` | `number` | Posts using the hashtag, exact. | | `hashtag.viewCount` | `number` | Total views of those posts, exact. | | `hashtag.isCommerce` | `boolean` | A branded hashtag (a sponsored hashtag challenge). | | `videos` | `object[]` | Posts, in the order tiktok.com shows them (not newest first). | | `videos[].id` | `string` | Video id, e.g. `7671325437229845774`. | | `videos[].url` | `string` | The post on tiktok.com. | | `videos[].desc` | `string` | Caption, hashtags included. | | `videos[].createTime` | `number` | Posted at, Unix seconds. | | `videos[].textLanguage` | `string`, nullable | Caption language as TikTok detects it, e.g. `en`; null if unknown. | | `videos[].locationCreated` | `string`, nullable | Country the post was made in, as a 2-letter code; null if unknown. | | `videos[].isAd` | `boolean` | A paid ad (Spark Ad or promoted post). | | `videos[].author` | `object` | Who posted it. | | `videos[].author.id` | `string` | Numeric user id. | | `videos[].author.uniqueId` | `string` | Username (handle), without @. | | `videos[].author.nickname` | `string` | Display name. | | `videos[].author.secUid` | `string` | TikTok's long, stable user id; other tools ask for it. | | `videos[].author.verified` | `boolean` | Has the verified badge. | | `videos[].author.privateAccount` | `boolean` | The account is private. | | `videos[].author.avatarThumb` | `string`, nullable | Small avatar URL (expires after a few days); null if TikTok has none. | | `videos[].author.avatarLarger` | `string`, nullable | Large avatar URL (expires after a few days); null if TikTok has none. | | `videos[].stats` | `object` | Engagement when fetched. Exact numbers, not rounded. | | `videos[].stats.playCount` | `number` | Views. | | `videos[].stats.diggCount` | `number` | Likes. | | `videos[].stats.commentCount` | `number` | Comments. | | `videos[].stats.shareCount` | `number` | Shares. | | `videos[].stats.collectCount` | `number`, nullable | Saves (favorites); null where TikTok doesn't show it. | | `videos[].stats.repostCount` | `number`, nullable | Reposts; null where TikTok doesn't show it. | | `videos[].video` | `object` | The video file. For photo posts see `images`. | | `videos[].video.duration` | `number` | Length in seconds; 0 for photo posts. | | `videos[].video.width` | `number` | Width in pixels; 0 for photo posts. | | `videos[].video.height` | `number` | Height in pixels; 0 for photo posts. | | `videos[].video.definition` | `string`, nullable | Quality of the default rendition, e.g. `720p`. | | `videos[].video.cover` | `string`, nullable | Cover image URL (expires after a few days); null if TikTok has none. | | `videos[].video.dynamicCover` | `string`, nullable | Animated cover URL (expires after a few days); null if TikTok has none. | | `videos[].video.playUrl` | `string`, nullable | MP4 URL that plays without cookies (h264, best quality). It redirects to TikTok's CDN and expires at `expires_at`; null if TikTok has none. | | `videos[].video.expires_at` | `number`, nullable | When `playUrl` stops working, Unix seconds; null if unknown. | | `videos[].music` | `object`, nullable | The sound; null if the post has none. | | `videos[].music.id` | `string` | Sound id; pass it to song endpoints. | | `videos[].music.title` | `string` | Sound title, e.g. `original sound`. | | `videos[].music.authorName` | `string`, nullable | Artist, or the creator for original sounds. | | `videos[].music.original` | `boolean` | An original sound made with this post rather than a track. | | `videos[].music.duration` | `number`, nullable | Length in seconds. | | `videos[].music.playUrl` | `string`, nullable | Audio URL (expires after a few hours); null if TikTok has none. | | `videos[].music.cover` | `string`, nullable | Sound cover image URL; null if TikTok has none. | | `videos[].hashtags` | `string[]` | Hashtags in the caption, without #. | | `videos[].mentions` | `string[]` | Usernames @mentioned in the caption. | | `videos[].images` | `object[]` | The slides of a photo post, in order. Empty for videos. | | `videos[].images[].url` | `string` | Image URL (expires after a few days). | | `videos[].images[].width` | `number` | Width in pixels. | | `videos[].images[].height` | `number` | Height in pixels. | | `has_more` | `boolean` | There are more posts after this page. | | `cursor` | `string`, nullable | Pass as `cursor` for the next page. null on the last page. | ## Pagination Pass the response's `cursor` back as `cursor`, with the other parameters unchanged, for the next page. It's `null` on the last page. Each page costs 1 credit. The first five pages, in JavaScript: ```js let cursor = null; for (let page = 1; page <= 5; page++) { const params = new URLSearchParams({ hashtag: "smallbusiness" }); if (cursor) params.set("cursor", cursor); const res = await fetch(`https://api.lurkapi.com/v1/tiktok/search/hashtag?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.videos); cursor = data.cursor; if (!cursor) break; // last page } ``` ## Caching and freshness Responses are cached for up to 10 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `tiktok_hashtag_videos` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's tiktok_hashtag_videos with hashtag "smallbusiness" and summarize what you find. ``` Claude calls `tiktok_hashtag_videos` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "hashtag": "smallbusiness" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Comment replies](https://lurkapi.com/docs/tiktok/comment-replies.md) · Next: [Song](https://lurkapi.com/docs/tiktok/song.md) --- # Song > A TikTok sound: title, artist, album, length, cover, audio URL and how many videos use it. - **Request:** `GET https://api.lurkapi.com/v1/tiktok/song` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `tiktok_song` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 1 hour - **Try it live:** https://lurkapi.com/docs/tiktok/song#try (no signup) - **Platform:** [TikTok](https://lurkapi.com/docs/tiktok.md) (tiktok.com) - **Web page:** https://lurkapi.com/docs/tiktok/song ## When to use this Use it to size up a sound before you build content on it: `videoCount` is how many posts use it. `original: true` means a creator's original sound rather than a released track, and `is_commerce_music` is TikTok's flag for sounds cleared for commercial use (the ones business accounts can pick). Get the id from any video's `music.id`, or pass the sound's tiktok.com/music/… URL. Unknown and removed sounds return 404 `not_found`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `clipId` | string | yes | | | The sound's id, e.g. `7433619007506761744`, or its URL (`https://www.tiktok.com/music/…-`). Every video carries it as `music.id`. | `7433619007506761744` | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/tiktok/song?clipId=7433619007506761744" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ clipId: "7433619007506761744", }); const res = await fetch(`https://api.lurkapi.com/v1/tiktok/song?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/tiktok/song", params={ "clipId": "7433619007506761744", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 99955, "credits_charged": 1, "id": "7433619007506761744", "url": "https://www.tiktok.com/music/Morning-Brew-Jazz-7433619007506761744", "title": "Morning Brew Jazz", "authorName": "NonLeo", "album": "Morning Brew Jazz", "original": false, "duration": 265, "cover": "https://p16-common.tiktokcdn-us.com/tos-alisg-v-2774/oAJSAyArAD0EpAPixgA…", "playUrl": "https://sf16.tiktokcdn-us.com/obj/tos-alisg-ve-2774/ocKlBfzUWiBoMThA1QzwiAapIOsMBFDSsAAEcS", "videoCount": 31300, "isCopyrighted": false, "is_commerce_music": true, "author": null } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `id` | `string` | Sound id. | | `url` | `string` | The sound's page on tiktok.com. | | `title` | `string` | Sound title, e.g. `original sound` or the track name. | | `authorName` | `string`, nullable | Artist, or the creator's display name for original sounds. | | `album` | `string`, nullable | Album of a released track; null for original sounds and when TikTok doesn't say. | | `original` | `boolean` | An original sound a creator made, rather than a released track. | | `duration` | `number`, nullable | Length in seconds (for tracks, the clip TikTok offers); null if unknown. | | `cover` | `string`, nullable | Cover art URL (may expire after a few days); null if TikTok has none. | | `playUrl` | `string`, nullable | Audio URL (may expire after a few hours); null if TikTok has none. | | `videoCount` | `number` | Posts using this sound. | | `isCopyrighted` | `boolean` | TikTok marks the sound as copyrighted. | | `is_commerce_music` | `boolean` | TikTok's commercial-use flag: cleared for business accounts and ads. | | `author` | `object`, nullable | The TikTok account behind the sound: its creator for original sounds, the artist's account for tracks; null if none. | | `author.id` | `string` | Numeric user id. | | `author.uniqueId` | `string` | Username (handle), without @. | | `author.nickname` | `string` | Display name. | | `author.secUid` | `string` | TikTok's long, stable user id. | | `author.avatarThumb` | `string`, nullable | Small avatar URL (expires after a few days); null if TikTok has none. | ## Caching and freshness Responses are cached for up to 1 hour, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `tiktok_song` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's tiktok_song with clipId "7433619007506761744" and summarize what you find. ``` Claude calls `tiktok_song` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "clipId": "7433619007506761744" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Hashtag videos](https://lurkapi.com/docs/tiktok/hashtag-videos.md) · Next: [Song videos](https://lurkapi.com/docs/tiktok/song-videos.md) --- # Song videos > Videos that use a sound, 30 per page, with stats and creators. - **Request:** `GET https://api.lurkapi.com/v1/tiktok/song/videos` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `tiktok_song_videos` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 10 minutes - **Try it live:** https://lurkapi.com/docs/tiktok/song-videos#try (no signup) - **Platform:** [TikTok](https://lurkapi.com/docs/tiktok.md) (tiktok.com) - **Web page:** https://lurkapi.com/docs/tiktok/song-videos ## When to use this Use it to see how a sound is being used: which creators and brands post with it and how their posts perform. Videos come in the order tiktok.com shows them on the sound's page, roughly most viewed first. **Pagination.** Up to 30 videos per page. Pass the response's `cursor` back as `cursor` (with the same `clipId`) for more; `has_more` is false at the end. TikTok only lists posts it still shows, so older sounds can list far fewer videos than their `videoCount` in song. Unknown sounds return 404 `not_found`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `clipId` | string | yes | | | The sound's id, e.g. `7433619007506761744`, or its URL (`https://www.tiktok.com/music/…-`). Every video carries it as `music.id`. | `7433619007506761744` | | `cursor` | string | no | | | The `cursor` from the previous response, to get the next page. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/tiktok/song/videos?clipId=7433619007506761744" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ clipId: "7433619007506761744", }); const res = await fetch(`https://api.lurkapi.com/v1/tiktok/song/videos?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.videos); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/tiktok/song/videos", params={ "clipId": "7433619007506761744", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["videos"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 99954, "credits_charged": 1, "videos": [ { "id": "7667417340195179790", "url": "https://www.tiktok.com/@mmonsemanriquez12/video/7667417340195179790", "desc": "tips for an effortess look ✨#makeup #skincare #elevateyourstyle #lipcombos #makeuphacks ", "createTime": 1785209738, "textLanguage": "en", "locationCreated": null, "isAd": false, "author": { "id": "6662197815143792645", "uniqueId": "mmonsemanriquez12", "nickname": "monse manriquez", "secUid": "MS4wLjABAAAAZCZG-3NAJ0PhmJBaPhudX2yRRolX53yV8dpw8xBawx_xk2f2v8Gs1-o4kyyKWCu8", "verified": false, "privateAccount": false, "avatarThumb": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/9423a52…", "avatarLarger": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/9423a52…" }, "stats": { "playCount": 2300000, "diggCount": 385500, "commentCount": 776, "shareCount": 7183, "collectCount": 85411, "repostCount": 0 }, "video": { "duration": 137, "width": 720, "height": 1280, "definition": "720p", "cover": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/osAHD46o6…", "dynamicCover": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/oQREUHg8K…", "playUrl": "https://www.tiktok.com/aweme/v1/play/?faid=1988&file_id=8e7356ae9d204b99…", "expires_at": 1790947782 }, "music": { "id": "7433619007506761744", "title": "Morning Brew Jazz", "authorName": "NonLeo", "original": false, "duration": 265, "playUrl": "https://sf16.tiktokcdn-us.com/obj/tos-alisg-ve-2774/ocKlBfzUWiBoMThA1QzwiAapIOsMBFDSsAAEcS", "cover": "https://p16-common.tiktokcdn-us.com/tos-alisg-v-2774/oAJSAyArAD0EpAPixgA…" }, "hashtags": [ "makeup", "skincare", "elevateyourstyle", "lipcombos", "makeuphacks" ], "mentions": [], "images": [] }, { "id": "7656489401085594894", "url": "https://www.tiktok.com/@b_saylor/video/7656489401085594894", "desc": "I wish I figured this out years ago #lifehack #shoppinghack #foldinghacks #foldingtips ", "createTime": 1782665384, "textLanguage": "en", "locationCreated": null, "isAd": false, "author": { "id": "6779736886967534597", "uniqueId": "b_saylor", "nickname": "Brooke Saylor", "secUid": "MS4wLjABAAAA0COfdEy_d30614L4iune3vYU-tiP4AOA4rc0Zw9h-2LyiumQfYxkGALg-_9dbc5m", "verified": false, "privateAccount": false, "avatarThumb": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/6f46133…", "avatarLarger": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/6f46133…" }, "stats": { "playCount": 2400000, "diggCount": 244000, "commentCount": 943, "shareCount": 49200, "collectCount": 87373, "repostCount": 0 }, "video": { "duration": 32, "width": 720, "height": 1280, "definition": "720p", "cover": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/oEFIkIUDA…", "dynamicCover": "https://p19-common-sign.tiktokcdn-us.com/tos-useast5-p-0068-tx/owC3Up0nG…", "playUrl": "https://www.tiktok.com/aweme/v1/play/?faid=1988&file_id=efcdb7f90d1641f2…", "expires_at": 1790947677 }, "music": { "id": "7433619007506761744", "title": "Morning Brew Jazz", "authorName": "NonLeo", "original": false, "duration": 265, "playUrl": "https://sf16.tiktokcdn-us.com/obj/tos-alisg-ve-2774/ocKlBfzUWiBoMThA1QzwiAapIOsMBFDSsAAEcS", "cover": "https://p16-common.tiktokcdn-us.com/tos-alisg-v-2774/oAJSAyArAD0EpAPixgA…" }, "hashtags": [ "lifehack", "shoppinghack", "foldinghacks", "foldingtips" ], "mentions": [], "images": [] } ], "has_more": true, "cursor": "30" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `videos` | `object[]` | Posts, in the order tiktok.com shows them (not newest first). | | `videos[].id` | `string` | Video id, e.g. `7671325437229845774`. | | `videos[].url` | `string` | The post on tiktok.com. | | `videos[].desc` | `string` | Caption, hashtags included. | | `videos[].createTime` | `number` | Posted at, Unix seconds. | | `videos[].textLanguage` | `string`, nullable | Caption language as TikTok detects it, e.g. `en`; null if unknown. | | `videos[].locationCreated` | `string`, nullable | Country the post was made in, as a 2-letter code; null if unknown. | | `videos[].isAd` | `boolean` | A paid ad (Spark Ad or promoted post). | | `videos[].author` | `object` | Who posted it. | | `videos[].author.id` | `string` | Numeric user id. | | `videos[].author.uniqueId` | `string` | Username (handle), without @. | | `videos[].author.nickname` | `string` | Display name. | | `videos[].author.secUid` | `string` | TikTok's long, stable user id; other tools ask for it. | | `videos[].author.verified` | `boolean` | Has the verified badge. | | `videos[].author.privateAccount` | `boolean` | The account is private. | | `videos[].author.avatarThumb` | `string`, nullable | Small avatar URL (expires after a few days); null if TikTok has none. | | `videos[].author.avatarLarger` | `string`, nullable | Large avatar URL (expires after a few days); null if TikTok has none. | | `videos[].stats` | `object` | Engagement when fetched. Exact numbers, not rounded. | | `videos[].stats.playCount` | `number` | Views. | | `videos[].stats.diggCount` | `number` | Likes. | | `videos[].stats.commentCount` | `number` | Comments. | | `videos[].stats.shareCount` | `number` | Shares. | | `videos[].stats.collectCount` | `number`, nullable | Saves (favorites); null where TikTok doesn't show it. | | `videos[].stats.repostCount` | `number`, nullable | Reposts; null where TikTok doesn't show it. | | `videos[].video` | `object` | The video file. For photo posts see `images`. | | `videos[].video.duration` | `number` | Length in seconds; 0 for photo posts. | | `videos[].video.width` | `number` | Width in pixels; 0 for photo posts. | | `videos[].video.height` | `number` | Height in pixels; 0 for photo posts. | | `videos[].video.definition` | `string`, nullable | Quality of the default rendition, e.g. `720p`. | | `videos[].video.cover` | `string`, nullable | Cover image URL (expires after a few days); null if TikTok has none. | | `videos[].video.dynamicCover` | `string`, nullable | Animated cover URL (expires after a few days); null if TikTok has none. | | `videos[].video.playUrl` | `string`, nullable | MP4 URL that plays without cookies (h264, best quality). It redirects to TikTok's CDN and expires at `expires_at`; null if TikTok has none. | | `videos[].video.expires_at` | `number`, nullable | When `playUrl` stops working, Unix seconds; null if unknown. | | `videos[].music` | `object`, nullable | The sound; null if the post has none. | | `videos[].music.id` | `string` | Sound id; pass it to song endpoints. | | `videos[].music.title` | `string` | Sound title, e.g. `original sound`. | | `videos[].music.authorName` | `string`, nullable | Artist, or the creator for original sounds. | | `videos[].music.original` | `boolean` | An original sound made with this post rather than a track. | | `videos[].music.duration` | `number`, nullable | Length in seconds. | | `videos[].music.playUrl` | `string`, nullable | Audio URL (expires after a few hours); null if TikTok has none. | | `videos[].music.cover` | `string`, nullable | Sound cover image URL; null if TikTok has none. | | `videos[].hashtags` | `string[]` | Hashtags in the caption, without #. | | `videos[].mentions` | `string[]` | Usernames @mentioned in the caption. | | `videos[].images` | `object[]` | The slides of a photo post, in order. Empty for videos. | | `videos[].images[].url` | `string` | Image URL (expires after a few days). | | `videos[].images[].width` | `number` | Width in pixels. | | `videos[].images[].height` | `number` | Height in pixels. | | `has_more` | `boolean` | There are more posts after this page. | | `cursor` | `string`, nullable | Pass as `cursor` for the next page. null on the last page. | ## Pagination Pass the response's `cursor` back as `cursor`, with the other parameters unchanged, for the next page. It's `null` on the last page. Each page costs 1 credit. The first five pages, in JavaScript: ```js let cursor = null; for (let page = 1; page <= 5; page++) { const params = new URLSearchParams({ clipId: "7433619007506761744" }); if (cursor) params.set("cursor", cursor); const res = await fetch(`https://api.lurkapi.com/v1/tiktok/song/videos?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.videos); cursor = data.cursor; if (!cursor) break; // last page } ``` ## Caching and freshness Responses are cached for up to 10 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `tiktok_song_videos` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's tiktok_song_videos with clipId "7433619007506761744" and summarize what you find. ``` Claude calls `tiktok_song_videos` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "clipId": "7433619007506761744" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Song](https://lurkapi.com/docs/tiktok/song.md) · Next: [Search TikTok Ad Library](https://lurkapi.com/docs/tiktok/ad-library-search.md) --- # Search TikTok Ad Library > Ads shown in the EU, the UK, Switzerland and Turkey that match a keyword or an advertiser, 12 per page. - **Request:** `GET https://api.lurkapi.com/v1/tiktok/ad-library/search` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `tiktok_ad_library_search` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 1 hour - **Try it live:** https://lurkapi.com/docs/tiktok/ad-library-search#try (no signup) - **Platform:** [TikTok](https://lurkapi.com/docs/tiktok.md) (tiktok.com) - **Web page:** https://lurkapi.com/docs/tiktok/ad-library-search ## When to use this Use it to see what a brand or a category advertises on TikTok: each ad with its advertiser, caption, video or images, when it ran and roughly how many people saw it. This is TikTok's Ad Library under the EU Digital Services Act, so it only has ads shown in the EU/EEA, Switzerland, the UK and Turkey. Ads shown only in the US or elsewhere aren't in it. Search by `query`, a keyword matched against ad text and advertiser names (so resellers show up too), or by `advertiser_name` for one advertiser's ads, spelled as the library shows it (e.g. `NIKE Retail B.V.`, as in a result's `advertiser_name`). Results are ads shown in the last 30 days, most relevant first. `region` narrows them to one country; an ad that ran in several countries appears under each. **Pagination.** 12 ads per page. Pass the response's `cursor` back as `cursor` (with the same params) for more; `has_more` is false at the end. For keyword searches `total` stops counting at 5,000. An `advertiser_name` the library doesn't know returns 404 `not_found`, with close matches in the message. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `query` | string | no | | | Keyword, e.g. `running shoes`. Pass this or `advertiser_name`. | `running shoes` | | `advertiser_name` | string | no | | | One advertiser's name as the library shows it, e.g. `NIKE Retail B.V.` Pass this or `query`. | | | `region` | string | no | `all` | `all`, `AT`, `BE`, `BG`, `CH`, `CY`, `CZ`, `DE`, `DK`, `EE`, `ES`, `FI`, `FR`, `GB`, `GR`, `HR`, `HU`, `IE`, `IS`, `IT`, `LI`, `LT`, `LU`, `LV`, `MT`, `NL`, `NO`, `PL`, `PT`, `RO`, `SE`, `SI`, `SK`, `TR` | Where the ads were shown: a 2-letter country code (DE, FR, GB, …) of the 33 countries the library covers, or `all`. | `DE` | | `cursor` | string | no | | | The `cursor` from the previous response, to get the next page. Keep every other param the same. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/tiktok/ad-library/search?query=running+shoes®ion=DE" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ query: "running shoes", region: "DE", }); const res = await fetch(`https://api.lurkapi.com/v1/tiktok/ad-library/search?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.ads); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/tiktok/ad-library/search", params={ "query": "running shoes", "region": "DE", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["ads"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999988, "credits_charged": 1, "ads": [ { "id": "1876549962700817", "url": "https://library.tiktok.com/ads/detail/?ad_id=1876549962700817", "advertiser_name": "扬州哲鸟网络科技有限公司", "title": "Breathable Running Shoes for Men", "category": "Apparel & Accessories", "first_shown": 1789603200, "last_shown": 1789603200, "estimated_audience": "0-1K", "videos": [ { "url": "https://library.tiktok.com/api/v1/cdn/1790776192/video/aHR0cHM6Ly92MTZtL…", "cover": "https://p16-common-sign.tiktokcdn.com/tos-alisg-p-0051c001-sg/owrWfpAWeA…" } ], "image_urls": [ "https://p16-common-sign.tiktokcdn.com/tos-alisg-p-0051c001-sg/owrWfpAWeA…" ] }, { "id": "1876549961564434", "url": "https://library.tiktok.com/ads/detail/?ad_id=1876549961564434", "advertiser_name": "扬州哲鸟网络科技有限公司", "title": "Breathable Running Shoes for Men", "category": "Apparel & Accessories", "first_shown": 1789603200, "last_shown": 1789603200, "estimated_audience": "0-1K", "videos": [ { "url": "https://library.tiktok.com/api/v1/cdn/1790776192/video/aHR0cHM6Ly92MTZtL…", "cover": "https://p16-common-sign.tiktokcdn.com/tos-alisg-p-0051c001-sg/ooNNDEglIq…" } ], "image_urls": [ "https://p16-common-sign.tiktokcdn.com/tos-alisg-p-0051c001-sg/ooNNDEglIq…" ] } ], "total": 5000, "has_more": true, "cursor": "eyJsYXN0X3NvcnQiOlszNy43Njk1NywxNzg5NjAzMjAwMDAwXSwibmV4dF9jdXJzb3IiOjEyfQ==" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `ads` | `object[]` | Ads, most relevant first. | | `ads[].id` | `string` | Ad id. Pass it as `ad_id` to TikTok Ad Library ad for targeting and reach. | | `ads[].url` | `string` | The ad on library.tiktok.com. | | `ads[].advertiser_name` | `string`, nullable | The advertiser as registered with TikTok; null for the few ads published without one. | | `ads[].title` | `string`, nullable | The ad's caption text; null if it has none. | | `ads[].category` | `string`, nullable | TikTok's category for the ad, e.g. `Apparel & Accessories` or `Apps`; null if none. | | `ads[].first_shown` | `number` | First day the ad was shown in the library's countries, Unix seconds (midnight UTC). | | `ads[].last_shown` | `number` | Latest day it was shown, Unix seconds (midnight UTC). Yesterday or today for ads still running. | | `ads[].estimated_audience` | `string`, nullable | TikTok's estimate of unique users who saw the ad at least once, as a range such as `10K-100K`; null if not given. | | `ads[].videos` | `object[]` | The ad's videos, usually one. Empty for image ads. | | `ads[].videos[].url` | `string` | MP4 URL. It redirects to TikTok's CDN and expires about 6 hours after we fetched the ad. | | `ads[].videos[].cover` | `string`, nullable | Cover image URL (expires about 6 hours after we fetched the ad); null if none. | | `ads[].image_urls` | `string[]` | The ad's images, in order. Empty for most video ads. | | `total` | `number` | Ads matching the search in the last 30 days. Keyword searches stop counting at 5,000. | | `has_more` | `boolean` | There are more ads after this page. | | `cursor` | `string`, nullable | Pass as `cursor` for the next page. null on the last page. | ## Pagination Pass the response's `cursor` back as `cursor`, with the other parameters unchanged, for the next page. It's `null` on the last page. Each page costs 1 credit. The first five pages, in JavaScript: ```js let cursor = null; for (let page = 1; page <= 5; page++) { const params = new URLSearchParams({ query: "running shoes", region: "DE" }); if (cursor) params.set("cursor", cursor); const res = await fetch(`https://api.lurkapi.com/v1/tiktok/ad-library/search?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.ads); cursor = data.cursor; if (!cursor) break; // last page } ``` ## Caching and freshness Responses are cached for up to 1 hour, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `tiktok_ad_library_search` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's tiktok_ad_library_search with query "running shoes", region "DE" and summarize what you find. ``` Claude calls `tiktok_ad_library_search` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "query": "running shoes", "region": "DE" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Song videos](https://lurkapi.com/docs/tiktok/song-videos.md) · Next: [TikTok Ad Library ad](https://lurkapi.com/docs/tiktok/ad-library-ad.md) --- # TikTok Ad Library ad > One ad from TikTok's EU Ad Library: creative, landing page, advertiser, who paid, targeting and reach by country, age and gender. - **Request:** `GET https://api.lurkapi.com/v1/tiktok/ad-library/ad` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `tiktok_ad_library_ad` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 3 hours - **Try it live:** https://lurkapi.com/docs/tiktok/ad-library-ad#try (no signup) - **Platform:** [TikTok](https://lurkapi.com/docs/tiktok.md) (tiktok.com) - **Web page:** https://lurkapi.com/docs/tiktok/ad-library-ad ## When to use this Use it to see how an ad was targeted and whom it reached: the countries, ages, genders and interests the advertiser chose, TikTok's estimate of how many users matched, and unique reach per country (split by age and gender for ads shown since June 2026). You also get the landing page, call to action, objective, who paid for the ad and the advertiser's TikTok account. Get `ad_id` from TikTok Ad Library search, or paste the ad's library.tiktok.com URL. Like the search, it only covers ads shown in the EU/EEA, Switzerland, the UK and Turkey. Unknown ids return 404 `not_found`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `ad_id` | string | yes | | | The ad's `id` from TikTok Ad Library search, e.g. `1876319908813409`, or its library.tiktok.com/ads/detail/?ad_id=… URL. | `1876319908813409` | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/tiktok/ad-library/ad?ad_id=1876319908813409" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ ad_id: "1876319908813409", }); const res = await fetch(`https://api.lurkapi.com/v1/tiktok/ad-library/ad?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.videos); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/tiktok/ad-library/ad", params={ "ad_id": "1876319908813409", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["videos"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999986, "credits_charged": 1, "id": "1876319908813409", "url": "https://library.tiktok.com/ads/detail/?ad_id=1876319908813409", "advertiser_name": "NIKE Retail B.V.", "title": null, "category": "Apparel & Accessories", "first_shown": 1789344000, "last_shown": 1790640000, "estimated_audience": "500K-600K", "videos": [ { "url": "https://library.tiktok.com/api/v1/cdn/1790776218/video/aHR0cHM6Ly92MTZtL…", "cover": "https://p16-common-sign.tiktokcdn.com/tos-useast2a-p-0037-euttp/oAgCkf5X…" } ], "image_urls": [], "external_url": "https://www.nike.com/fr/w/air-rift-chaussures-56wtbzy7ok?cp=72924050721_brs_CR9S9EUTR0WV", "call_to_action": "Shop now", "objective": "Traffic", "advertiser": { "name": "NIKE Retail B.V.", "adv_biz_ids": "6876453864464188162", "registry_location": "Netherlands", "sponsor": "INITIATIVE MEDIA B.V.", "tiktok_user": null }, "targeting": { "countries": [ "FR" ], "age": [ "18-24", "25-34" ], "gender": [ "female" ], "audience_size": "3.9M-4.8M", "interests": [ "Apparel & Accessories", "Sports" ], "video_interactions": [ "Beauty & Style" ], "creator_interactions": [ "Fashion & Beauty" ], "languages": [], "operating_systems": [] }, "reach_by_country": [ { "country": "FR", "users": "540K", "by_age_gender": [ { "age": "18-24", "gender": "female", "users": "302K" }, { "age": "25-34", "gender": "female", "users": "254K" } ] } ] } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `id` | `string` | Ad id. Pass it as `ad_id` to TikTok Ad Library ad for targeting and reach. | | `url` | `string` | The ad on library.tiktok.com. | | `advertiser_name` | `string`, nullable | The advertiser as registered with TikTok; null for the few ads published without one. | | `title` | `string`, nullable | The ad's caption text; null if it has none. | | `category` | `string`, nullable | TikTok's category for the ad, e.g. `Apparel & Accessories` or `Apps`; null if none. | | `first_shown` | `number` | First day the ad was shown in the library's countries, Unix seconds (midnight UTC). | | `last_shown` | `number` | Latest day it was shown, Unix seconds (midnight UTC). Yesterday or today for ads still running. | | `estimated_audience` | `string`, nullable | TikTok's estimate of unique users who saw the ad at least once, as a range such as `10K-100K`; null if not given. | | `videos` | `object[]` | The ad's videos, usually one. Empty for image ads. | | `videos[].url` | `string` | MP4 URL. It redirects to TikTok's CDN and expires about 6 hours after we fetched the ad. | | `videos[].cover` | `string`, nullable | Cover image URL (expires about 6 hours after we fetched the ad); null if none. | | `image_urls` | `string[]` | The ad's images, in order. Empty for most video ads. | | `external_url` | `string`, nullable | Where the ad sends people: the landing page, or the app store page for app ads; null if none. | | `call_to_action` | `string`, nullable | The button text, e.g. `Shop now`; null if none. | | `objective` | `string`, nullable | The advertiser's campaign objective, e.g. `Traffic` or `Sales`; null if not given. | | `advertiser` | `object` | Who ran the ad. | | `advertiser.name` | `string`, nullable | The advertiser as registered with TikTok. | | `advertiser.adv_biz_ids` | `string`, nullable | TikTok's business id for the advertiser, e.g. `6876453864464188162`. | | `advertiser.registry_location` | `string`, nullable | Country the advertiser is registered in, e.g. `Germany`. | | `advertiser.sponsor` | `string`, nullable | Who paid for the ad ("Ad paid for by"), often an agency or the advertiser itself; null if not given. | | `advertiser.tiktok_user` | `object`, nullable | The advertiser's TikTok account; null when the ad didn't run from one. | | `advertiser.tiktok_user.username` | `string` | TikTok username (handle), without @. Pass it to the profile endpoint. | | `advertiser.tiktok_user.display_name` | `string`, nullable | Display name. | | `advertiser.tiktok_user.followers` | `string`, nullable | Followers as TikTok rounds them in the library, e.g. `319.1K`. | | `advertiser.tiktok_user.avatar_url` | `string`, nullable | Avatar URL (expires after a few days). | | `advertiser.tiktok_user.url` | `string` | The account on tiktok.com. | | `targeting` | `object` | The main ways the advertiser chose to reach people. TikTok may also have shown the ad for other reasons. | | `targeting.countries` | `string[]` | Countries targeted, as 2-letter codes. | | `targeting.age` | `string[]` | Age ranges targeted, e.g. `18-24`, `25-34` or `55+` (in any of the countries). | | `targeting.gender` | `string[]` | Genders targeted: `female`, `male` and/or `unknown`. | | `targeting.audience_size` | `string`, nullable | TikTok's estimate of how many users matched the targeting, e.g. `3.9M-4.8M`. | | `targeting.interests` | `string[]` | Interest categories targeted, e.g. `Sports`. Empty if none. | | `targeting.video_interactions` | `string[]` | Categories of videos the audience interacted with. Empty if not used. | | `targeting.creator_interactions` | `string[]` | Categories of creators the audience followed or watched. Empty if not used. | | `targeting.languages` | `string[]` | Languages targeted. Empty means any. | | `targeting.operating_systems` | `string[]` | Operating systems targeted, e.g. `ANDROID`. Empty means any. | | `reach_by_country` | `object[]` | Unique users reached, per country. | | `reach_by_country[].country` | `string` | 2-letter country code. | | `reach_by_country[].users` | `string` | Unique users reached there, e.g. `540K` or a range like `0-1K`. | | `reach_by_country[].by_age_gender` | `object[]` | Reach split by age and gender. TikTok only publishes it for ads shown since June 2026; empty otherwise. | | `reach_by_country[].by_age_gender[].age` | `string` | Age range, e.g. `18-24` or `55+`. | | `reach_by_country[].by_age_gender[].gender` | `string` | `female`, `male` or `unknown`. | | `reach_by_country[].by_age_gender[].users` | `string` | Unique users reached in this group, e.g. `302K` or `0-1K`. | ## Caching and freshness Responses are cached for up to 3 hours, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `tiktok_ad_library_ad` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's tiktok_ad_library_ad with ad_id "1876319908813409" and summarize what you find. ``` Claude calls `tiktok_ad_library_ad` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "ad_id": "1876319908813409" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Search TikTok Ad Library](https://lurkapi.com/docs/tiktok/ad-library-search.md) · Next: [Live status](https://lurkapi.com/docs/tiktok/live.md) --- # Live status > Whether a public creator is live, with the current room title, viewers and start time. - **Request:** `GET https://api.lurkapi.com/v1/tiktok/user/live` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `tiktok_live` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 30 seconds - **Try it live:** https://lurkapi.com/docs/tiktok/live#try (no signup) - **Platform:** [TikTok](https://lurkapi.com/docs/tiktok.md) (tiktok.com) - **Web page:** https://lurkapi.com/docs/tiktok/live ## When to use this Check a creator before a campaign or live collaboration. A room id on the profile can belong to a finished stream, so this endpoint verifies the room's status. Only an active stream returns room details; offline accounts return `is_live: false` and null room fields. Refreshes every 30 seconds. Unknown accounts return 404 `not_found`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `handle` | string | yes | | | TikTok username, with or without @ (e.g. `nike`), or the profile URL. | `tiktok` | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/tiktok/user/live?handle=tiktok" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ handle: "tiktok", }); const res = await fetch(`https://api.lurkapi.com/v1/tiktok/user/live?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/tiktok/user/live", params={ "handle": "tiktok", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999957, "credits_charged": 1, "user": { "id": "107955", "uniqueId": "tiktok", "nickname": "TikTok", "avatarThumb": "https://p16-common-sign.tiktokcdn-us.com/tos-useast5-avt-0068-tx/ba67b11…", "verified": true }, "is_live": false, "room_id": null, "title": null, "started_at": null, "viewer_count": null, "total_joins": null } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `user` | `object` | The creator. | | `user.id` | `string` | Numeric user id. | | `user.uniqueId` | `string` | Username (handle), without @. | | `user.nickname` | `string` | Display name. | | `user.avatarThumb` | `string`, nullable | Small avatar URL (expires after a few days); null if TikTok has none. | | `user.verified` | `boolean` | Has the verified badge. | | `is_live` | `boolean` | TikTok reports an active livestream (room status 2). | | `room_id` | `string`, nullable | Current live room id; null when offline. | | `title` | `string`, nullable | Current stream title; null when offline or unavailable. | | `started_at` | `number`, nullable | Current stream start time, Unix seconds; null when offline or unknown. | | `viewer_count` | `number`, nullable | Current concurrent viewers; null when offline or unknown. | | `total_joins` | `number`, nullable | Total joins since this stream started; null when offline or unknown. | ## Caching and freshness Responses are cached for up to 30 seconds, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `tiktok_live` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's tiktok_live with handle "tiktok" and summarize what you find. ``` Claude calls `tiktok_live` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "handle": "tiktok" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [TikTok Ad Library ad](https://lurkapi.com/docs/tiktok/ad-library-ad.md) · Next: [Public stories](https://lurkapi.com/docs/tiktok/stories.md) --- # Public stories > A creator's active public stories, with captions, media and expiration times. - **Request:** `GET https://api.lurkapi.com/v1/tiktok/user/stories` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `tiktok_stories` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 1 minute - **Try it live:** https://lurkapi.com/docs/tiktok/stories#try (no signup) - **Platform:** [TikTok](https://lurkapi.com/docs/tiktok.md) (tiktok.com) - **Web page:** https://lurkapi.com/docs/tiktok/stories ## When to use this See the temporary content a public creator shares alongside regular posts. Each story includes the usual post fields and its `expires_at` time. Returns up to 20 stories, oldest first; `has_more` warns if TikTok reports additional stories beyond this page. This endpoint does not paginate. No active stories returns an empty list. Only stories TikTok exposes to logged-out visitors are available; private stories are excluded. Unknown accounts return 404 `not_found`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `handle` | string | yes | | | TikTok username, with or without @ (e.g. `nike`), or the profile URL. | `nono95118` | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/tiktok/user/stories?handle=nono95118" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ handle: "nono95118", }); const res = await fetch(`https://api.lurkapi.com/v1/tiktok/user/stories?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.stories); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/tiktok/user/stories", params={ "handle": "nono95118", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["stories"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999956, "credits_charged": 1, "user": { "id": "7322921924349690912", "uniqueId": "nono95118", "nickname": "n.o4m'🫆", "avatarThumb": "https://p19-common-sign.tiktokcdn-us.com/tos-maliva-avt-0068/2eefcd56d9d…", "verified": false }, "stories": [ { "id": "7691030223965506849", "url": "https://www.tiktok.com/@nono95118/video/7691030223965506849", "desc": "", "createTime": 1790707521, "textLanguage": "un", "locationCreated": null, "isAd": false, "author": { "id": "7322921924349690912", "uniqueId": "nono95118", "nickname": "n.o4m'🫆", "secUid": "MS4wLjABAAAAm4M5uYboGN9cOhADsXDQe_AIUrXvvalv-7c1nV7AUhaiinF_Sk75ktDi8SRKZjfv", "verified": false, "privateAccount": false, "avatarThumb": "https://p16-common-sign.tiktokcdn-us.com/tos-maliva-avt-0068/2eefcd56d9d…", "avatarLarger": "https://p16-common-sign.tiktokcdn-us.com/tos-maliva-avt-0068/2eefcd56d9d…" }, "stats": { "playCount": 69, "diggCount": 2, "commentCount": 0, "shareCount": 0, "collectCount": 0, "repostCount": 0 }, "video": { "duration": 24, "width": 576, "height": 1024, "definition": "540p", "cover": "https://p16-common-sign.tiktokcdn-us.com/tos-useast2a-p-0037-euttp/oY5z1…", "dynamicCover": "https://p16-common-sign.tiktokcdn-us.com/tos-useast2a-p-0037-euttp/osB5Q…", "playUrl": "https://www.tiktok.com/aweme/v1/play/?faid=1988&file_id=a11cc2e02b9b4450…", "expires_at": 1790952038 }, "music": { "id": "6716518209370982401", "title": "Hymn for the Weekend", "authorName": "Coldplay", "original": false, "duration": 60, "playUrl": "https://sf19.tiktokcdn-us.com/obj/tos-alisg-ve-2774/7b32380bc8be43028a40d287cc6e4489", "cover": "https://p16-common.tiktokcdn-us.com/tos-alisg-v-2774/9ed8c123c47c4eac897…" }, "hashtags": [], "mentions": [], "images": [], "expires_at": 1790793921 }, { "id": "7691227743198940448", "url": "https://www.tiktok.com/@nono95118/video/7691227743198940448", "desc": "", "createTime": 1790753511, "textLanguage": "un", "locationCreated": null, "isAd": false, "author": { "id": "7322921924349690912", "uniqueId": "nono95118", "nickname": "n.o4m'🫆", "secUid": "MS4wLjABAAAAm4M5uYboGN9cOhADsXDQe_AIUrXvvalv-7c1nV7AUhaiinF_Sk75ktDi8SRKZjfv", "verified": false, "privateAccount": false, "avatarThumb": "https://p16-common-sign.tiktokcdn-us.com/tos-maliva-avt-0068/2eefcd56d9d…", "avatarLarger": "https://p16-common-sign.tiktokcdn-us.com/tos-maliva-avt-0068/2eefcd56d9d…" }, "stats": { "playCount": 36, "diggCount": 0, "commentCount": 0, "shareCount": 0, "collectCount": 0, "repostCount": 0 }, "video": { "duration": 10, "width": 576, "height": 1024, "definition": "540p", "cover": "https://p16-common-sign.tiktokcdn-us.com/tos-useast2a-p-0037-euttp/okrAZ…", "dynamicCover": "https://p16-common-sign.tiktokcdn-us.com/tos-useast2a-p-0037-euttp/oUfdd…", "playUrl": "https://www.tiktok.com/aweme/v1/play/?faid=1988&file_id=667c80c4e20f4a8d…", "expires_at": 1790952024 }, "music": { "id": "7683828213017316118", "title": "sunet original", "authorName": "Kashif", "original": true, "duration": 14, "playUrl": "https://v16-webapp-prime.us.tiktok.com/video/tos/no1a/tos-no1a-v-2370-no…", "cover": "https://p16-common-sign.tiktokcdn-us.com/tos-maliva-avt-0068/bb05916d5c1…" }, "hashtags": [], "mentions": [], "images": [], "expires_at": 1790839911 } ], "total_count": 3, "has_more": false } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `user` | `object` | The creator. | | `user.id` | `string` | Numeric user id. | | `user.uniqueId` | `string` | Username (handle), without @. | | `user.nickname` | `string` | Display name. | | `user.avatarThumb` | `string`, nullable | Small avatar URL (expires after a few days); null if TikTok has none. | | `user.verified` | `boolean` | Has the verified badge. | | `stories` | `object[]` | Active public stories, oldest first, up to 20. | | `stories[].id` | `string` | Video id, e.g. `7671325437229845774`. | | `stories[].url` | `string` | The post on tiktok.com. | | `stories[].desc` | `string` | Caption, hashtags included. | | `stories[].createTime` | `number` | Posted at, Unix seconds. | | `stories[].textLanguage` | `string`, nullable | Caption language as TikTok detects it, e.g. `en`; null if unknown. | | `stories[].locationCreated` | `string`, nullable | Country the post was made in, as a 2-letter code; null if unknown. | | `stories[].isAd` | `boolean` | A paid ad (Spark Ad or promoted post). | | `stories[].author` | `object` | Who posted it. | | `stories[].author.id` | `string` | Numeric user id. | | `stories[].author.uniqueId` | `string` | Username (handle), without @. | | `stories[].author.nickname` | `string` | Display name. | | `stories[].author.secUid` | `string` | TikTok's long, stable user id; other tools ask for it. | | `stories[].author.verified` | `boolean` | Has the verified badge. | | `stories[].author.privateAccount` | `boolean` | The account is private. | | `stories[].author.avatarThumb` | `string`, nullable | Small avatar URL (expires after a few days); null if TikTok has none. | | `stories[].author.avatarLarger` | `string`, nullable | Large avatar URL (expires after a few days); null if TikTok has none. | | `stories[].stats` | `object` | Engagement when fetched. Exact numbers, not rounded. | | `stories[].stats.playCount` | `number` | Views. | | `stories[].stats.diggCount` | `number` | Likes. | | `stories[].stats.commentCount` | `number` | Comments. | | `stories[].stats.shareCount` | `number` | Shares. | | `stories[].stats.collectCount` | `number`, nullable | Saves (favorites); null where TikTok doesn't show it. | | `stories[].stats.repostCount` | `number`, nullable | Reposts; null where TikTok doesn't show it. | | `stories[].video` | `object` | The video file. For photo posts see `images`. | | `stories[].video.duration` | `number` | Length in seconds; 0 for photo posts. | | `stories[].video.width` | `number` | Width in pixels; 0 for photo posts. | | `stories[].video.height` | `number` | Height in pixels; 0 for photo posts. | | `stories[].video.definition` | `string`, nullable | Quality of the default rendition, e.g. `720p`. | | `stories[].video.cover` | `string`, nullable | Cover image URL (expires after a few days); null if TikTok has none. | | `stories[].video.dynamicCover` | `string`, nullable | Animated cover URL (expires after a few days); null if TikTok has none. | | `stories[].video.playUrl` | `string`, nullable | MP4 URL that plays without cookies (h264, best quality). It redirects to TikTok's CDN and expires at `expires_at`; null if TikTok has none. | | `stories[].video.expires_at` | `number`, nullable | When `playUrl` stops working, Unix seconds; null if unknown. | | `stories[].music` | `object`, nullable | The sound; null if the post has none. | | `stories[].music.id` | `string` | Sound id; pass it to song endpoints. | | `stories[].music.title` | `string` | Sound title, e.g. `original sound`. | | `stories[].music.authorName` | `string`, nullable | Artist, or the creator for original sounds. | | `stories[].music.original` | `boolean` | An original sound made with this post rather than a track. | | `stories[].music.duration` | `number`, nullable | Length in seconds. | | `stories[].music.playUrl` | `string`, nullable | Audio URL (expires after a few hours); null if TikTok has none. | | `stories[].music.cover` | `string`, nullable | Sound cover image URL; null if TikTok has none. | | `stories[].hashtags` | `string[]` | Hashtags in the caption, without #. | | `stories[].mentions` | `string[]` | Usernames @mentioned in the caption. | | `stories[].images` | `object[]` | The slides of a photo post, in order. Empty for videos. | | `stories[].images[].url` | `string` | Image URL (expires after a few days). | | `stories[].images[].width` | `number` | Width in pixels. | | `stories[].images[].height` | `number` | Height in pixels. | | `stories[].expires_at` | `number` | When this story expires, Unix seconds. | | `total_count` | `number` | Total stories TikTok reported when fetched; may exceed the returned page. | | `has_more` | `boolean` | TikTok reports stories beyond this page; this endpoint does not paginate. | ## Caching and freshness Responses are cached for up to 1 minute, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `tiktok_stories` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's tiktok_stories with handle "nono95118" and summarize what you find. ``` Claude calls `tiktok_stories` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "handle": "nono95118" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Live status](https://lurkapi.com/docs/tiktok/live.md) · Next: [TikTok Shop product](https://lurkapi.com/docs/tiktok/product.md) --- # TikTok Shop product > A US TikTok Shop product's price, images, seller, variants, sales and review totals. - **Request:** `GET https://api.lurkapi.com/v1/tiktok/product` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `tiktok_product` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 5 minutes - **Try it live:** https://lurkapi.com/docs/tiktok/product#try (no signup) - **Platform:** [TikTok](https://lurkapi.com/docs/tiktok.md) (tiktok.com) - **Web page:** https://lurkapi.com/docs/tiktok/product ## When to use this Inspect a product sold through the US TikTok Shop: public product copy, price, variant inventory, seller and category information. Prices are decimal strings in the reported currency and reflect the logged-out US storefront; checkout discounts and shipping can differ. Sales and reviews are lifetime totals, not a time series. This endpoint returns a single product, not Shop search or review pages. Other storefront countries are not supported. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `url` | string | yes | | | US product URL on shop.tiktok.com/us/pdp/… or its numeric product id. | `https://shop.tiktok.com/us/pdp/biodance-collagen-mask-pdrn-sea-kelp-overnight-hydration/1732049122233848586` | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/tiktok/product?url=https%3A%2F%2Fshop.tiktok.com%2Fus%2Fpdp%2Fbiodance-collagen-mask-pdrn-sea-kelp-overnight-hydration%2F1732049122233848586" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ url: "https://shop.tiktok.com/us/pdp/biodance-collagen-mask-pdrn-sea-kelp-overnight-hydration/1732049122233848586", }); const res = await fetch(`https://api.lurkapi.com/v1/tiktok/product?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.images); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/tiktok/product", params={ "url": "https://shop.tiktok.com/us/pdp/biodance-collagen-mask-pdrn-sea-kelp-overnight-hydration/1732049122233848586", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["images"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999955, "credits_charged": 1, "id": "1732049122233848586", "url": "https://shop.tiktok.com/us/pdp/biodance-collagen-mask-pdrn-sea-kelp-over…", "region": "US", "title": "[Biodance Official] Maskholic Gift Bundle | Collagen, PDRN, Ceranol, Sea…", "description": "This product is a bundle made up of individual items.\nIf the products ar…", "sold_count": 26233, "price": { "currency": "USD", "amount": "69.92", "original_amount": "131.96", "display": "$69.92" }, "images": [ { "url": "https://p16-oec-general.ttcdn-us.com/tos-alisg-i-aphluv4xwc-sg/84912eaae…", "width": 800, "height": 800 }, { "url": "https://p16-oec-general-useast5.ttcdn-us.com/tos-useast5-i-omjb5zjo8w-tx…", "width": 800, "height": 800 } ], "categories": [ { "id": "601450", "name": "Beauty & Personal Care" }, { "id": "848776", "name": "Skincare" }, { "id": "601611", "name": "Skin Care Kits" } ], "specifications": [ { "name": "Brand", "value": "Biodance" }, { "name": "Application area", "value": "Face" }, { "name": "Manufacturer", "value": "Biodance" }, { "name": "Volume", "value": "34g * 4ea" }, { "name": "Scent", "value": "Unscented" }, { "name": "Region of origin", "value": "Korea" }, { "name": "Benefits", "value": "Pore Tightening,Pore Treatment,Anti Aging,Firming,Hydrating,Moisturizing" }, { "name": "Age group", "value": "Adults" }, { "name": "Gender", "value": "Female,Male,Unisex" }, { "name": "Feature", "value": "Alcohol free" }, { "name": "Benefit", "value": "Hydration,Firming or lifting,Anti wrinkle,Pore control,Moisturizing" }, { "name": "Skin type", "value": "All,Sensitive,Combination,Dry,Oily" }, { "name": "Product form", "value": "Sheet" }, { "name": "Contains alcohol or aerosol", "value": "Contains neither" }, { "name": "Contains batteries or cells?", "value": "None" }, { "name": "Dangerous goods or hazardous materials", "value": "No" }, { "name": "Flammable liquid", "value": "No" }, { "name": "Ingredients", "value": "Water,Galactomyces Ferment Filtrate,Glycerin,Acrylates Copolymer,Niacina…" } ], "seller": { "id": "7495992422530583306", "name": "Biodance Store US", "url": "https://shop.tiktok.com/us/store/biodance-store-us/7495992422530583306", "avatar": "https://p19-oec-general-useast5.ttcdn-us.com/tos-useast5-i-omjb5zjo8w-tx…", "rating": 4.8, "follower_count": 192838, "product_count": 39 }, "rating": 4.8, "review_count": 1536, "variants": [ { "id": "1732469803725394698", "stock": 260, "price": "69.92", "currency": "USD", "properties": [ { "name": "Specifications", "value": "Default" } ] } ] } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `id` | `string` | TikTok Shop product id. | | `url` | `string` | Canonical US storefront product URL. | | `region` | `"US"` | Storefront country; this endpoint supports US products. | | `title` | `string` | Product name. | | `description` | `string` | Public product description as plain text. | | `sold_count` | `number`, nullable | Lifetime items sold globally, excluding returns; null when unavailable. | | `price` | `object` | Public logged-out storefront price. | | `price.currency` | `string`, nullable | Currency code, e.g. USD; null when unavailable. | | `price.amount` | `string`, nullable | Lowest variant sale price as a decimal string; null when unavailable. | | `price.original_amount` | `string`, nullable | Lowest variant original price before discount, as a decimal string; null when unavailable. | | `price.display` | `string`, nullable | Storefront formatted sale price; null when unavailable. | | `images` | `object[]` | Product images in gallery order. | | `images[].url` | `string` | Product image URL. | | `images[].width` | `number`, nullable | Image width in pixels; null when unknown. | | `images[].height` | `number`, nullable | Image height in pixels; null when unknown. | | `categories` | `object[]` | Category hierarchy, broadest first. | | `categories[].id` | `string` | Category id. | | `categories[].name` | `string` | Category name. | | `specifications` | `object[]` | Product specifications, such as brand and ingredients. | | `specifications[].name` | `string` | Specification name. | | `specifications[].value` | `string` | Specification value. | | `seller` | `object` | The seller. | | `seller.id` | `string` | Seller id. | | `seller.name` | `string`, nullable | Seller storefront name; null when unavailable. | | `seller.url` | `string`, nullable | Seller storefront URL; null when unavailable. | | `seller.avatar` | `string`, nullable | Seller avatar URL; null when unavailable. | | `seller.rating` | `number`, nullable | Seller rating out of 5; null when unavailable. | | `seller.follower_count` | `number`, nullable | Store followers; null when unavailable. | | `seller.product_count` | `number`, nullable | Products in the store; null when unavailable. | | `rating` | `number`, nullable | Product rating out of 5; null when unavailable. | | `review_count` | `number`, nullable | Total product reviews; null when unavailable. | | `variants` | `object[]` | Publicly listed product variants. | | `variants[].id` | `string` | SKU id. | | `variants[].stock` | `number`, nullable | Stock TikTok exposes for this variant; null when unavailable. | | `variants[].price` | `string`, nullable | Variant sale price as a decimal string; null when unavailable. | | `variants[].currency` | `string`, nullable | Variant currency code; null when unavailable. | | `variants[].properties` | `object[]` | Variant options, such as size or color. | | `variants[].properties[].name` | `string` | Option name. | | `variants[].properties[].value` | `string` | Option value. | ## Caching and freshness Responses are cached for up to 5 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `tiktok_product` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's tiktok_product with url "https://shop.tiktok.com/us/pdp/biodance-collagen-mask-pdrn-sea-kelp-overnight-hydration/1732049122233848586" and summarize what you find. ``` Claude calls `tiktok_product` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "url": "https://shop.tiktok.com/us/pdp/biodance-collagen-mask-pdrn-sea-kelp-overnight-hydration/1732049122233848586" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Public stories](https://lurkapi.com/docs/tiktok/stories.md) · Next: [Search Google advertisers](https://lurkapi.com/docs/google/search-advertisers.md) --- # Google API > Ads running on Google Search, YouTube, Maps, Play and Shopping, from Google's Ads Transparency Center. - **Platform:** [Google](https://lurkapi.com/docs/google.md) (adstransparency.google.com) - **Web page:** https://lurkapi.com/docs/google ## Overview Find any advertiser in Google's public Ads Transparency Center, list every ad they run, and pull the detail of one ad. Great for competitor research on Google Ads and YouTube. Source: adstransparency.google.com. ## Try it live Pick an endpoint and run it: real data, no signup, 5 free requests a day. Live playground: https://lurkapi.com/docs/google#try ## Endpoints - [Search Google advertisers](https://lurkapi.com/docs/google/search-advertisers.md): Find an advertiser's Google Ads id by brand name, with how many ads they run, plus matching websites. (GET /v1/google/adLibrary/advertisers/search · 1 credit) - [Google ads by company](https://lurkapi.com/docs/google/company-ads.md): Every ad an advertiser or website runs on Google Search, YouTube, Maps, Play and Shopping, from the Ads Transparency Center. (GET /v1/google/company/ads · 1 credit) - [Google ad details](https://lurkapi.com/docs/google/ad.md): One ad from Google's Ads Transparency Center: every version of the creative, where it ran and, for political ads, spend and targeting. (GET /v1/google/ad · 1 credit) ## Call it Every endpoint is a `GET` with your key in the `x-api-key` header. For example, [Search Google advertisers](https://lurkapi.com/docs/google/search-advertisers.md): curl: ```bash curl "https://api.lurkapi.com/v1/google/adLibrary/advertisers/search?query=nike" \ -H "x-api-key: YOUR_API_KEY" ``` [Get a free API key](https://lurkapi.com/login?next=/dashboard) ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), each endpoint is a tool Claude can call: | Tool | What it does | | --- | --- | | `google_search_advertisers` | [Search Google advertisers](https://lurkapi.com/docs/google/search-advertisers.md): Find an advertiser's Google Ads id by brand name, with how many ads they run, plus matching websites. | | `google_company_ads` | [Google ads by company](https://lurkapi.com/docs/google/company-ads.md): Every ad an advertiser or website runs on Google Search, YouTube, Maps, Play and Shopping, from the Ads Transparency Center. | | `google_ad` | [Google ad details](https://lurkapi.com/docs/google/ad.md): One ad from Google's Ads Transparency Center: every version of the creative, where it ran and, for political ads, spend and targeting. | --- # Search Google advertisers > Find an advertiser's Google Ads id by brand name, with how many ads they run, plus matching websites. - **Request:** `GET https://api.lurkapi.com/v1/google/adLibrary/advertisers/search` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `google_search_advertisers` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 1 day - **Try it live:** https://lurkapi.com/docs/google/search-advertisers#try (no signup) - **Platform:** [Google](https://lurkapi.com/docs/google.md) (adstransparency.google.com) - **Web page:** https://lurkapi.com/docs/google/search-advertisers ## When to use this Use it to turn a brand name into the `advertiser_id` that company ads (google_company_ads) needs, or to find the website to pass as `domain`. Big brands have one advertiser per country or agency; pick by `region` and `number_of_ads_estimate`. The same suggestions as the search box on adstransparency.google.com: up to 10 advertisers and 10 websites, no pagination. `region` (default US) counts only ads shown there, so the list changes by region; pass ALL for every region. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `query` | string | yes | | | Brand or advertiser name, e.g. `nike`. | `nike` | | `region` | string | no | `US` | | Count only ads shown in this country: a 2-letter code (US, GB, DE…), or ALL for anywhere. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/google/adLibrary/advertisers/search?query=nike" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ query: "nike", }); const res = await fetch(`https://api.lurkapi.com/v1/google/adLibrary/advertisers/search?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.advertisers); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/google/adLibrary/advertisers/search", params={ "query": "nike", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["advertisers"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999999, "credits_charged": 1, "advertisers": [ { "name": "Nike, Inc.", "advertiser_id": "AR16735076323512287233", "region": "US", "number_of_ads_estimate": 8000, "number_of_ads_range": { "min": 8000, "max": 9000 }, "is_verified": true }, { "name": "NIKE SRL", "advertiser_id": "AR17365672681860497409", "region": "IT", "number_of_ads_estimate": 1, "number_of_ads_range": { "min": 1, "max": 1 }, "is_verified": true } ], "websites": [ { "domain": "nike.com" }, { "domain": "nike.cl" } ] } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `advertisers` | `object[]` | Matching advertisers, best match first. | | `advertisers[].name` | `string` | The advertiser's verified name, e.g. `Nike, Inc.`. | | `advertisers[].advertiser_id` | `string` | The advertiser's id (`AR…`). Pass it as `advertiser_id` to company ads. | | `advertisers[].region` | `string`, nullable | Country the advertiser is based in, as a 2-letter code. | | `advertisers[].number_of_ads_estimate` | `number`, nullable | How many ads they have in `region`: the lower end of Google's range (exact for small advertisers). | | `advertisers[].number_of_ads_range` | `object`, nullable | The ad count as the range Google shows, e.g. 9000–10000; null when it gives none. | | `advertisers[].number_of_ads_range.min` | `number` | Lower bound of the ad count. | | `advertisers[].number_of_ads_range.max` | `number` | Upper bound of the ad count. | | `advertisers[].is_verified` | `boolean` | false when Google marks the advertiser as not yet verified. | | `websites` | `object[]` | Websites matching the query. | | `websites[].domain` | `string` | A website with ads, e.g. `nike.com`. Pass it as `domain` to company ads. | ## Caching and freshness Responses are cached for up to 1 day, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `google_search_advertisers` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's google_search_advertisers with query "nike" and summarize what you find. ``` Claude calls `google_search_advertisers` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "query": "nike" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [TikTok Shop product](https://lurkapi.com/docs/tiktok/product.md) · Next: [Google ads by company](https://lurkapi.com/docs/google/company-ads.md) --- # Google ads by company > Every ad an advertiser or website runs on Google Search, YouTube, Maps, Play and Shopping, from the Ads Transparency Center. - **Request:** `GET https://api.lurkapi.com/v1/google/company/ads` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `google_company_ads` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 12 hours - **Try it live:** https://lurkapi.com/docs/google/company-ads#try (no signup; free tool: [Google Ads Transparency search](https://lurkapi.com/free-tools/google-ads-transparency)) - **Platform:** [Google](https://lurkapi.com/docs/google.md) (adstransparency.google.com) - **Web page:** https://lurkapi.com/docs/google/company-ads ## When to use this Use it to study a competitor's Google and YouTube ads: formats, run dates and how long each ad has run. Pass `domain` (e.g. nike.com, every advertiser promoting it) or `advertiser_id` from search advertisers (google_search_advertisers). Filter by region, platform, format and date range. `topic=political` lists election ads with their spend and impressions, and needs a `region`. For an ad's text, landing page and per-country stats, pass its ids to ad details (google_ad). **Pagination.** Up to 100 ads per page. Pass `cursor` back with the same filters for the next page; it's null on the last page. Date ranges that start more than about a year back come back empty. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `domain` | string | no | | | The advertiser's website, e.g. `nike.com`. Required unless you pass `advertiser_id`. | `nike.com` | | `advertiser_id` | string | no | | | The advertiser's id, e.g. `AR16735076323512287233` (Nike, Inc.). Required unless you pass `domain`. | | | `topic` | string | no | `all` | `all`, `political` | `political` lists only election ads (with spend and impressions); it needs a `region`. | | | `region` | string | no | `ALL` | | Only ads shown in this country: a 2-letter code (US, GB, DE…), or ALL for anywhere. | `US` | | `start_date` | string | no | | | Only ads shown on or after this date (YYYY-MM-DD). | | | `end_date` | string | no | | | Only ads shown on or before this date (YYYY-MM-DD). | | | `platform` | string | no | `all` | `all`, `google_maps`, `google_play`, `google_search`, `google_shopping`, `youtube` | Only ads shown on this Google product. | | | `format` | string | no | `all` | `all`, `text`, `image`, `video` | Only ads in this format. | | | `cursor` | string | no | | | The `cursor` from the previous response, for the next page. Keep every other param the same. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/google/company/ads?domain=nike.com®ion=US" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ domain: "nike.com", region: "US", }); const res = await fetch(`https://api.lurkapi.com/v1/google/company/ads?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.ads); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/google/company/ads", params={ "domain": "nike.com", "region": "US", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["ads"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999998, "credits_charged": 1, "ads": [ { "advertiserId": "AR16735076323512287233", "creativeId": "CR12839449348618059777", "format": "text", "adUrl": "https://adstransparency.google.com/advertiser/AR16735076323512287233/cre…", "advertiserName": "Nike, Inc.", "domain": "nike.com", "imageUrl": "https://tpc.googlesyndication.com/archive/simgad/24906591946562615", "previewUrl": null, "firstShown": "2023-11-16T22:25:33.000Z", "lastShown": "2026-09-30T13:22:17.000Z", "daysShown": 902, "overallImpressions": null, "spend": null }, { "advertiserId": "AR06365026152670560257", "creativeId": "CR12984801659073855489", "format": "video", "adUrl": "https://adstransparency.google.com/advertiser/AR06365026152670560257/cre…", "advertiserName": "WPP MEDIA MANAGEMENT", "domain": "nike.com", "imageUrl": null, "previewUrl": "https://displayads-formats.googleusercontent.com/ads/preview/content.js?…", "firstShown": "2022-01-28T21:52:24.000Z", "lastShown": "2026-09-30T11:52:56.000Z", "daysShown": 1656, "overallImpressions": null, "spend": null } ], "cursor": "CgoAP7zmjTmT5kTPEhDDfxH9IpWWGVjQMnUAAAAAGgn8+Gjk+IV3Ks4=", "number_of_ads_estimate": 8000, "number_of_ads_range": { "min": 8000, "max": 9000 } } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `ads` | `object[]` | The ads in Google's order: roughly most recently shown first. | | `ads[].advertiserId` | `string` | The advertiser's id (`AR…`). Pass it as `advertiser_id` to company ads. | | `ads[].creativeId` | `string` | The ad's id (`CR…`). Pass it with `advertiser_id` as `creative_id` to ad details. | | `ads[].format` | `string`, nullable | text, image or video. Text ads are often archived as a picture of the ad. | | `ads[].adUrl` | `string` | The ad's page on the Ads Transparency Center. | | `ads[].advertiserName` | `string`, nullable | The advertiser's verified name, e.g. `Nike, Inc.`. | | `ads[].domain` | `string`, nullable | The website this ad matched, on domain searches; null on advertiser searches. | | `ads[].imageUrl` | `string`, nullable | Picture of the ad, for image ads and text ads Google archived as an image. null when the ad only has a live preview (`previewUrl`). | | `ads[].previewUrl` | `string`, nullable | Google's live preview of the ad: a script the Transparency Center renders. null when there's an `imageUrl`. | | `ads[].firstShown` | `string`, nullable | When the ad was first shown, ISO 8601 (UTC). | | `ads[].lastShown` | `string`, nullable | When the ad was last shown, ISO 8601 (UTC). Today's date for ads still running. | | `ads[].daysShown` | `number`, nullable | How many days the ad has been shown. A long run usually means the ad works. | | `ads[].overallImpressions` | `object`, nullable | Impressions as the range Google reports. Political ads only; null otherwise. | | `ads[].overallImpressions.min` | `number` | Lower bound of impressions. | | `ads[].overallImpressions.max` | `number` | Upper bound of impressions. | | `ads[].spend` | `object`, nullable | Spend as the range Google reports. Political ads only; null otherwise. | | `ads[].spend.currency` | `string` | Currency code, e.g. USD. | | `ads[].spend.lower` | `number` | Lower bound of the spend, in `currency`. | | `ads[].spend.upper` | `number` | Upper bound of the spend, in `currency`. | | `cursor` | `string`, nullable | Pass as `cursor` for the next page. null on the last page. | | `number_of_ads_estimate` | `number`, nullable | How many ads match, as Google estimates it: the lower end of its range, or the exact count when it gives one. | | `number_of_ads_range` | `object`, nullable | The ad count as the range Google shows (e.g. 7000–8000); null when it gives none. | | `number_of_ads_range.min` | `number` | Lower bound of the ad count. | | `number_of_ads_range.max` | `number` | Upper bound of the ad count. | ## Pagination Pass the response's `cursor` back as `cursor`, with the other parameters unchanged, for the next page. It's `null` on the last page. Each page costs 1 credit. The first five pages, in JavaScript: ```js let cursor = null; for (let page = 1; page <= 5; page++) { const params = new URLSearchParams({ domain: "nike.com", region: "US" }); if (cursor) params.set("cursor", cursor); const res = await fetch(`https://api.lurkapi.com/v1/google/company/ads?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.ads); cursor = data.cursor; if (!cursor) break; // last page } ``` ## Caching and freshness Responses are cached for up to 12 hours, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `google_company_ads` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's google_company_ads with domain "nike.com", region "US" and summarize what you find. ``` Claude calls `google_company_ads` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "domain": "nike.com", "region": "US" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Search Google advertisers](https://lurkapi.com/docs/google/search-advertisers.md) · Next: [Google ad details](https://lurkapi.com/docs/google/ad.md) --- # Google ad details > One ad from Google's Ads Transparency Center: every version of the creative, where it ran and, for political ads, spend and targeting. - **Request:** `GET https://api.lurkapi.com/v1/google/ad` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `google_ad` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 12 hours - **Try it live:** https://lurkapi.com/docs/google/ad#try (no signup) - **Platform:** [Google](https://lurkapi.com/docs/google.md) (adstransparency.google.com) - **Web page:** https://lurkapi.com/docs/google/ad ## When to use this Use it after company ads (google_company_ads) for one ad's versions, regions and landing page. Pass the ad's Transparency Center link as `url`, or `advertiser_id` + `creative_id`. Each version has an `imageUrl` when Google archived it as a picture, else a `previewUrl`. For text and video ads we also read one live preview for the headline, description, landing page and YouTube video; image-only text stays null (we don't OCR). Impressions, spend and targeting are only published for political ads. Returns 404 `not_found` if the ad doesn't exist. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `url` | string | no | | | The ad's Transparency Center link, e.g. `https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR11071274990039990273`. Required unless you pass both ids. | `https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR11071274990039990273?region=US` | | `advertiser_id` | string | no | | | The advertiser's id (`AR…`), with `creative_id`, instead of `url`. | | | `creative_id` | string | no | | | The ad's id (`CR…`), with `advertiser_id`, instead of `url`. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/google/ad?url=https%3A%2F%2Fadstransparency.google.com%2Fadvertiser%2FAR16735076323512287233%2Fcreative%2FCR11071274990039990273%3Fregion%3DUS" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ url: "https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR11071274990039990273?region=US", }); const res = await fetch(`https://api.lurkapi.com/v1/google/ad?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.creativeRegions); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/google/ad", params={ "url": "https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR11071274990039990273?region=US", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["creativeRegions"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999997, "credits_charged": 1, "advertiserId": "AR16735076323512287233", "creativeId": "CR11071274990039990273", "advertiserName": "Nike, Inc.", "format": "video", "adUrl": "https://adstransparency.google.com/advertiser/AR16735076323512287233/cre…", "firstShown": null, "lastShown": "2026-09-30T09:58:06.000Z", "daysShown": null, "overallImpressions": null, "spend": null, "targeting": null, "creativeRegions": [ { "regionCode": "US", "regionName": "United States" } ], "regionStats": [ { "regionCode": "US", "regionName": "United States", "firstShown": null, "lastShown": "2026-09-30", "impressions": null, "platformImpressions": [] } ], "variations": [ { "destinationUrl": "https://www.nike.com/retail/", "headline": "Engineered for Max Airflow", "description": "Maximum breathability meets ultra-light comfort in new Nike Aero-FIT styles.", "allText": null, "imageUrl": null, "previewUrl": "https://displayads-formats.googleusercontent.com/ads/preview/content.js?…", "videoId": "RZ1MLoOdWcc" }, { "destinationUrl": null, "headline": null, "description": null, "allText": null, "imageUrl": null, "previewUrl": "https://displayads-formats.googleusercontent.com/ads/preview/content.js?…", "videoId": null } ] } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `advertiserId` | `string` | The advertiser's id (`AR…`). Pass it as `advertiser_id` to company ads. | | `creativeId` | `string` | The ad's id (`CR…`). | | `advertiserName` | `string`, nullable | Who paid for the ad (Google's "paid for by" name), e.g. `Nike, Inc.`; it can differ from the advertiser account, e.g. for agencies. Often null for political ads. | | `format` | `string`, nullable | text, image or video. Text ads are often archived as a picture of the ad. | | `adUrl` | `string` | The ad's page on the Ads Transparency Center. | | `firstShown` | `string`, nullable | When the ad was first shown, ISO 8601 (UTC). Google often leaves it out here; company ads always has it. | | `lastShown` | `string`, nullable | When the ad was last shown, ISO 8601 (UTC). Today's date for ads still running. | | `daysShown` | `number`, nullable | How many days the ad has been shown. A long run usually means the ad works. null when Google leaves it out. | | `overallImpressions` | `object`, nullable | Impressions as the range Google reports. Political ads only; null otherwise. | | `overallImpressions.min` | `number` | Lower bound of impressions. | | `overallImpressions.max` | `number` | Upper bound of impressions. | | `spend` | `object`, nullable | Spend as the range Google reports. Political ads only; null otherwise. | | `spend.currency` | `string` | Currency code, e.g. USD. | | `spend.lower` | `number` | Lower bound of the spend, in `currency`. | | `spend.upper` | `number` | Upper bound of the spend, in `currency`. | | `targeting` | `object`, nullable | Who the ad targeted. Political ads only; null otherwise. Empty lists can still mean targeting was used: Google doesn't always list the groups. | | `targeting.age` | `object` | Age groups (0-17, 18-24, 25-34, 35-44, 45-54, 55-64, 65+, unknown) targeting. | | `targeting.age.included` | `string[]` | Age groups (0-17, 18-24, 25-34, 35-44, 45-54, 55-64, 65+, unknown) the ad targeted. | | `targeting.age.excluded` | `string[]` | Age groups (0-17, 18-24, 25-34, 35-44, 45-54, 55-64, 65+, unknown) the ad excluded. | | `targeting.gender` | `object` | Genders (male, female, unknown) targeting. | | `targeting.gender.included` | `string[]` | Genders (male, female, unknown) the ad targeted. | | `targeting.gender.excluded` | `string[]` | Genders (male, female, unknown) the ad excluded. | | `targeting.location` | `object` | Location targeting. | | `targeting.location.included` | `object[]` | Locations the ad targeted. | | `targeting.location.included[].criterionId` | `string` | Google's location id, e.g. 2840 (United States) or 1015229 (a city). | | `targeting.location.included[].name` | `string`, nullable | The country's name for country-level ids; null for cities and regions, which we don't resolve. | | `targeting.location.included[].fullName` | `string`, nullable | Same as `name` for countries; null for cities and regions. | | `targeting.location.included[].countryCodes` | `string[]` | The country code for country-level ids, e.g. ["US"]; empty otherwise. | | `targeting.location.excluded` | `object[]` | Locations the ad excluded. | | `targeting.location.excluded[].criterionId` | `string` | Google's location id, e.g. 2840 (United States) or 1015229 (a city). | | `targeting.location.excluded[].name` | `string`, nullable | The country's name for country-level ids; null for cities and regions, which we don't resolve. | | `targeting.location.excluded[].fullName` | `string`, nullable | Same as `name` for countries; null for cities and regions. | | `targeting.location.excluded[].countryCodes` | `string[]` | The country code for country-level ids, e.g. ["US"]; empty otherwise. | | `creativeRegions` | `object[]` | Countries where the ad was shown. | | `creativeRegions[].regionCode` | `string` | 2-letter country code, e.g. US. | | `creativeRegions[].regionName` | `string` | Country name in English. | | `regionStats` | `object[]` | Per-country delivery: dates and, for political ads, impressions. | | `regionStats[].regionCode` | `string` | 2-letter country code, e.g. US. | | `regionStats[].regionName` | `string` | Country name in English. | | `regionStats[].firstShown` | `string`, nullable | First day shown in this country, YYYY-MM-DD; often null outside political ads. | | `regionStats[].lastShown` | `string`, nullable | Last day shown in this country, YYYY-MM-DD. | | `regionStats[].impressions` | `object`, nullable | Impressions in this country. Political ads only; null otherwise. | | `regionStats[].impressions.min` | `number` | Lower bound of impressions. | | `regionStats[].impressions.max` | `number` | Upper bound of impressions. | | `regionStats[].platformImpressions` | `object[]` | Impressions per Google product in this country, when Google reports them; usually empty. | | `regionStats[].platformImpressions[].platform` | `string`, nullable | The Google product. | | `regionStats[].platformImpressions[].min` | `number`, nullable | Lower bound of impressions there. | | `regionStats[].platformImpressions[].max` | `number`, nullable | Upper bound of impressions there. | | `variations` | `object[]` | Every version of the creative Google shows. We read the live preview of the first previewed version of text and video ads only. | | `variations[].destinationUrl` | `string`, nullable | Landing page, from the version's live preview. For search ads it can be just the display URL (e.g. `nike.com`); null when we didn't read one. | | `variations[].headline` | `string`, nullable | Headline, from the live preview; null for image-only versions and ones we didn't read. | | `variations[].description` | `string`, nullable | Description text, from the live preview; null for image-only versions and ones we didn't read. | | `variations[].allText` | `string`, nullable | All text on the ad. Always null for now: reading text off archived images needs OCR, which we don't run. | | `variations[].imageUrl` | `string`, nullable | Picture of this version; null when it only has a live preview. | | `variations[].previewUrl` | `string`, nullable | Google's live preview of this version (a script); null when there's an `imageUrl`. | | `variations[].videoId` | `string`, nullable | The YouTube video id of a video ad, from the live preview (youtube.com/watch?v=); null otherwise. | ## Caching and freshness Responses are cached for up to 12 hours, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `google_ad` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's google_ad with url "https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR11071274990039990273?region=US" and summarize what you find. ``` Claude calls `google_ad` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "url": "https://adstransparency.google.com/advertiser/AR16735076323512287233/creative/CR11071274990039990273?region=US" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Google ads by company](https://lurkapi.com/docs/google/company-ads.md) · Next: [Profile](https://lurkapi.com/docs/bluesky/profile.md) --- # Bluesky API > Profiles, posts and full threads from any public Bluesky account. - **Platform:** [Bluesky](https://lurkapi.com/docs/bluesky.md) (bsky.app) - **Web page:** https://lurkapi.com/docs/bluesky ## Overview Read Bluesky the way bsky.app shows it: profiles with exact follower counts, an account's posts and reposts, and a post with its replies. Useful for brand and creator monitoring now that journalists, researchers and brands post there. Data comes from Bluesky's official public API, with its own field names; images, video and link cards are flattened into plain fields. Source: bsky.app. ## Try it live Pick an endpoint and run it: real data, no signup, 5 free requests a day. Live playground: https://lurkapi.com/docs/bluesky#try ## Endpoints - [Profile](https://lurkapi.com/docs/bluesky/profile.md): An account's bio, avatar, banner and exact follower, following and post counts. (GET /v1/bluesky/profile · 1 credit) - [User posts](https://lurkapi.com/docs/bluesky/user-posts.md): An account's latest posts and reposts, newest first, with text, media and engagement counts. (GET /v1/bluesky/user/posts · 1 credit) - [Post](https://lurkapi.com/docs/bluesky/post.md): A post with its replies (three levels deep) and the posts it replies to. (GET /v1/bluesky/post · 1 credit) ## Call it Every endpoint is a `GET` with your key in the `x-api-key` header. For example, [Profile](https://lurkapi.com/docs/bluesky/profile.md): curl: ```bash curl "https://api.lurkapi.com/v1/bluesky/profile?handle=bsky.app" \ -H "x-api-key: YOUR_API_KEY" ``` [Get a free API key](https://lurkapi.com/login?next=/dashboard) ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), each endpoint is a tool Claude can call: | Tool | What it does | | --- | --- | | `bluesky_profile` | [Profile](https://lurkapi.com/docs/bluesky/profile.md): An account's bio, avatar, banner and exact follower, following and post counts. | | `bluesky_user_posts` | [User posts](https://lurkapi.com/docs/bluesky/user-posts.md): An account's latest posts and reposts, newest first, with text, media and engagement counts. | | `bluesky_post` | [Post](https://lurkapi.com/docs/bluesky/post.md): A post with its replies (three levels deep) and the posts it replies to. | --- # Profile > An account's bio, avatar, banner and exact follower, following and post counts. - **Request:** `GET https://api.lurkapi.com/v1/bluesky/profile` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `bluesky_profile` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 30 seconds - **Try it live:** https://lurkapi.com/docs/bluesky/profile#try (no signup) - **Platform:** [Bluesky](https://lurkapi.com/docs/bluesky.md) (bsky.app) - **Web page:** https://lurkapi.com/docs/bluesky/profile ## When to use this Use it to size up an account: exact followers, following and posts, plus bio, images and join date. `did` is the permanent id; the handle can change. Accounts that opted out of logged-out visibility (`!no-unauthenticated`) return 403 `not_public`. Unknown, deactivated and suspended accounts return 404 `not_found`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `handle` | string | yes | | | Handle (e.g. `bsky.app`, with or without @), a DID (`did:plc:…`) or the profile URL. A bare name like `jay` means `jay.bsky.social`. | `bsky.app` | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/bluesky/profile?handle=bsky.app" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ handle: "bsky.app", }); const res = await fetch(`https://api.lurkapi.com/v1/bluesky/profile?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/bluesky/profile", params={ "handle": "bsky.app", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999994, "credits_charged": 1, "did": "did:plc:z72i7hdynmk6r22z27h6tvur", "handle": "bsky.app", "displayName": "Bluesky", "description": "official Bluesky account (check username👆)\n\nBugs, feature requests, feedback: support@bsky.app", "url": "https://bsky.app/profile/bsky.app", "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/b…", "banner": "https://cdn.bsky.app/img/banner/plain/did:plc:z72i7hdynmk6r22z27h6tvur/b…", "followersCount": 35097048, "followsCount": 15, "postsCount": 865, "createdAt": "2023-04-12T04:53:57.057Z", "pinnedPost": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3l6oveex3ii2l", "verified": false, "hiddenFromLoggedOut": false } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `did` | `string` | The account's permanent id, e.g. `did:plc:z72i7hdynmk6r22z27h6tvur`. | | `handle` | `string` | Handle, e.g. `bsky.app`. | | `displayName` | `string`, nullable | Display name; null if not set. | | `description` | `string`, nullable | Bio text; null if not set. | | `url` | `string` | The profile on bsky.app. | | `avatar` | `string`, nullable | Avatar image URL; null if none. | | `banner` | `string`, nullable | Banner image URL; null if none. | | `followersCount` | `number` | Followers, exact. | | `followsCount` | `number` | Accounts they follow. | | `postsCount` | `number` | Posts, replies included. | | `createdAt` | `string`, nullable | When the account was created, ISO 8601. | | `pinnedPost` | `string`, nullable | `at://` URI of the pinned post; null if none. | | `verified` | `boolean` | Has Bluesky's blue verification check. | | `hiddenFromLoggedOut` | `boolean` | Always false for returned profiles. Accounts hidden from logged-out visitors return 403 `not_public`. | ## Caching and freshness Responses are cached for up to 30 seconds, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 403 | `not_public` | The platform confirmed this account or content isn't available to logged-out visitors. Use a publicly visible target. Charged when the platform was checked. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `bluesky_profile` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's bluesky_profile with handle "bsky.app" and summarize what you find. ``` Claude calls `bluesky_profile` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "handle": "bsky.app" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Google ad details](https://lurkapi.com/docs/google/ad.md) · Next: [User posts](https://lurkapi.com/docs/bluesky/user-posts.md) --- # User posts > An account's latest posts and reposts, newest first, with text, media and engagement counts. - **Request:** `GET https://api.lurkapi.com/v1/bluesky/user/posts` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `bluesky_user_posts` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 3 minutes - **Try it live:** https://lurkapi.com/docs/bluesky/user-posts#try (no signup; free tool: [Bluesky viewer](https://lurkapi.com/free-tools/bluesky-viewer)) - **Platform:** [Bluesky](https://lurkapi.com/docs/bluesky.md) (bsky.app) - **Web page:** https://lurkapi.com/docs/bluesky/user-posts ## When to use this Use it to track what an account posts and how it lands: likes, reposts, replies and quotes on every post. The default `filter` matches the Posts tab on bsky.app: posts, reposts and the account's own threads, without replies to others, with the pinned post first (`pinned: true`). Reposts have `repost: true` and the original poster as `author`. Pass `handle` or `user_id`. Accounts that opted out of logged-out visibility return 403 `not_public`, including empty feeds. Posts and reposts by hidden authors are omitted; hidden quotes are null. Visibility of the requested account is checked with a 30-second cached profile lookup. **Pagination.** Up to 50 posts per page. Pass the response's `cursor` back as `cursor` (with the same account and filter); it's null at the end. Unknown, deactivated and suspended accounts return 404 `not_found`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `handle` | string | no | | | Handle (e.g. `bsky.app`), a DID or the profile URL. Required unless you pass `user_id`. | `bsky.app` | | `user_id` | string | no | | | The account's DID, e.g. `did:plc:z72i7hdynmk6r22z27h6tvur`. Required unless you pass `handle`. | | | `filter` | string | no | `posts_and_author_threads` | `posts_and_author_threads`, `posts_with_replies`, `posts_no_replies`, `posts_with_media`, `posts_with_video` | `posts_and_author_threads` (the Posts tab), `posts_with_replies`, `posts_no_replies`, `posts_with_media` or `posts_with_video`. | | | `cursor` | string | no | | | The `cursor` from the previous response, to get the next page. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/bluesky/user/posts?handle=bsky.app" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ handle: "bsky.app", }); const res = await fetch(`https://api.lurkapi.com/v1/bluesky/user/posts?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.posts); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/bluesky/user/posts", params={ "handle": "bsky.app", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["posts"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999987, "credits_charged": 1, "posts": [ { "uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3l6oveex3ii2l", "cid": "bafyreicnt42y6vo6pfpvyro234ac4o6ijug6adwwrh7awflgrqlt4zibxq", "url": "https://bsky.app/profile/bsky.app/post/3l6oveex3ii2l", "text": "👋 Bluesky is an open social network that gives creators independence f…", "createdAt": "2024-10-17T07:06:51.491Z", "author": { "did": "did:plc:z72i7hdynmk6r22z27h6tvur", "handle": "bsky.app", "displayName": "Bluesky", "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/b…" }, "replyCount": 8621, "repostCount": 9536, "likeCount": 63723, "quoteCount": 711, "bookmarkCount": 256, "langs": [ "en" ], "images": [], "video": null, "external": null, "quote": null, "links": [], "mentions": [], "tags": [], "replyTo": null, "repost": false, "pinned": true }, { "uri": "at://did:plc:kwxpje5qog6wwndagwbssatp/app.bsky.feed.post/3mviutosqmk2t", "cid": "bafyreigyzxfb3tynf6hus4cshhzcrwr7sht4vfp2l5phbbfephvn7bcd7a", "url": "https://bsky.app/profile/avclub.com/post/3mviutosqmk2t", "text": "Less than 4 hours until the #Emmys kick off! You can keep up with all th…", "createdAt": "2026-09-14T19:47:12.655Z", "author": { "did": "did:plc:kwxpje5qog6wwndagwbssatp", "handle": "avclub.com", "displayName": "The A.V. Club", "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:kwxpje5qog6wwndagwbssatp/b…" }, "replyCount": 31, "repostCount": 37, "likeCount": 525, "quoteCount": 4, "bookmarkCount": 26, "langs": [ "en" ], "images": [ { "thumbnail": "https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:kwxpje5qog6wwndagw…", "fullsize": "https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:kwxpje5qog6wwndagwb…", "alt": "" } ], "video": null, "external": null, "quote": null, "links": [ "https://bsky.app/profile/avclub.com/feed/emmys-watch-r" ], "mentions": [ "bsky.app" ], "tags": [ "Emmys" ], "replyTo": null, "repost": true, "pinned": false } ], "cursor": "2026-08-26T14:31:49.108Z" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `posts` | `object[]` | One page of posts, newest first (the pinned post leads the first page). | | `posts[].uri` | `string` | The post's `at://` URI, its permanent id. | | `posts[].cid` | `string` | Content hash of this version of the post. | | `posts[].url` | `string` | The post on bsky.app. | | `posts[].text` | `string` | Post text. Empty for media-only posts. | | `posts[].createdAt` | `string` | When it was posted, ISO 8601. | | `posts[].author` | `object` | Who posted it. | | `posts[].author.did` | `string` | The account's permanent id, e.g. `did:plc:z72i7hdynmk6r22z27h6tvur`. | | `posts[].author.handle` | `string` | Handle, e.g. `bsky.app`. It can change; the DID doesn't. | | `posts[].author.displayName` | `string`, nullable | Display name; null if not set. | | `posts[].author.avatar` | `string`, nullable | Avatar image URL; null if none. | | `posts[].replyCount` | `number` | Replies. | | `posts[].repostCount` | `number` | Reposts. | | `posts[].likeCount` | `number` | Likes. | | `posts[].quoteCount` | `number` | Quote posts. | | `posts[].bookmarkCount` | `number` | Times saved to bookmarks. | | `posts[].langs` | `string[]` | Languages the author tagged, e.g. `en`. | | `posts[].images` | `object[]` | Attached images, in order. Empty if none. | | `posts[].images[].thumbnail` | `string` | Small image URL. | | `posts[].images[].fullsize` | `string` | Full-size image URL. | | `posts[].images[].alt` | `string` | Alt text; empty if none. | | `posts[].video` | `object`, nullable | Attached video; null if none. | | `posts[].video.playlist` | `string` | HLS playlist (.m3u8) URL. | | `posts[].video.thumbnail` | `string`, nullable | Poster image URL; null if none. | | `posts[].video.alt` | `string`, nullable | Alt text; null if none. | | `posts[].external` | `object`, nullable | Link card; null if none. | | `posts[].external.uri` | `string` | The linked page. | | `posts[].external.title` | `string` | Link card title. | | `posts[].external.description` | `string` | Link card description. | | `posts[].external.thumbnail` | `string`, nullable | Link card image URL; null if none. | | `posts[].quote` | `object`, nullable | The post this one quotes; null if none or hidden from logged-out visitors. Embedded lists, feeds and starter packs aren't returned. | | `posts[].quote.uri` | `string` | The quoted post's `at://` URI. Pass it as `url` to the post endpoint. | | `posts[].quote.url` | `string` | The quoted post on bsky.app. | | `posts[].quote.text` | `string`, nullable | Its text; null when it was deleted or its author blocks viewers. | | `posts[].quote.createdAt` | `string`, nullable | When it was posted, ISO 8601; null when unavailable. | | `posts[].quote.author` | `object`, nullable | Who posted it; null when unavailable. | | `posts[].quote.author.did` | `string` | The account's permanent id, e.g. `did:plc:z72i7hdynmk6r22z27h6tvur`. | | `posts[].quote.author.handle` | `string` | Handle, e.g. `bsky.app`. It can change; the DID doesn't. | | `posts[].quote.author.displayName` | `string`, nullable | Display name; null if not set. | | `posts[].quote.author.avatar` | `string`, nullable | Avatar image URL; null if none. | | `posts[].links` | `string[]` | Links in the text. | | `posts[].mentions` | `string[]` | Handles mentioned in the text, without @. | | `posts[].tags` | `string[]` | Hashtags in the text, without #. | | `posts[].replyTo` | `string`, nullable | `at://` URI of the post this replies to; null for top-level posts. | | `posts[].repost` | `boolean` | A repost by this account; `author` is the original poster. | | `posts[].pinned` | `boolean` | The account's pinned post, shown first on the first page. | | `cursor` | `string`, nullable | Pass as `cursor` for the next page. null on the last page. | ## Pagination Pass the response's `cursor` back as `cursor`, with the other parameters unchanged, for the next page. It's `null` on the last page. Each page costs 1 credit. The first five pages, in JavaScript: ```js let cursor = null; for (let page = 1; page <= 5; page++) { const params = new URLSearchParams({ handle: "bsky.app" }); if (cursor) params.set("cursor", cursor); const res = await fetch(`https://api.lurkapi.com/v1/bluesky/user/posts?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.posts); cursor = data.cursor; if (!cursor) break; // last page } ``` ## Caching and freshness Responses are cached for up to 3 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 403 | `not_public` | The platform confirmed this account or content isn't available to logged-out visitors. Use a publicly visible target. Charged when the platform was checked. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `bluesky_user_posts` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's bluesky_user_posts with handle "bsky.app" and summarize what you find. ``` Claude calls `bluesky_user_posts` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "handle": "bsky.app" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Profile](https://lurkapi.com/docs/bluesky/profile.md) · Next: [Post](https://lurkapi.com/docs/bluesky/post.md) --- # Post > A post with its replies (three levels deep) and the posts it replies to. - **Request:** `GET https://api.lurkapi.com/v1/bluesky/post` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `bluesky_post` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 3 minutes - **Try it live:** https://lurkapi.com/docs/bluesky/post#try (no signup) - **Platform:** [Bluesky](https://lurkapi.com/docs/bluesky.md) (bsky.app) - **Web page:** https://lurkapi.com/docs/bluesky/post ## When to use this Use it to read a conversation: the post, its replies nested under `replies`, and `parents` from the thread's first post down to the one this post answers. Replies go three levels deep; pass a reply's `uri` as `url` to read further down. Deleted posts and posts from accounts that block viewers are left out of `replies`; in `parents`, the chain stops below one. A requested post hidden from logged-out visitors returns 403 `not_public`. Hidden replies and their branches are omitted, hidden quotes are null, and the parent chain stops below a hidden author. Unknown and deleted posts return 404 `not_found`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `url` | string | yes | | | The post's URL, e.g. `https://bsky.app/profile/bsky.app/post/3l6oveex3ii2l`, or its `at://` URI. | `https://bsky.app/profile/bsky.app/post/3mtykea5kds2d` | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/bluesky/post?url=https%3A%2F%2Fbsky.app%2Fprofile%2Fbsky.app%2Fpost%2F3mtykea5kds2d" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ url: "https://bsky.app/profile/bsky.app/post/3mtykea5kds2d", }); const res = await fetch(`https://api.lurkapi.com/v1/bluesky/post?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.replies); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/bluesky/post", params={ "url": "https://bsky.app/profile/bsky.app/post/3mtykea5kds2d", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["replies"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999985, "credits_charged": 1, "post": { "uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtykea5kds2d", "cid": "bafyreiekdsq4kh7go75zsdw7echsceb6zp2zczozoqoz3kcmtezievbdtu", "url": "https://bsky.app/profile/bsky.app/post/3mtykea5kds2d", "text": "NOW PLAYING: \"Flight over Jupiter\" by @kevinmgill.bsky.social", "createdAt": "2026-08-26T14:31:49.108Z", "author": { "did": "did:plc:z72i7hdynmk6r22z27h6tvur", "handle": "bsky.app", "displayName": "Bluesky", "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/b…" }, "replyCount": 10, "repostCount": 32, "likeCount": 494, "quoteCount": 0, "bookmarkCount": 66, "langs": [ "en" ], "images": [], "video": null, "external": null, "quote": { "uri": "at://did:plc:we2butxgfrqjxqxztwznhrrv/app.bsky.feed.post/3mtw2mowtdc2q", "url": "https://bsky.app/profile/kevinmgill.bsky.social/post/3mtw2mowtdc2q", "text": "Take a relaxing flight with NASA's Juno Spacecraft as it passes over Jup…", "createdAt": "2026-08-25T14:44:53.700Z", "author": { "did": "did:plc:we2butxgfrqjxqxztwznhrrv", "handle": "kevinmgill.bsky.social", "displayName": "Kevin M. Gill", "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:we2butxgfrqjxqxztwznhrrv/b…" } }, "links": [], "mentions": [ "kevinmgill.bsky.social" ], "tags": [], "replyTo": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtwm5grjns22" }, "replies": [ { "uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtyzbvfbb222", "cid": "bafyreighwp7yizyb62fchduf2wta5xc5bubbjkn4552rvpyi4j4apunxsq", "url": "https://bsky.app/profile/bsky.app/post/3mtyzbvfbb222", "text": "NOW PLAYING: \"Tour of Western Railway BART Exhibit\" by @bart.gov", "createdAt": "2026-08-26T18:58:56.845Z", "author": { "did": "did:plc:z72i7hdynmk6r22z27h6tvur", "handle": "bsky.app", "displayName": "Bluesky", "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/b…" }, "replyCount": 9, "repostCount": 18, "likeCount": 402, "quoteCount": 2, "bookmarkCount": 32, "langs": [ "en" ], "images": [], "video": null, "external": null, "quote": { "uri": "at://did:plc:qddhk5tchy7gdyws2l4srl47/app.bsky.feed.post/3mtwudf3uok27", "url": "https://bsky.app/profile/bart.gov/post/3mtwudf3uok27", "text": "Come take the full tour of the BART A, B and C Cars on display at the We…", "createdAt": "2026-08-25T22:24:58.681Z", "author": { "did": "did:plc:qddhk5tchy7gdyws2l4srl47", "handle": "bart.gov", "displayName": "BART", "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:qddhk5tchy7gdyws2l4srl47/b…" } }, "links": [], "mentions": [ "bart.gov" ], "tags": [], "replyTo": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtykea5kds2d", "replies": [ { "uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtzbwnbvrs24", "cid": "bafyreie4usq5gutvl7drrj6rliswozwzzmcei3qy7g6wyuxyobwb4wprdy", "url": "https://bsky.app/profile/bsky.app/post/3mtzbwnbvrs24", "text": "NOW PLAYING: \"Sludgecam\" by @neorsd.org", "createdAt": "2026-08-26T21:33:42.923Z", "author": { "did": "did:plc:z72i7hdynmk6r22z27h6tvur", "handle": "bsky.app", "displayName": "Bluesky", "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/b…" }, "replyCount": 15, "repostCount": 16, "likeCount": 298, "quoteCount": 0, "bookmarkCount": 11, "langs": [ "en" ], "images": [], "video": null, "external": null, "quote": { "uri": "at://did:plc:itwimoiaj7qfal7hizju3syz/app.bsky.feed.post/3mtwwrztp622j", "url": "https://bsky.app/profile/neorsd.org/post/3mtwwrztp622j", "text": "longer videos on @bsky.app means i can finally post 10 full minutes of s…", "createdAt": "2026-08-25T23:08:57.677Z", "author": { "did": "did:plc:itwimoiaj7qfal7hizju3syz", "handle": "neorsd.org", "displayName": "NE Ohio Regional Sewer District", "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:itwimoiaj7qfal7hizju3syz/b…" } }, "links": [], "mentions": [ "neorsd.org" ], "tags": [], "replyTo": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtyzbvfbb222", "replies": [ { "uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtzmdtsaw22w", "cid": "bafyreie7xfzf65hhtseni2ppxs5q5mrjr5cyzawblhwuknvjcy2mc7kgny", "url": "https://bsky.app/profile/bsky.app/post/3mtzmdtsaw22w", "text": "NOW PLAYING: \"Taskmaster Fan Fiction (Greg's Christmas Feast)\" by @thehornesection.bsky.social", "createdAt": "2026-08-27T00:40:03.375Z", "author": { "did": "did:plc:z72i7hdynmk6r22z27h6tvur", "handle": "bsky.app", "displayName": "Bluesky", "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/b…" }, "replyCount": 9, "repostCount": 23, "likeCount": 525, "quoteCount": 3, "bookmarkCount": 55, "langs": [ "en" ], "images": [], "video": null, "external": null, "quote": { "uri": "at://did:plc:sfytqb5ksdwbhen5jorylyf6/app.bsky.feed.post/3mtwakstrcc2k", "url": "https://bsky.app/profile/thehornesection.bsky.social/post/3mtwakstrcc2k", "text": "Throwback to when Alex found some Taskmaster fan fiction about him and G…", "createdAt": "2026-08-25T16:31:13.139Z", "author": { "did": "did:plc:sfytqb5ksdwbhen5jorylyf6", "handle": "thehornesection.bsky.social", "displayName": "The Horne Section", "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:sfytqb5ksdwbhen5jorylyf6/b…" } }, "links": [], "mentions": [ "thehornesection.bsky.social" ], "tags": [], "replyTo": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtzbwnbvrs24", "replies": [] } ] }, { "uri": "at://did:plc:xwzonyjk7rpsn7v6attpxmf2/app.bsky.feed.post/3mu3j2i2m222k", "cid": "bafyreieuadtlbmfyw4hl4gtyg66eb7nkdtil5csqirktskq3v67mdgd3ui", "url": "https://bsky.app/profile/hologrid99.bsky.social/post/3mu3j2i2m222k", "text": "nice ⭐", "createdAt": "2026-08-27T18:46:27.328Z", "author": { "did": "did:plc:xwzonyjk7rpsn7v6attpxmf2", "handle": "hologrid99.bsky.social", "displayName": null, "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:xwzonyjk7rpsn7v6attpxmf2/b…" }, "replyCount": 0, "repostCount": 0, "likeCount": 0, "quoteCount": 0, "bookmarkCount": 0, "langs": [ "en" ], "images": [], "video": null, "external": null, "quote": null, "links": [], "mentions": [], "tags": [], "replyTo": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtyzbvfbb222", "replies": [] } ] }, { "uri": "at://did:plc:xh3czhp62wowuzbor5jfmklx/app.bsky.feed.post/3mtylqgnnjc25", "cid": "bafyreib35r3mx4fuk4g5aiviczcsxhwhqkjwqytcqpphpjvm7cwgbmpnce", "url": "https://bsky.app/profile/gokaired215.bsky.social/post/3mtylqgnnjc25", "text": "OK so for some reason videos over like 5 minutes don't appear on a profile for me?", "createdAt": "2026-08-26T14:56:32.321Z", "author": { "did": "did:plc:xh3czhp62wowuzbor5jfmklx", "handle": "gokaired215.bsky.social", "displayName": "G", "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:xh3czhp62wowuzbor5jfmklx/b…" }, "replyCount": 0, "repostCount": 0, "likeCount": 0, "quoteCount": 0, "bookmarkCount": 0, "langs": [ "en" ], "images": [], "video": null, "external": null, "quote": null, "links": [], "mentions": [], "tags": [], "replyTo": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtykea5kds2d", "replies": [] } ], "parents": [ { "uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtwkahfcss2p", "cid": "bafyreie76xk3wtpecmwh3s5ybju7vjv7cybe2he4l77okrmpcpr3uuwece", "url": "https://bsky.app/profile/bsky.app/post/3mtwkahfcss2p", "text": "NOW PLAYING: \"Chiitan Blooper Reel\" by @chiitan.love", "createdAt": "2026-08-25T19:24:23.007Z", "author": { "did": "did:plc:z72i7hdynmk6r22z27h6tvur", "handle": "bsky.app", "displayName": "Bluesky", "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/b…" }, "replyCount": 11, "repostCount": 76, "likeCount": 806, "quoteCount": 8, "bookmarkCount": 50, "langs": [ "en" ], "images": [], "video": null, "external": null, "quote": { "uri": "at://did:plc:t5pur7eruxfgxy7ynpfrmg2w/app.bsky.feed.post/3mtw4hu5cy22l", "url": "https://bsky.app/profile/chiitan.love/post/3mtw4hu5cy22l", "text": "✨Chiitan Special video✨\nCelebrating that Bluesky now lets you post longe…", "createdAt": "2026-08-25T15:17:58.872Z", "author": { "did": "did:plc:t5pur7eruxfgxy7ynpfrmg2w", "handle": "chiitan.love", "displayName": "Chiitan🌈ちぃたん☆", "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:t5pur7eruxfgxy7ynpfrmg2w/b…" } }, "links": [], "mentions": [ "chiitan.love" ], "tags": [], "replyTo": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtwgs26j2c2d" }, { "uri": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtwm5grjns22", "cid": "bafyreifn4ucipyjbdtylqjnfbpimp5ujf3wmbuvechb6lwjgf36vlj5vvi", "url": "https://bsky.app/profile/bsky.app/post/3mtwm5grjns22", "text": "NOW PLAYING: \"Four Minutes and Nineteen Seconds of College Football High…", "createdAt": "2026-08-25T19:58:29.180Z", "author": { "did": "did:plc:z72i7hdynmk6r22z27h6tvur", "handle": "bsky.app", "displayName": "Bluesky", "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:z72i7hdynmk6r22z27h6tvur/b…" }, "replyCount": 13, "repostCount": 31, "likeCount": 524, "quoteCount": 9, "bookmarkCount": 29, "langs": [ "en" ], "images": [], "video": null, "external": null, "quote": { "uri": "at://did:plc:upyx3raxzpwlnzojrncogd7y/app.bsky.feed.post/3mtw23sqfcs2w", "url": "https://bsky.app/profile/sickoscommittee.org/post/3mtw23sqfcs2w", "text": "I set a college football highlight video to the full 4 minutes and 19 se…", "createdAt": "2026-08-25T14:35:27.264Z", "author": { "did": "did:plc:upyx3raxzpwlnzojrncogd7y", "handle": "sickoscommittee.org", "displayName": "Sickos Committee", "avatar": "https://cdn.bsky.app/img/avatar/plain/did:plc:upyx3raxzpwlnzojrncogd7y/b…" } }, "links": [], "mentions": [ "sickoscommittee.org" ], "tags": [], "replyTo": "at://did:plc:z72i7hdynmk6r22z27h6tvur/app.bsky.feed.post/3mtwkahfcss2p" } ] } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `post` | `object` | The post. | | `post.uri` | `string` | The post's `at://` URI, its permanent id. | | `post.cid` | `string` | Content hash of this version of the post. | | `post.url` | `string` | The post on bsky.app. | | `post.text` | `string` | Post text. Empty for media-only posts. | | `post.createdAt` | `string` | When it was posted, ISO 8601. | | `post.author` | `object` | Who posted it. | | `post.author.did` | `string` | The account's permanent id, e.g. `did:plc:z72i7hdynmk6r22z27h6tvur`. | | `post.author.handle` | `string` | Handle, e.g. `bsky.app`. It can change; the DID doesn't. | | `post.author.displayName` | `string`, nullable | Display name; null if not set. | | `post.author.avatar` | `string`, nullable | Avatar image URL; null if none. | | `post.replyCount` | `number` | Replies. | | `post.repostCount` | `number` | Reposts. | | `post.likeCount` | `number` | Likes. | | `post.quoteCount` | `number` | Quote posts. | | `post.bookmarkCount` | `number` | Times saved to bookmarks. | | `post.langs` | `string[]` | Languages the author tagged, e.g. `en`. | | `post.images` | `object[]` | Attached images, in order. Empty if none. | | `post.images[].thumbnail` | `string` | Small image URL. | | `post.images[].fullsize` | `string` | Full-size image URL. | | `post.images[].alt` | `string` | Alt text; empty if none. | | `post.video` | `object`, nullable | Attached video; null if none. | | `post.video.playlist` | `string` | HLS playlist (.m3u8) URL. | | `post.video.thumbnail` | `string`, nullable | Poster image URL; null if none. | | `post.video.alt` | `string`, nullable | Alt text; null if none. | | `post.external` | `object`, nullable | Link card; null if none. | | `post.external.uri` | `string` | The linked page. | | `post.external.title` | `string` | Link card title. | | `post.external.description` | `string` | Link card description. | | `post.external.thumbnail` | `string`, nullable | Link card image URL; null if none. | | `post.quote` | `object`, nullable | The post this one quotes; null if none or hidden from logged-out visitors. Embedded lists, feeds and starter packs aren't returned. | | `post.quote.uri` | `string` | The quoted post's `at://` URI. Pass it as `url` to the post endpoint. | | `post.quote.url` | `string` | The quoted post on bsky.app. | | `post.quote.text` | `string`, nullable | Its text; null when it was deleted or its author blocks viewers. | | `post.quote.createdAt` | `string`, nullable | When it was posted, ISO 8601; null when unavailable. | | `post.quote.author` | `object`, nullable | Who posted it; null when unavailable. | | `post.quote.author.did` | `string` | The account's permanent id, e.g. `did:plc:z72i7hdynmk6r22z27h6tvur`. | | `post.quote.author.handle` | `string` | Handle, e.g. `bsky.app`. It can change; the DID doesn't. | | `post.quote.author.displayName` | `string`, nullable | Display name; null if not set. | | `post.quote.author.avatar` | `string`, nullable | Avatar image URL; null if none. | | `post.links` | `string[]` | Links in the text. | | `post.mentions` | `string[]` | Handles mentioned in the text, without @. | | `post.tags` | `string[]` | Hashtags in the text, without #. | | `post.replyTo` | `string`, nullable | `at://` URI of the post this replies to; null for top-level posts. | | `replies` | `object[]` | Replies in Bluesky's order, each with its own replies. | | `replies[].uri` | `string` | The post's `at://` URI, its permanent id. | | `replies[].cid` | `string` | Content hash of this version of the post. | | `replies[].url` | `string` | The post on bsky.app. | | `replies[].text` | `string` | Post text. Empty for media-only posts. | | `replies[].createdAt` | `string` | When it was posted, ISO 8601. | | `replies[].author` | `object` | Who posted it. | | `replies[].author.did` | `string` | The account's permanent id, e.g. `did:plc:z72i7hdynmk6r22z27h6tvur`. | | `replies[].author.handle` | `string` | Handle, e.g. `bsky.app`. It can change; the DID doesn't. | | `replies[].author.displayName` | `string`, nullable | Display name; null if not set. | | `replies[].author.avatar` | `string`, nullable | Avatar image URL; null if none. | | `replies[].replyCount` | `number` | Replies. | | `replies[].repostCount` | `number` | Reposts. | | `replies[].likeCount` | `number` | Likes. | | `replies[].quoteCount` | `number` | Quote posts. | | `replies[].bookmarkCount` | `number` | Times saved to bookmarks. | | `replies[].langs` | `string[]` | Languages the author tagged, e.g. `en`. | | `replies[].images` | `object[]` | Attached images, in order. Empty if none. | | `replies[].images[].thumbnail` | `string` | Small image URL. | | `replies[].images[].fullsize` | `string` | Full-size image URL. | | `replies[].images[].alt` | `string` | Alt text; empty if none. | | `replies[].video` | `object`, nullable | Attached video; null if none. | | `replies[].video.playlist` | `string` | HLS playlist (.m3u8) URL. | | `replies[].video.thumbnail` | `string`, nullable | Poster image URL; null if none. | | `replies[].video.alt` | `string`, nullable | Alt text; null if none. | | `replies[].external` | `object`, nullable | Link card; null if none. | | `replies[].external.uri` | `string` | The linked page. | | `replies[].external.title` | `string` | Link card title. | | `replies[].external.description` | `string` | Link card description. | | `replies[].external.thumbnail` | `string`, nullable | Link card image URL; null if none. | | `replies[].quote` | `object`, nullable | The post this one quotes; null if none or hidden from logged-out visitors. Embedded lists, feeds and starter packs aren't returned. | | `replies[].quote.uri` | `string` | The quoted post's `at://` URI. Pass it as `url` to the post endpoint. | | `replies[].quote.url` | `string` | The quoted post on bsky.app. | | `replies[].quote.text` | `string`, nullable | Its text; null when it was deleted or its author blocks viewers. | | `replies[].quote.createdAt` | `string`, nullable | When it was posted, ISO 8601; null when unavailable. | | `replies[].quote.author` | `object`, nullable | Who posted it; null when unavailable. | | `replies[].quote.author.did` | `string` | The account's permanent id, e.g. `did:plc:z72i7hdynmk6r22z27h6tvur`. | | `replies[].quote.author.handle` | `string` | Handle, e.g. `bsky.app`. It can change; the DID doesn't. | | `replies[].quote.author.displayName` | `string`, nullable | Display name; null if not set. | | `replies[].quote.author.avatar` | `string`, nullable | Avatar image URL; null if none. | | `replies[].links` | `string[]` | Links in the text. | | `replies[].mentions` | `string[]` | Handles mentioned in the text, without @. | | `replies[].tags` | `string[]` | Hashtags in the text, without #. | | `replies[].replyTo` | `string`, nullable | `at://` URI of the post this replies to; null for top-level posts. | | `replies[].replies` | `BlueskyReply[]` | Replies to this reply. | | `parents` | `object[]` | The posts above this one in the thread, first post first. Empty for top-level posts. | | `parents[].uri` | `string` | The post's `at://` URI, its permanent id. | | `parents[].cid` | `string` | Content hash of this version of the post. | | `parents[].url` | `string` | The post on bsky.app. | | `parents[].text` | `string` | Post text. Empty for media-only posts. | | `parents[].createdAt` | `string` | When it was posted, ISO 8601. | | `parents[].author` | `object` | Who posted it. | | `parents[].author.did` | `string` | The account's permanent id, e.g. `did:plc:z72i7hdynmk6r22z27h6tvur`. | | `parents[].author.handle` | `string` | Handle, e.g. `bsky.app`. It can change; the DID doesn't. | | `parents[].author.displayName` | `string`, nullable | Display name; null if not set. | | `parents[].author.avatar` | `string`, nullable | Avatar image URL; null if none. | | `parents[].replyCount` | `number` | Replies. | | `parents[].repostCount` | `number` | Reposts. | | `parents[].likeCount` | `number` | Likes. | | `parents[].quoteCount` | `number` | Quote posts. | | `parents[].bookmarkCount` | `number` | Times saved to bookmarks. | | `parents[].langs` | `string[]` | Languages the author tagged, e.g. `en`. | | `parents[].images` | `object[]` | Attached images, in order. Empty if none. | | `parents[].images[].thumbnail` | `string` | Small image URL. | | `parents[].images[].fullsize` | `string` | Full-size image URL. | | `parents[].images[].alt` | `string` | Alt text; empty if none. | | `parents[].video` | `object`, nullable | Attached video; null if none. | | `parents[].video.playlist` | `string` | HLS playlist (.m3u8) URL. | | `parents[].video.thumbnail` | `string`, nullable | Poster image URL; null if none. | | `parents[].video.alt` | `string`, nullable | Alt text; null if none. | | `parents[].external` | `object`, nullable | Link card; null if none. | | `parents[].external.uri` | `string` | The linked page. | | `parents[].external.title` | `string` | Link card title. | | `parents[].external.description` | `string` | Link card description. | | `parents[].external.thumbnail` | `string`, nullable | Link card image URL; null if none. | | `parents[].quote` | `object`, nullable | The post this one quotes; null if none or hidden from logged-out visitors. Embedded lists, feeds and starter packs aren't returned. | | `parents[].quote.uri` | `string` | The quoted post's `at://` URI. Pass it as `url` to the post endpoint. | | `parents[].quote.url` | `string` | The quoted post on bsky.app. | | `parents[].quote.text` | `string`, nullable | Its text; null when it was deleted or its author blocks viewers. | | `parents[].quote.createdAt` | `string`, nullable | When it was posted, ISO 8601; null when unavailable. | | `parents[].quote.author` | `object`, nullable | Who posted it; null when unavailable. | | `parents[].quote.author.did` | `string` | The account's permanent id, e.g. `did:plc:z72i7hdynmk6r22z27h6tvur`. | | `parents[].quote.author.handle` | `string` | Handle, e.g. `bsky.app`. It can change; the DID doesn't. | | `parents[].quote.author.displayName` | `string`, nullable | Display name; null if not set. | | `parents[].quote.author.avatar` | `string`, nullable | Avatar image URL; null if none. | | `parents[].links` | `string[]` | Links in the text. | | `parents[].mentions` | `string[]` | Handles mentioned in the text, without @. | | `parents[].tags` | `string[]` | Hashtags in the text, without #. | | `parents[].replyTo` | `string`, nullable | `at://` URI of the post this replies to; null for top-level posts. | ## Caching and freshness Responses are cached for up to 3 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 403 | `not_public` | The platform confirmed this account or content isn't available to logged-out visitors. Use a publicly visible target. Charged when the platform was checked. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `bluesky_post` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's bluesky_post with url "https://bsky.app/profile/bsky.app/post/3mtykea5kds2d" and summarize what you find. ``` Claude calls `bluesky_post` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "url": "https://bsky.app/profile/bsky.app/post/3mtykea5kds2d" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [User posts](https://lurkapi.com/docs/bluesky/user-posts.md) · Next: [Profile and stories](https://lurkapi.com/docs/snapchat/profile.md) --- # Snapchat API > Public profiles, live stories, highlights and Spotlight videos from Snapchat. - **Platform:** [Snapchat](https://lurkapi.com/docs/snapchat.md) (snapchat.com) - **Web page:** https://lurkapi.com/docs/snapchat ## Overview Read a Snapchat Public Profile the way snapchat.com shows it to a logged-out visitor: the profile, the story that's live right now, saved highlights and recent Spotlight videos, with direct links to every image and video. Useful for creator research and keeping an eye on brands. Source: snapchat.com. ## Try it live Pick an endpoint and run it: real data, no signup, 5 free requests a day. Live playground: https://lurkapi.com/docs/snapchat#try ## Endpoints - [Profile and stories](https://lurkapi.com/docs/snapchat/profile.md): A public profile with its live story, highlights and Spotlight videos, with direct media links. (GET /v1/snapchat/profile · 1 credit) ## Call it Every endpoint is a `GET` with your key in the `x-api-key` header. For example, [Profile and stories](https://lurkapi.com/docs/snapchat/profile.md): curl: ```bash curl "https://api.lurkapi.com/v1/snapchat/profile?handle=nba" \ -H "x-api-key: YOUR_API_KEY" ``` [Get a free API key](https://lurkapi.com/login?next=/dashboard) ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), each endpoint is a tool Claude can call: | Tool | What it does | | --- | --- | | `snapchat_profile` | [Profile and stories](https://lurkapi.com/docs/snapchat/profile.md): A public profile with its live story, highlights and Spotlight videos, with direct media links. | --- # Profile and stories > A public profile with its live story, highlights and Spotlight videos, with direct media links. - **Request:** `GET https://api.lurkapi.com/v1/snapchat/profile` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `snapchat_profile` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 15 minutes - **Try it live:** https://lurkapi.com/docs/snapchat/profile#try (no signup; free tool: [Snapchat story viewer](https://lurkapi.com/free-tools/snapchat-story-viewer)) - **Platform:** [Snapchat](https://lurkapi.com/docs/snapchat.md) (snapchat.com) - **Web page:** https://lurkapi.com/docs/snapchat/profile ## When to use this Use it to see what a creator or brand is posting on Snapchat right now, without an account. One call returns the profile, the live public story (`story`), saved highlights and up to ~25 recent Spotlight videos. **Only Public Profiles post to the web.** For a regular account you get the username, display name and Snapcode, `isPublic: false` and empty lists; that's not an error. Friends-only and private stories are never visible. **Media links** point straight at Snapchat's CDN and open without cookies. Story snaps are temporary (usually 24 hours; some creators keep theirs up for several days), so save what you need. Unknown usernames return 404 `not_found`. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `handle` | string | yes | | | Snapchat username, with or without @ (e.g. `nba`), or the profile URL (`snapchat.com/add/nba`, `snapchat.com/@nba`). | `nba` | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/snapchat/profile?handle=nba" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ handle: "nba", }); const res = await fetch(`https://api.lurkapi.com/v1/snapchat/profile?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.story); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/snapchat/profile", params={ "handle": "nba", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["story"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999999, "credits_charged": 1, "profile": { "username": "nba", "displayName": "NBA", "bio": "30 teams, 1 goal.", "subscriberCount": 3572700, "website": "https://NBA.com", "avatarUrl": "https://cf-st.sc-cdn.net/aps/bolt/aHR0cHM6Ly9jZi1zdC5zYy1jZG4ubmV0L2QvcG…", "snapcodeUrl": "https://app.snapchat.com/web/deeplink/snapcode?username=nba&type=SVG&bitmoji=enable", "category": "business-group", "subcategory": "sports-league", "badge": "star", "verified": true, "isPublic": true, "hasStory": true, "createdAt": "2018-05-17T22:48:15.058Z", "url": "https://www.snapchat.com/@nba" }, "story": [ { "id": "2wzhbo4SSiWIG-fd9lWR2wAAgc3pueWZkaGV1AaDuF62BAaDt-_1RAAAAAA", "type": "video", "thumbnailUrl": "https://cf-st.sc-cdn.net/d/y5Z3yeCzQGozfAaHB1jA5.256.IRZXSOY?mo=GlMaDjIC…", "mediaUrl": "https://cf-st.sc-cdn.net/d/y5Z3yeCzQGozfAaHB1jA5.1034.IRZXSOY?mo=Gl8aGDI…", "postedAt": "2026-09-29T16:25:09.000Z" }, { "id": "2wzhbo4SSiWIG-fd9lWR2wAAgdHB4dmdtdWt2AaDuF7BAAaDuDo3MAAAAAA", "type": "video", "thumbnailUrl": "https://cf-st.sc-cdn.net/d/ASfxTY9T1IATr8Yh7lf0R.256.IRZXSOY?mo=GlMaDjIC…", "mediaUrl": "https://cf-st.sc-cdn.net/d/ASfxTY9T1IATr8Yh7lf0R.1034.IRZXSOY?mo=Gl8aGDI…", "postedAt": "2026-09-29T16:45:26.000Z" } ], "highlights": [ { "id": "029f2cc3-c0df-46c2-b610-485c137f9a0a", "title": "2025-26 NBA Finals 🏆", "thumbnailUrl": "https://cf-st.sc-cdn.net/d/ZXSSacNIpSYqxAm21SSGc.410?mo=GjcaFjIBBDoBfUIG…", "snaps": [ { "id": null, "type": "image", "thumbnailUrl": "https://cf-st.sc-cdn.net/d/ZXSSacNIpSYqxAm21SSGc.410?mo=GjcaFjIBBDoBfUIG…", "mediaUrl": "https://cf-st.sc-cdn.net/d/ZXSSacNIpSYqxAm21SSGc.400?mo=Gk8aDDIBBDoBfVBe…", "postedAt": "2026-06-01T17:36:48.000Z" }, { "id": null, "type": "image", "thumbnailUrl": "https://cf-st.sc-cdn.net/d/FgntIqJi6clNRLmaxXkXN.410?mo=GjcaFjIBBDoBfUIG…", "mediaUrl": "https://cf-st.sc-cdn.net/d/FgntIqJi6clNRLmaxXkXN.400?mo=Gk0aDjIBBDoBfUgC…", "postedAt": "2026-06-01T17:36:48.000Z" } ] }, { "id": "2941c1a3-96ba-45aa-bdf4-30b344e63e42", "title": "Your 2025-26 Kia NBA MVP 🏆", "thumbnailUrl": "https://cf-st.sc-cdn.net/d/iqFfVpTceYNBTtMJvlQns.410.IRZXSOY?mo=GkAaFjIB…", "snaps": [ { "id": null, "type": "image", "thumbnailUrl": "https://cf-st.sc-cdn.net/d/iqFfVpTceYNBTtMJvlQns.410.IRZXSOY?mo=GkAaFjIB…", "mediaUrl": "https://cf-st.sc-cdn.net/d/iqFfVpTceYNBTtMJvlQns.400.IRZXSOY?mo=GlwaCTIB…", "postedAt": "2026-05-17T23:43:34.000Z" }, { "id": null, "type": "video", "thumbnailUrl": "https://cf-st.sc-cdn.net/d/eHfqR7qRY9kuVjZbupqN4.410.IRZXSOY?mo=GkAaFjIB…", "mediaUrl": "https://cf-st.sc-cdn.net/d/eHfqR7qRY9kuVjZbupqN4.1034.IRZXSOY?mo=GlgaDDI…", "postedAt": "2026-05-17T23:50:52.000Z" } ] } ], "spotlight": [ { "id": "W7_EDlXWTBiXAEEniNoMPwAAYZGl0Ym1wZ3ZsAaDvhddzAaDvhbJSAAAAAQ", "url": "https://www.snapchat.com/spotlight/W7_EDlXWTBiXAEEniNoMPwAAYZGl0Ym1wZ3ZsAaDvhddzAaDvhbJSAAAAAQ", "title": "WEMBY ON THE OUTCOME OF THE 2026 NBA FINALS: | “I COULDN’T IMAGINE A BETTER MOTIVATION.” 😤", "description": "Victor Wembanyama’s mindset is built for greatness 🔥🗣️ #NBA #Basketball #Wemby #Spurs", "thumbnailUrl": "https://cf-st.sc-cdn.net/d/24TV6xVxTujwNzwlpEVXB.256.IRZXSOY?mo=GkYaCTIB…", "videoUrl": "https://cf-st.sc-cdn.net/d/24TV6xVxTujwNzwlpEVXB.27.IRZXSOY?mo=Gl0aCTIBB…", "durationMs": 15080, "views": 2019, "shares": 12, "comments": 2, "postedAt": "2026-09-29T23:35:11.698Z", "hashtags": [ "#nba", "#wemby", "#basketball", "#spurs" ] }, { "id": "W7_EDlXWTBiXAEEniNoMPwAAYdW10aGdlY2h3AaDvVhSqAaDvVgVGAAAAAQ", "url": "https://www.snapchat.com/spotlight/W7_EDlXWTBiXAEEniNoMPwAAYdW10aGdlY2h3AaDvVhSqAaDvVgVGAAAAAQ", "title": "Steph Curry & Jordan Poole's Golden State Warriors Photo Shoot Fun", "description": null, "thumbnailUrl": "https://cf-st.sc-cdn.net/d/jJMXmhYbdFnf15nBog0BX.256.IRZXSOY?mo=GkYaCTIB…", "videoUrl": "https://cf-st.sc-cdn.net/d/jJMXmhYbdFnf15nBog0BX.27.IRZXSOY?mo=Gl0aCTIBB…", "durationMs": 13630, "views": 2077, "shares": 0, "comments": 3, "postedAt": "2026-09-29T22:43:07.206Z", "hashtags": [] } ], "relatedAccounts": [ { "username": "warriors", "displayName": "Golden State Warriors", "avatarUrl": "https://cf-st.sc-cdn.net/aps/bolt/aHR0cHM6Ly9jZi1zdC5zYy1jZG4ubmV0L2QvNj…", "verified": true, "url": "https://www.snapchat.com/@warriors" }, { "username": "nfl", "displayName": "NFL Official", "avatarUrl": "https://cf-st.sc-cdn.net/aps/bolt/aHR0cHM6Ly9jZi1zdC5zYy1jZG4ubmV0L2QvT3…", "verified": true, "url": "https://www.snapchat.com/@nfl" } ] } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `profile` | `object` | The account. | | `profile.username` | `string` | Username, lowercase. | | `profile.displayName` | `string`, nullable | Display name. | | `profile.bio` | `string`, nullable | Bio; null if empty or not public. | | `profile.subscriberCount` | `number`, nullable | Subscribers, rounded by Snapchat (to 100). null when the creator hides it or the account isn't public. | | `profile.website` | `string`, nullable | Website link from the profile, with `https://` added when the creator left it out. | | `profile.avatarUrl` | `string`, nullable | Profile picture. | | `profile.snapcodeUrl` | `string`, nullable | The account's Snapcode (SVG), scannable to add them. | | `profile.category` | `string`, nullable | Profile category, e.g. `people` or `business-group`. | | `profile.subcategory` | `string`, nullable | Profile subcategory, e.g. `artist` or `sports-league`. | | `profile.badge` | `string`, nullable | Badge next to the name: `star` (Snap Star), `tick` (verified checkmark) or `snapchat_plus`; null without one. | | `profile.verified` | `boolean` | Has a Snap Star or checkmark badge. | | `profile.isPublic` | `boolean` | Has a Public Profile. Only public profiles show stories, highlights and Spotlight on the web; for other accounts the lists are empty. | | `profile.hasStory` | `boolean` | A public story is live right now (`story` isn't empty). | | `profile.createdAt` | `string`, nullable | When the Public Profile was created; null if not public, ISO 8601. | | `profile.url` | `string` | Profile on snapchat.com. | | `story` | `object[]` | The public story that's live now, oldest snap first. Empty when there's none. | | `story[].id` | `string`, nullable | Snap id. null in highlights, where Snapchat serves snaps without one. | | `story[].type` | `string` | `image` or `video`. | | `story[].thumbnailUrl` | `string`, nullable | Small preview image on Snapchat's CDN. | | `story[].mediaUrl` | `string` | The full image or video on Snapchat's CDN. Opens in a browser as is: no login, no cookies. | | `story[].postedAt` | `string`, nullable | When it was posted, ISO 8601. | | `highlights` | `object[]` | Saved story highlights, as ordered on the profile. | | `highlights[].id` | `string`, nullable | Highlight id. | | `highlights[].title` | `string`, nullable | The highlight's title, as the creator named it. | | `highlights[].thumbnailUrl` | `string`, nullable | Cover image. | | `highlights[].snaps` | `object[]` | The snaps saved in this highlight, in order. | | `highlights[].snaps[].id` | `string`, nullable | Snap id. null in highlights, where Snapchat serves snaps without one. | | `highlights[].snaps[].type` | `string` | `image` or `video`. | | `highlights[].snaps[].thumbnailUrl` | `string`, nullable | Small preview image on Snapchat's CDN. | | `highlights[].snaps[].mediaUrl` | `string` | The full image or video on Snapchat's CDN. Opens in a browser as is: no login, no cookies. | | `highlights[].snaps[].postedAt` | `string`, nullable | When it was posted, ISO 8601. | | `spotlight` | `object[]` | Recent Spotlight videos (up to ~25), newest first. | | `spotlight[].id` | `string` | Spotlight id. | | `spotlight[].url` | `string` | The Spotlight's page on snapchat.com. | | `spotlight[].title` | `string`, nullable | The creator's title or on-screen caption. When there's none, Snapchat's own AI-generated title; null if neither exists. | | `spotlight[].description` | `string`, nullable | The creator's description, hashtags included; null if empty. | | `spotlight[].thumbnailUrl` | `string`, nullable | Cover image. | | `spotlight[].videoUrl` | `string` | The video on Snapchat's CDN. Opens in a browser as is. | | `spotlight[].durationMs` | `number`, nullable | Length in milliseconds. | | `spotlight[].views` | `number`, nullable | View count; null if hidden. | | `spotlight[].shares` | `number`, nullable | Share count; null if Snapchat didn't include it. | | `spotlight[].comments` | `number`, nullable | Comment count; null if Snapchat didn't include it. | | `spotlight[].postedAt` | `string`, nullable | When it was posted, ISO 8601. | | `spotlight[].hashtags` | `string[]` | Hashtags, as written, e.g. `#nba`. | | `relatedAccounts` | `object[]` | Accounts Snapchat suggests alongside this one. | | `relatedAccounts[].username` | `string` | Username. | | `relatedAccounts[].displayName` | `string`, nullable | Display name. | | `relatedAccounts[].avatarUrl` | `string`, nullable | Profile picture. | | `relatedAccounts[].verified` | `boolean` | Has a Snap Star or checkmark badge. | | `relatedAccounts[].url` | `string` | Profile on snapchat.com. | ## Caching and freshness Responses are cached for up to 15 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `snapchat_profile` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's snapchat_profile with handle "nba" and summarize what you find. ``` Claude calls `snapchat_profile` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "handle": "nba" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Post](https://lurkapi.com/docs/bluesky/post.md) · Next: [Profile](https://lurkapi.com/docs/truthsocial/profile.md) --- # Truth Social API > Profiles and posts from public Truth Social accounts. - **Platform:** [Truth Social](https://lurkapi.com/docs/truthsocial.md) (truthsocial.com) - **Web page:** https://lurkapi.com/docs/truthsocial ## Overview Read public Truth Social accounts the way logged-out visitors see them: profile stats, the latest posts with media and engagement counts, and single posts. Field names follow Mastodon's API (Truth Social runs on it), so Mastodon client code keeps working; every post also carries plain `text` next to the HTML `content`. Accounts explicitly hidden from logged-out visitors return 403 `not_public`. When the upstream cannot distinguish a hidden account from a missing one, the response is 404 `not_found`. Prominent accounts and most other public ones are visible. Source: truthsocial.com. ## Try it live Pick an endpoint and run it: real data, no signup, 5 free requests a day. Live playground: https://lurkapi.com/docs/truthsocial#try ## Endpoints - [Profile](https://lurkapi.com/docs/truthsocial/profile.md): An account's bio, avatar, follower and post counts, and verification. (GET /v1/truthsocial/profile · 1 credit) - [User posts](https://lurkapi.com/docs/truthsocial/user-posts.md): An account's latest posts and ReTruths, newest first, 20 per page. (GET /v1/truthsocial/user/posts · 1 credit) - [Post](https://lurkapi.com/docs/truthsocial/post.md): One post with its text, media, link preview and engagement counts. (GET /v1/truthsocial/post · 1 credit) ## Call it Every endpoint is a `GET` with your key in the `x-api-key` header. For example, [Profile](https://lurkapi.com/docs/truthsocial/profile.md): curl: ```bash curl "https://api.lurkapi.com/v1/truthsocial/profile?handle=realDonaldTrump" \ -H "x-api-key: YOUR_API_KEY" ``` [Get a free API key](https://lurkapi.com/login?next=/dashboard) ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), each endpoint is a tool Claude can call: | Tool | What it does | | --- | --- | | `truthsocial_profile` | [Profile](https://lurkapi.com/docs/truthsocial/profile.md): An account's bio, avatar, follower and post counts, and verification. | | `truthsocial_user_posts` | [User posts](https://lurkapi.com/docs/truthsocial/user-posts.md): An account's latest posts and ReTruths, newest first, 20 per page. | | `truthsocial_post` | [Post](https://lurkapi.com/docs/truthsocial/post.md): One post with its text, media, link preview and engagement counts. | --- # Profile > An account's bio, avatar, follower and post counts, and verification. - **Request:** `GET https://api.lurkapi.com/v1/truthsocial/profile` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `truthsocial_profile` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 10 minutes - **Try it live:** https://lurkapi.com/docs/truthsocial/profile#try (no signup) - **Platform:** [Truth Social](https://lurkapi.com/docs/truthsocial.md) (truthsocial.com) - **Web page:** https://lurkapi.com/docs/truthsocial/profile ## When to use this Use it to size up an account: followers, following, post count, verification and the date of the latest post. `id` is what user posts takes as `user_id`. Pass `handle` as a username, `@username` or a truthsocial.com profile URL. Unknown accounts return 404 `not_found`. Accounts explicitly hidden from logged-out visitors return 403 `not_public`. When the upstream cannot distinguish a hidden account from a missing one, the response is 404 `not_found`. Prominent accounts and most other public ones are visible. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `handle` | string | yes | | | Username, with or without @ (e.g. `realDonaldTrump`), or the profile URL. | `realDonaldTrump` | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/truthsocial/profile?handle=realDonaldTrump" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ handle: "realDonaldTrump", }); const res = await fetch(`https://api.lurkapi.com/v1/truthsocial/profile?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.fields); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/truthsocial/profile", params={ "handle": "realDonaldTrump", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["fields"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999998, "credits_charged": 1, "id": "107780257626128497", "username": "realDonaldTrump", "acct": "realDonaldTrump", "display_name": "Donald J. Trump", "note": "

", "note_text": "", "url": "https://truthsocial.com/@realDonaldTrump", "avatar": "https://static-assets-1.truthsocial.com/tmtg:prime-ts-assets/accounts/av…", "header": "https://static-assets-1.truthsocial.com/tmtg:prime-ts-assets/accounts/he…", "website": "www.DonaldJTrump.com", "location": null, "fields": [], "followers_count": 13086927, "following_count": 69, "statuses_count": 36875, "last_status_at": "2026-09-30", "verified": true, "premium": true, "locked": false, "bot": false, "created_at": "2022-02-11T16:16:57.705Z" } ``` ## Response fields Every field of a successful response. `[]` marks a list: `a[].b` is the `b` of each item in `a`. **nullable** fields can be `null`; **optional** fields can be missing. | Field | Type | Description | | --- | --- | --- | | `success` | `true` | Always true here; errors have `success: false`. | | `credits_remaining` | `number` | Your balance after this call. On anonymous playground calls: free tries left today. | | `credits_charged` | `number` | Credits this call cost; 0 on free endpoints. On anonymous playground calls: tries used (1). | | `id` | `string` | Account id, e.g. `107780257626128497`. Pass it as `user_id` to user posts. | | `username` | `string` | Username without @, e.g. `realDonaldTrump`. | | `acct` | `string` | Same as username: every Truth Social account is local. | | `display_name` | `string` | Display name. | | `note` | `string` | Bio as HTML. | | `note_text` | `string` | Bio as plain text. | | `url` | `string` | Profile on truthsocial.com. | | `avatar` | `string`, nullable | Profile picture URL; null if none. | | `header` | `string`, nullable | Banner image URL; null if none. | | `website` | `string`, nullable | Website from the profile, as typed (may lack https://); null if none. | | `location` | `string`, nullable | Location from the profile; null if none. | | `fields` | `object[]` | Extra profile fields (label/value pairs). | | `fields[].name` | `string` | Label. | | `fields[].value` | `string` | Value as HTML. | | `followers_count` | `number` | Followers. | | `following_count` | `number` | Accounts followed. | | `statuses_count` | `number` | Posts, replies and reposts. | | `last_status_at` | `string`, nullable | Day of the latest post, `YYYY-MM-DD`; null if never posted. | | `verified` | `boolean` | Has the verified badge. | | `premium` | `boolean`, nullable | Has Truth+ premium; null if not reported. | | `locked` | `boolean` | Approves followers manually. | | `bot` | `boolean` | Marked as an automated account. | | `created_at` | `string` | Account created at, ISO 8601. | ## Caching and freshness Responses are cached for up to 10 minutes, so a repeat call within that window can return the same data. It still costs 1 credit. ## Errors Errors return `{ success: false, error, code, docs }` with the HTTP status below. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). [All error codes](https://lurkapi.com/docs.md#errors). | Status | Code | Meaning | | --- | --- | --- | | 401 | `missing_api_key` | No API key. Send it in the `x-api-key` header. | | 401 | `invalid_api_key` | The key is unknown or was revoked. | | 402 | `insufficient_credits` | Not enough credits for this call. Buy a pack or wait for tomorrow's top-up. | | 403 | `not_public` | The platform confirmed this account or content isn't available to logged-out visitors. Use a publicly visible target. Charged when the platform was checked. | | 404 | `not_found` | The endpoint, or the thing you asked for (ad, post, subreddit), doesn't exist. Charged when the platform was checked. | | 405 | `method_not_allowed` | Endpoints take GET with query params. | | 429 | `rate_limited` | Too many calls at once. Wait for the `retry-after` seconds, then retry. | | 500 | `internal_error` | Something broke on our side. Retry; 5xx errors are free. | | 502 | `upstream_error` | The platform didn't give a usable answer. Retry; 5xx errors are free. | | 503 | `upstream_busy` | All our connections to the platform are busy. Retry in a few seconds; 5xx errors are free. | | 504 | `upstream_timeout` | The platform took over 30 seconds to answer. Retry; timeouts are free. | ## Use it in Claude Once LurkAPI is [connected to Claude](https://lurkapi.com/docs.md#claude), this endpoint is the `truthsocial_profile` tool. Ask in plain English, for example: Ask Claude: ```text Use LurkAPI's truthsocial_profile with handle "realDonaldTrump" and summarize what you find. ``` Claude calls `truthsocial_profile` with arguments like these, and each call costs 1 credit: Tool arguments: ```json { "handle": "realDonaldTrump" } ``` Tool results skip nulls and empty lists to save tokens. --- Previous: [Profile and stories](https://lurkapi.com/docs/snapchat/profile.md) · Next: [User posts](https://lurkapi.com/docs/truthsocial/user-posts.md) --- # User posts > An account's latest posts and ReTruths, newest first, 20 per page. - **Request:** `GET https://api.lurkapi.com/v1/truthsocial/user/posts` - **Auth:** `x-api-key` header ([get a free key](https://lurkapi.com/login?next=/dashboard)) - **Cost:** 1 credit per call. Validation errors, `401`/`402`/`429` rejections and `5xx` failures are free; `not_found` is charged ([how charging works](https://lurkapi.com/docs.md#credits)). - **MCP tool:** `truthsocial_user_posts` on `https://api.lurkapi.com/mcp` - **Freshness:** responses are cached for up to 2 minutes - **Try it live:** https://lurkapi.com/docs/truthsocial/user-posts#try (no signup) - **Platform:** [Truth Social](https://lurkapi.com/docs/truthsocial.md) (truthsocial.com) - **Web page:** https://lurkapi.com/docs/truthsocial/user-posts ## When to use this Use it to monitor an account: every post with text, media, link previews and reply, ReTruth and like counts. Replies are left out; ReTruths are included, with the original post in `reblog`. Pass `handle` or `user_id` (from profile). `user_id` skips a lookup, but both cost 1 credit. **Pagination.** Pass the response's `next_max_id` back as `next_max_id` (with the same account) for older posts. It's null once a page comes back empty. Unknown accounts return 404 `not_found`. Accounts explicitly hidden from logged-out visitors return 403 `not_public`. When the upstream cannot distinguish a hidden account from a missing one, the response is 404 `not_found`. Prominent accounts and most other public ones are visible. ## Parameters All parameters go in the query string. | Name | Type | Required | Default | Allowed values | Description | Example | | --- | --- | --- | --- | --- | --- | --- | | `handle` | string | no | | | Username (e.g. `realDonaldTrump`), `@username` or profile URL. Required unless you pass `user_id`. | `realDonaldTrump` | | `user_id` | string | no | | | Numeric account id from profile, e.g. `107780257626128497`. Used instead of `handle` when both are given. | | | `next_max_id` | string | no | | | The `next_max_id` from the previous response, to get older posts. | | | `trim` | boolean | no | `false` | `true`, `false` | `true` omits each post's HTML `content` (plain `text` stays) for a smaller response. | | ## Example request Replace `YOUR_API_KEY` with your key, or set `LURKAPI_KEY` for the code. [Get a free key](https://lurkapi.com/login?next=/dashboard). curl: ```bash curl "https://api.lurkapi.com/v1/truthsocial/user/posts?handle=realDonaldTrump" \ -H "x-api-key: YOUR_API_KEY" ``` JavaScript: ```js const params = new URLSearchParams({ handle: "realDonaldTrump", }); const res = await fetch(`https://api.lurkapi.com/v1/truthsocial/user/posts?${params}`, { headers: { "x-api-key": process.env.LURKAPI_KEY }, }); const data = await res.json(); if (!data.success) throw new Error(`${data.code}: ${data.error}`); console.log(data.posts); ``` Python: ```python import os import requests res = requests.get( "https://api.lurkapi.com/v1/truthsocial/user/posts", params={ "handle": "realDonaldTrump", }, headers={"x-api-key": os.environ["LURKAPI_KEY"]}, timeout=60, ) data = res.json() if not data["success"]: raise RuntimeError(f"{data['code']}: {data['error']}") print(data["posts"]) ``` ## Example response A real response from this endpoint, captured from the live API and trimmed to a couple of items. Strings over 96 characters (mostly signed media URLs) are cut short and end in `…`. 200 OK (application/json): ```json { "success": true, "credits_remaining": 999988, "credits_charged": 1, "posts": [ { "id": "117357090030899348", "created_at": "2026-09-30T00:11:21.773Z", "url": "https://truthsocial.com/@realDonaldTrump/117357090030899348", "content": "

", "text": "", "title": null, "language": null, "visibility": "public", "sensitive": false, "spoiler_text": "", "in_reply_to_id": null, "in_reply_to_account_id": null, "quote_id": null, "replies_count": 1313, "reblogs_count": 2788, "favourites_count": 9614, "upvotes_count": 9614, "downvotes_count": 0, "edited_at": null, "media_attachments": [ { "id": "117357089273579505", "type": "image", "url": "https://static-assets-1.truthsocial.com/tmtg:prime-ts-assets/media_attac…", "preview_url": "https://static-assets-1.truthsocial.com/tmtg:prime-ts-assets/media_attac…", "description": null, "meta": { "original": { "width": 794, "height": 1294, "aspect": 0.6136012364760433, "duration": null }, "small": { "width": 626, "height": 1021, "aspect": 0.6131243878550441, "duration": null } } } ], "card": null, "group": null, "mentions": [], "tags": [], "account": { "id": "107780257626128497", "username": "realDonaldTrump", "acct": "realDonaldTrump", "display_name": "Donald J. Trump", "url": "https://truthsocial.com/@realDonaldTrump", "avatar": "https://static-assets-1.truthsocial.com/tmtg:prime-ts-assets/accounts/av…", "verified": true, "followers_count": 13086928 }, "reblog": null, "quote": null }, { "id": "117356339820644146", "created_at": "2026-09-29T21:00:34.470Z", "url": "https://truthsocial.com/@realDonaldTrump/117356339820644146", "content": "

Iran Has Lost Control of the Strait: