# ShortsIntel Intelligence API — full reference Base URL: `https://www.shortsintel.com/v1` OpenAPI 3.1: `https://www.shortsintel.com/docs/openapi.json` ShortsIntel is a short-form video intelligence API for agents. It searches, enriches, scores and tracks public videos on TikTok, Instagram Reels and YouTube Shorts, and returns compact JSON built for model context rather than raw vendor payloads. Core model: - Every operation creates a **Run**. `wait` (seconds, default 30, max 120) holds the response; if the work finishes you get `{ run, result }`, otherwise `{ run }` with status `running` to poll at `GET /v1/runs/{id}`. The envelope is identical either way. - Run states: `running`, `completed`, `partial`, `failed`. A `partial` Run keeps its completed items and charges for them, up to what the Balance covers. A `failed` Run charges nothing. - Lists paginate with `items` and `next_cursor`. Cursors are opaque: pass back the `next_cursor` you were given. Any other cursor is a `400 invalid_request` and is not charged. - Errors are `application/problem+json` with a stable `code`. Branch on `code`, never on the prose. - Re-reading a Run is always free. Check `GET /v1/runs` before repeating work you already paid for. Money: - Prepaid balance in USD. `estimate=true` on any paid operation returns `{ estimate_usd, cached }` without creating a Run or charging anything. Use it before spending. - Cached results cost 25% of fresh. A result is cached when the Corpus holds a Snapshot under 24 h old. - Before any work starts, the Balance is checked against the operation's quoted price (for Research and enriched searches, the worst-case quote). If it cannot cover that, the call is a `402 insufficient_balance` and no Run is created or charged. - A multi-item Run (Research, enriched search) settles per item when it finishes. If the Balance no longer covers every completed item, the completed items are charged in order up to the Balance, the rest fail with `insufficient_balance`, and the Run ends `partial`. A Run that cannot afford any of its work ends `failed` and is not charged. - Top-ups start at $10. Limits: 60 requests per minute per key, 10 concurrent running Runs per Customer. `429` carries `Retry-After`. Auth: `Authorization: Bearer sk_live_...`. Keys are created in the console, shown once and revocable. The MCP endpoint also accepts OAuth 2.1. ## Quick start ```bash export SHORTSINTEL_API_KEY="sk_live_..." # What will this cost? (no Run, no charge) curl -X POST https://www.shortsintel.com/v1/videos/search \ -H "Authorization: Bearer $SHORTSINTEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query":"cold plunge","estimate":true}' # Run it curl -X POST https://www.shortsintel.com/v1/videos/search \ -H "Authorization: Bearer $SHORTSINTEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query":"cold plunge","platforms":["tiktok"],"limit":30}' # If it came back with status "running", poll it curl https://www.shortsintel.com/v1/runs/RUN_ID -H "Authorization: Bearer $SHORTSINTEL_API_KEY" ``` ## Endpoints ## Research One intent in plain language, a Digest back: themes, hooks that work verbatim, sentiment, recommended angles, and the ranked videos, creators, hashtags and sounds behind them. ### `POST /v1/research` — Research a niche Derives keywords from an intent, searches every platform, de-duplicates by platform id, enriches the top `max_videos` with Intelligence, scores them against their creators' baselines, ranks them and produces one Digest. Always async: expect 2 to 10 minutes. The full evidence lists page at `GET /v1/runs/{id}/items?type=videos|creators|hashtags|sounds`. Instagram is reported as `coverage: "partial"`. **Price:** $0.50 covering up to 50 freshly enriched videos, then $0.01 each, so at most $2.00 at `max_videos` 200. Videos already enriched in the Corpus are free; failed videos are never charged. The 402 pre-check uses that worst case. Parameters: - `Idempotency-Key` (header) — 1-255 printable ASCII characters. Replaying this key with the same payload returns the original Run (202 while it is still running); a different payload, or a different operation, is a 409 `idempotency_conflict`. Body fields: - `intent` (required) — What you want to learn, in plain language. - `keywords` (optional) — Skip keyword derivation and use these. At most 4. - `platforms` (optional) - `region` (optional) — ISO country code. - `window` (optional) - `max_videos` (optional) — Videos to enrich. Default 50, max 200. - `wait` (optional) - `estimate` (optional) Responses: - `200` — The Run finished inside the wait budget: `{ run, result }`. With `estimate=true`, `{ estimate_usd, cached }` instead. - `202` — Still running: `{ run }`. Poll `GET /v1/runs/{id}`. - `400` — `invalid_request` (a malformed id, `wait`, `estimate` or Idempotency-Key) or `unsupported_platform`, refused before any Run opens. Nothing is charged. - `404` — A Run that opened and then failed is answered as `application/problem+json` with its problem's status — `not_found` here, `vendor_error` as 502 — and the Run envelope alongside, so the Run id is kept. A failed Run is never charged. - `402` — `insufficient_balance` — the Balance cannot cover the quoted price (the worst case for Research and enriched search). No Run is created and nothing is charged. A multi-item Run whose Balance runs short later charges its completed items up to the Balance and ends `partial`. - `409` — `idempotency_conflict` — the same Idempotency-Key with a different payload. - `429` — `rate_limit_exceeded` or `concurrency_limit_exceeded`, with `Retry-After`. Example: ```bash curl -X POST https://www.shortsintel.com/v1/research \ -H "Authorization: Bearer $SHORTSINTEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"intent":"why are cold plunge videos taking off","platforms":["tiktok"],"window":"month","max_videos":50}' ``` ## Videos Search, look up, enrich, score and read the comments of short-form videos across TikTok, Instagram Reels and YouTube Shorts. ### `POST /v1/videos/search` — Search videos Keyword search across TikTok, Instagram and YouTube, one page per call. `coverage` reports each platform as complete, partial or failed. With `intelligence: true` the search runs in the request and the enrichment passes run as a workflow, so each enriched video becomes a RunItem with its own status and charge at `GET /v1/runs/{id}/items`. An enriched search that finds no videos completes with the empty page and is charged the search fee, like a plain search. **Price:** $0.01 per page. `intelligence: true` adds $0.02 for each of the top 20 results ($0.005 when already enriched). Parameters: - `Idempotency-Key` (header) — 1-255 printable ASCII characters. Replaying this key with the same payload returns the original Run (202 while it is still running); a different payload, or a different operation, is a 409 `idempotency_conflict`. Body fields: - `query` (required) - `platforms` (optional) - `region` (optional) - `window` (optional) - `sort` (optional) - `limit` (optional) - `cursor` (optional) - `intelligence` (optional) — Enrich the top 20 results with Intelligence. - `wait` (optional) - `estimate` (optional) Responses: - `200` — The Run finished inside the wait budget: `{ run, result }`. With `estimate=true`, `{ estimate_usd, cached }` instead. - `202` — Still running: `{ run }`. Poll `GET /v1/runs/{id}`. - `400` — `invalid_request` (a malformed id, `wait`, `estimate` or Idempotency-Key) or `unsupported_platform`, refused before any Run opens. Nothing is charged. - `404` — A Run that opened and then failed is answered as `application/problem+json` with its problem's status — `not_found` here, `vendor_error` as 502 — and the Run envelope alongside, so the Run id is kept. A failed Run is never charged. - `402` — `insufficient_balance` — the Balance cannot cover the quoted price (the worst case for Research and enriched search). No Run is created and nothing is charged. A multi-item Run whose Balance runs short later charges its completed items up to the Balance and ends `partial`. - `409` — `idempotency_conflict` — the same Idempotency-Key with a different payload. - `429` — `rate_limit_exceeded` or `concurrency_limit_exceeded`, with `Retry-After`. Example: ```bash curl -X POST https://www.shortsintel.com/v1/videos/search \ -H "Authorization: Bearer $SHORTSINTEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"query":"cold plunge","platforms":["tiktok"],"limit":30,"sort":"likes"}' ``` ### `POST /v1/videos/lookup` — Look up a video by URL The same operation as `GET /v1/videos/{platform}/{id}`, addressed by whatever URL the user pasted. Platform and id are parsed out of the URL, so both spellings charge and cache alike. TikTok short links (`vm.tiktok.com`, `vt.tiktok.com`, `tiktok.com/t/...`) are resolved by following TikTok's redirect (https only, at most 3 hops, 5 s), after the 402 check and before the Run opens; the time spent comes out of `wait`. An estimate for a short link quotes the fresh price with `cached: false`, since the video is unknown until resolved. Idempotency matches the URL as sent, so a retry replays without resolving again. **Price:** $0.005 fresh, $0.00125 when a Snapshot under 24 h old can answer. Parameters: - `Idempotency-Key` (header) — 1-255 printable ASCII characters. Replaying this key with the same payload returns the original Run (202 while it is still running); a different payload, or a different operation, is a 409 `idempotency_conflict`. Body fields: - `url` (required) — Any TikTok, Reels or Shorts URL, including a TikTok short link. - `wait` (optional) - `estimate` (optional) Responses: - `200` — The Run finished inside the wait budget: `{ run, result }`. With `estimate=true`, `{ estimate_usd, cached }` instead. - `202` — Still running: `{ run }`. Poll `GET /v1/runs/{id}`. - `400` — `invalid_request` (a malformed id, `wait`, `estimate` or Idempotency-Key) or `unsupported_platform`, refused before any Run opens. Nothing is charged. - `404` — A Run that opened and then failed is answered as `application/problem+json` with its problem's status — `not_found` here, `vendor_error` as 502 — and the Run envelope alongside, so the Run id is kept. A failed Run is never charged. - `402` — `insufficient_balance` — the Balance cannot cover the quoted price (the worst case for Research and enriched search). No Run is created and nothing is charged. A multi-item Run whose Balance runs short later charges its completed items up to the Balance and ends `partial`. - `409` — `idempotency_conflict` — the same Idempotency-Key with a different payload. - `429` — `rate_limit_exceeded` or `concurrency_limit_exceeded`, with `Retry-After`. Example: ```bash curl -X POST https://www.shortsintel.com/v1/videos/lookup \ -H "Authorization: Bearer $SHORTSINTEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://www.tiktok.com/@someone/video/7300000000000000000"}' ``` ### `GET /v1/videos/{platform}/{id}` — Get a video TikTok today; Instagram and YouTube are refused with 400 `unsupported_platform` before any Run. Metadata, stats, the latest Snapshot and Scores where the creator has a baseline. Scores are `null` rather than fetched when no baseline exists; `POST /videos/{platform}/{id}/score` forces that fetch. Any `include` turns the call into one Video Intelligence Run (kind `video_intelligence`) charged a single price in place of the lookup price, never on top of it: `include=intelligence` (alone or with `transcript`) is the Intelligence price, `include=transcript` alone is the flat transcript price. **Price:** $0.005 fresh, $0.00125 cached. With `include=intelligence`: $0.02 fresh, $0.005 cached, instead of the lookup price (`refresh=true` is always fresh). With `include=transcript` only: a flat $0.005, cached or not. Parameters: - `platform` (path) (required) — Platform the subject lives on. - `id` (path) (required) — Platform video id. - `include` (query) — `intelligence` adds the full Intelligence object; `transcript` adds the transcript. Repeat the param or comma-separate (`include=intelligence,transcript`). Any value changes the price; see above. - `refresh` (query) — Only with `include`. `true` recomputes Intelligence even when a current one is stored, and is charged as fresh. Ignored without `include`. - `wait` (query) — Seconds to hold the response open before handing back a running Run to poll. Default 30, max 120. A POST may send it in the body instead; given in both places with different values it is a 400. - `estimate` (query) — `true` returns `{ estimate_usd, cached }` computed from Corpus state and creates no Run. Nothing is charged. Only `true` or `false`; anything else is a 400. A POST may send it in the body instead; given in both places with different values it is a 400. - `Idempotency-Key` (header) — 1-255 printable ASCII characters. Replaying this key with the same payload returns the original Run (202 while it is still running); a different payload, or a different operation, is a 409 `idempotency_conflict`. Responses: - `200` — The Run finished inside the wait budget: `{ run, result }`. With `estimate=true`, `{ estimate_usd, cached }` instead. - `202` — Still running: `{ run }`. Poll `GET /v1/runs/{id}`. - `400` — `invalid_request` (a malformed id, `wait`, `estimate` or Idempotency-Key) or `unsupported_platform`, refused before any Run opens. Nothing is charged. - `404` — A Run that opened and then failed is answered as `application/problem+json` with its problem's status — `not_found` here, `vendor_error` as 502 — and the Run envelope alongside, so the Run id is kept. A failed Run is never charged. - `402` — `insufficient_balance` — the Balance cannot cover the quoted price (the worst case for Research and enriched search). No Run is created and nothing is charged. A multi-item Run whose Balance runs short later charges its completed items up to the Balance and ends `partial`. - `409` — `idempotency_conflict` — the same Idempotency-Key with a different payload. - `429` — `rate_limit_exceeded` or `concurrency_limit_exceeded`, with `Retry-After`. Example: ```bash curl "https://www.shortsintel.com/v1/videos/tiktok/7300000000000000000" \ -H "Authorization: Bearer $SHORTSINTEL_API_KEY" ``` ### `POST /v1/videos/{platform}/{id}/intelligence` — Compute video Intelligence The full video pass: a scene pass over the media, then a structured pass producing one versioned object with `content`, `hook`, `persuasion`, `tone`, `visual` and `safety` groups. Cached forever per version. Videos with no speech still run; only unfetchable media fails, with `media_unavailable`, which is terminal: retrying the same video will not succeed. **Price:** $0.02 fresh, $0.005 cached. `refresh: true` is always charged as fresh — it is a request to redo the work. Parameters: - `platform` (path) (required) — Platform the subject lives on. - `id` (path) (required) — Platform video id. - `wait` (query) — Seconds to hold the response open before handing back a running Run to poll. Default 30, max 120. A POST may send it in the body instead; given in both places with different values it is a 400. - `estimate` (query) — `true` returns `{ estimate_usd, cached }` computed from Corpus state and creates no Run. Nothing is charged. Only `true` or `false`; anything else is a 400. A POST may send it in the body instead; given in both places with different values it is a 400. - `Idempotency-Key` (header) — 1-255 printable ASCII characters. Replaying this key with the same payload returns the original Run (202 while it is still running); a different payload, or a different operation, is a 409 `idempotency_conflict`. Body fields: - `refresh` (optional) — Recompute even when a current version is stored: the scene pass over the media runs again, not just the structured pass. Always charged as fresh. Responses: - `200` — The Run finished inside the wait budget: `{ run, result }`. With `estimate=true`, `{ estimate_usd, cached }` instead. - `202` — Still running: `{ run }`. Poll `GET /v1/runs/{id}`. - `400` — `invalid_request` (a malformed id, `wait`, `estimate` or Idempotency-Key) or `unsupported_platform`, refused before any Run opens. Nothing is charged. - `404` — A Run that opened and then failed is answered as `application/problem+json` with its problem's status — `not_found` here, `vendor_error` as 502 — and the Run envelope alongside, so the Run id is kept. A failed Run is never charged. - `402` — `insufficient_balance` — the Balance cannot cover the quoted price (the worst case for Research and enriched search). No Run is created and nothing is charged. A multi-item Run whose Balance runs short later charges its completed items up to the Balance and ends `partial`. - `409` — `idempotency_conflict` — the same Idempotency-Key with a different payload. - `429` — `rate_limit_exceeded` or `concurrency_limit_exceeded`, with `Retry-After`. ### `POST /v1/videos/{platform}/{id}/score` — Score a video Outlier multiple against the creator's median views, `outlier_p90`, views per hour and percentile within the creator's sample. Forces the creator fetch and the baseline computation. Velocity is `null` with `reason: aged_out` past 30 days. Numbers only, no labels. **Price:** $0.05, never cached — asking for a score is asking for the fetch. Parameters: - `platform` (path) (required) — Platform the subject lives on. - `id` (path) (required) — Platform video id. - `wait` (query) — Seconds to hold the response open before handing back a running Run to poll. Default 30, max 120. A POST may send it in the body instead; given in both places with different values it is a 400. - `estimate` (query) — `true` returns `{ estimate_usd, cached }` computed from Corpus state and creates no Run. Nothing is charged. Only `true` or `false`; anything else is a 400. A POST may send it in the body instead; given in both places with different values it is a 400. - `Idempotency-Key` (header) — 1-255 printable ASCII characters. Replaying this key with the same payload returns the original Run (202 while it is still running); a different payload, or a different operation, is a 409 `idempotency_conflict`. Responses: - `200` — The Run finished inside the wait budget: `{ run, result }`. With `estimate=true`, `{ estimate_usd, cached }` instead. - `202` — Still running: `{ run }`. Poll `GET /v1/runs/{id}`. - `400` — `invalid_request` (a malformed id, `wait`, `estimate` or Idempotency-Key) or `unsupported_platform`, refused before any Run opens. Nothing is charged. - `404` — A Run that opened and then failed is answered as `application/problem+json` with its problem's status — `not_found` here, `vendor_error` as 502 — and the Run envelope alongside, so the Run id is kept. A failed Run is never charged. - `402` — `insufficient_balance` — the Balance cannot cover the quoted price (the worst case for Research and enriched search). No Run is created and nothing is charged. A multi-item Run whose Balance runs short later charges its completed items up to the Balance and ends `partial`. - `409` — `idempotency_conflict` — the same Idempotency-Key with a different payload. - `429` — `rate_limit_exceeded` or `concurrency_limit_exceeded`, with `Retry-After`. ### `GET /v1/videos/{platform}/{id}/comments` — Get video comments A page of comments, optionally with a sentiment summary. Comments carry no author information at all — it is stripped at the vendor boundary, not here — and raw comments are purged after 90 days while the analysis is kept. **Price:** $0.01 per page, plus $0.05 with `analyze=true`. Each is discounted to 25% on its own cache clock. Parameters: - `platform` (path) (required) — Platform the subject lives on. - `id` (path) (required) — Platform video id. - `limit` (query) — Items per page. - `cursor` (query) — Opaque `next_cursor` from the previous page. - `analyze` (query) — Add a sentiment summary over the page. - `wait` (query) — Seconds to hold the response open before handing back a running Run to poll. Default 30, max 120. A POST may send it in the body instead; given in both places with different values it is a 400. - `estimate` (query) — `true` returns `{ estimate_usd, cached }` computed from Corpus state and creates no Run. Nothing is charged. Only `true` or `false`; anything else is a 400. A POST may send it in the body instead; given in both places with different values it is a 400. - `Idempotency-Key` (header) — 1-255 printable ASCII characters. Replaying this key with the same payload returns the original Run (202 while it is still running); a different payload, or a different operation, is a 409 `idempotency_conflict`. Responses: - `200` — The Run finished inside the wait budget: `{ run, result }`. With `estimate=true`, `{ estimate_usd, cached }` instead. - `202` — Still running: `{ run }`. Poll `GET /v1/runs/{id}`. - `400` — `invalid_request` (a malformed id, `wait`, `estimate` or Idempotency-Key) or `unsupported_platform`, refused before any Run opens. Nothing is charged. - `404` — A Run that opened and then failed is answered as `application/problem+json` with its problem's status — `not_found` here, `vendor_error` as 502 — and the Run envelope alongside, so the Run id is kept. A failed Run is never charged. - `402` — `insufficient_balance` — the Balance cannot cover the quoted price (the worst case for Research and enriched search). No Run is created and nothing is charged. A multi-item Run whose Balance runs short later charges its completed items up to the Balance and ends `partial`. - `409` — `idempotency_conflict` — the same Idempotency-Key with a different payload. - `429` — `rate_limit_exceeded` or `concurrency_limit_exceeded`, with `Retry-After`. ## Creators Creator profiles, their videos as the Corpus holds them, their baseline metrics and their follower history. ### `GET /v1/creators/{platform}/{handle}` — Get a creator Profile, follower stats, baseline (median views, p90, mean engagement rate, posts per week, sample size and confidence) and Scores. The baseline is stale after 7 days and refreshed by this lookup. **Price:** $0.02 fresh; 25% when a creator Snapshot under 24 h old and a baseline under 7 days old can answer. Parameters: - `platform` (path) (required) — Platform the subject lives on. - `handle` (path) (required) — A bare handle, an `@handle`, or a URL-encoded profile URL — whichever the user pasted. - `include` (query) — `videos` adds the creator's recent videos. - `wait` (query) — Seconds to hold the response open before handing back a running Run to poll. Default 30, max 120. A POST may send it in the body instead; given in both places with different values it is a 400. - `estimate` (query) — `true` returns `{ estimate_usd, cached }` computed from Corpus state and creates no Run. Nothing is charged. Only `true` or `false`; anything else is a 400. A POST may send it in the body instead; given in both places with different values it is a 400. - `Idempotency-Key` (header) — 1-255 printable ASCII characters. Replaying this key with the same payload returns the original Run (202 while it is still running); a different payload, or a different operation, is a 409 `idempotency_conflict`. Responses: - `200` — The Run finished inside the wait budget: `{ run, result }`. With `estimate=true`, `{ estimate_usd, cached }` instead. - `202` — Still running: `{ run }`. Poll `GET /v1/runs/{id}`. - `400` — `invalid_request` (a malformed id, `wait`, `estimate` or Idempotency-Key) or `unsupported_platform`, refused before any Run opens. Nothing is charged. - `404` — A Run that opened and then failed is answered as `application/problem+json` with its problem's status — `not_found` here, `vendor_error` as 502 — and the Run envelope alongside, so the Run id is kept. A failed Run is never charged. - `402` — `insufficient_balance` — the Balance cannot cover the quoted price (the worst case for Research and enriched search). No Run is created and nothing is charged. A multi-item Run whose Balance runs short later charges its completed items up to the Balance and ends `partial`. - `409` — `idempotency_conflict` — the same Idempotency-Key with a different payload. - `429` — `rate_limit_exceeded` or `concurrency_limit_exceeded`, with `Retry-After`. Example: ```bash curl "https://www.shortsintel.com/v1/creators/tiktok/someone?include=videos" \ -H "Authorization: Bearer $SHORTSINTEL_API_KEY" ``` ### `GET /v1/creators/{platform}/{handle}/videos` — List a creator's videos The creator's videos as the Corpus holds them, newest first, each with its Scores. A Corpus read that makes no vendor call: to get fresh videos, call the creator lookup with `include=videos` first and page here afterwards. **Price:** Free. Counts toward the per-key request limit. Parameters: - `platform` (path) (required) — Platform the subject lives on. - `handle` (path) (required) — A bare handle, an `@handle`, or a URL-encoded profile URL — whichever the user pasted. - `limit` (query) — Items per page. - `cursor` (query) — Opaque `next_cursor` from the previous page. Responses: - `200` — `{ items, next_cursor }`. - `400` — `invalid_request` for a `cursor` that is not a `next_cursor` this API issued. Nothing is charged. - `404` — `not_found` — including anything belonging to another Customer. ### `GET /v1/creators/{platform}/{handle}/snapshots` — List a creator's snapshots Follower history, newest first. Snapshots are shared across Customers and kept forever, so history predates your first lookup. **Price:** Free. Parameters: - `platform` (path) (required) — Platform the subject lives on. - `handle` (path) (required) — A bare handle, an `@handle`, or a URL-encoded profile URL — whichever the user pasted. - `limit` (query) — Items per page. - `cursor` (query) — Opaque `next_cursor` from the previous page. Responses: - `200` — `{ items, next_cursor }`. - `400` — `invalid_request` for a `cursor` that is not a `next_cursor` this API issued. Nothing is charged. - `404` — `not_found` — including anything belonging to another Customer. ## Hashtags Hashtag stats, trend direction and the videos carrying a tag. ### `GET /v1/hashtags/{platform}/{tag}` — Get a hashtag Stats, trend direction and top videos, with a Snapshot written every time so the next lookup has something to compare against. **Price:** $0.02 fresh, 25% when a Snapshot under 24 h old can answer. Parameters: - `platform` (path) (required) — Platform the subject lives on. - `tag` (path) (required) — Tag without the `#`. - `wait` (query) — Seconds to hold the response open before handing back a running Run to poll. Default 30, max 120. A POST may send it in the body instead; given in both places with different values it is a 400. - `estimate` (query) — `true` returns `{ estimate_usd, cached }` computed from Corpus state and creates no Run. Nothing is charged. Only `true` or `false`; anything else is a 400. A POST may send it in the body instead; given in both places with different values it is a 400. - `Idempotency-Key` (header) — 1-255 printable ASCII characters. Replaying this key with the same payload returns the original Run (202 while it is still running); a different payload, or a different operation, is a 409 `idempotency_conflict`. Responses: - `200` — The Run finished inside the wait budget: `{ run, result }`. With `estimate=true`, `{ estimate_usd, cached }` instead. - `202` — Still running: `{ run }`. Poll `GET /v1/runs/{id}`. - `400` — `invalid_request` (a malformed id, `wait`, `estimate` or Idempotency-Key) or `unsupported_platform`, refused before any Run opens. Nothing is charged. - `404` — A Run that opened and then failed is answered as `application/problem+json` with its problem's status — `not_found` here, `vendor_error` as 502 — and the Run envelope alongside, so the Run id is kept. A failed Run is never charged. - `402` — `insufficient_balance` — the Balance cannot cover the quoted price (the worst case for Research and enriched search). No Run is created and nothing is charged. A multi-item Run whose Balance runs short later charges its completed items up to the Balance and ends `partial`. - `409` — `idempotency_conflict` — the same Idempotency-Key with a different payload. - `429` — `rate_limit_exceeded` or `concurrency_limit_exceeded`, with `Retry-After`. ### `GET /v1/hashtags/{platform}/{tag}/videos` — List a hashtag's videos Pages the videos carrying a tag in the platform's own ordering. Always fresh: the ordering is the thing being paged, so a page cannot be served from the Corpus without answering a different question. **Price:** $0.02 per page. Parameters: - `platform` (path) (required) — Platform the subject lives on. - `tag` (path) (required) — Tag without the `#`. - `limit` (query) — Items per page. - `cursor` (query) — Opaque `next_cursor` from the previous page. - `wait` (query) — Seconds to hold the response open before handing back a running Run to poll. Default 30, max 120. A POST may send it in the body instead; given in both places with different values it is a 400. - `estimate` (query) — `true` returns `{ estimate_usd, cached }` computed from Corpus state and creates no Run. Nothing is charged. Only `true` or `false`; anything else is a 400. A POST may send it in the body instead; given in both places with different values it is a 400. - `Idempotency-Key` (header) — 1-255 printable ASCII characters. Replaying this key with the same payload returns the original Run (202 while it is still running); a different payload, or a different operation, is a 409 `idempotency_conflict`. Responses: - `200` — The Run finished inside the wait budget: `{ run, result }`. With `estimate=true`, `{ estimate_usd, cached }` instead. - `202` — Still running: `{ run }`. Poll `GET /v1/runs/{id}`. - `400` — `invalid_request` (a malformed id, `wait`, `estimate` or Idempotency-Key) or `unsupported_platform`, refused before any Run opens. Nothing is charged. - `404` — A Run that opened and then failed is answered as `application/problem+json` with its problem's status — `not_found` here, `vendor_error` as 502 — and the Run envelope alongside, so the Run id is kept. A failed Run is never charged. - `402` — `insufficient_balance` — the Balance cannot cover the quoted price (the worst case for Research and enriched search). No Run is created and nothing is charged. A multi-item Run whose Balance runs short later charges its completed items up to the Balance and ends `partial`. - `409` — `idempotency_conflict` — the same Idempotency-Key with a different payload. - `429` — `rate_limit_exceeded` or `concurrency_limit_exceeded`, with `Retry-After`. ## Sounds Sound and music metadata, usage counts and the videos using a track. ### `GET /v1/sounds/{platform}/{id}` — Get a sound Track metadata, the platform's own count of videos using it, sample stats, trend direction and top videos, with a Snapshot written every time. On TikTok `{id}` is the `clipId` from a `/music/-` URL — not the `music.id` carried on a video payload. **Price:** $0.02 fresh, 25% cached. Parameters: - `platform` (path) (required) — Platform the subject lives on. - `id` (path) (required) — Sound id — TikTok's `clipId`. - `wait` (query) — Seconds to hold the response open before handing back a running Run to poll. Default 30, max 120. A POST may send it in the body instead; given in both places with different values it is a 400. - `estimate` (query) — `true` returns `{ estimate_usd, cached }` computed from Corpus state and creates no Run. Nothing is charged. Only `true` or `false`; anything else is a 400. A POST may send it in the body instead; given in both places with different values it is a 400. - `Idempotency-Key` (header) — 1-255 printable ASCII characters. Replaying this key with the same payload returns the original Run (202 while it is still running); a different payload, or a different operation, is a 409 `idempotency_conflict`. Responses: - `200` — The Run finished inside the wait budget: `{ run, result }`. With `estimate=true`, `{ estimate_usd, cached }` instead. - `202` — Still running: `{ run }`. Poll `GET /v1/runs/{id}`. - `400` — `invalid_request` (a malformed id, `wait`, `estimate` or Idempotency-Key) or `unsupported_platform`, refused before any Run opens. Nothing is charged. - `404` — A Run that opened and then failed is answered as `application/problem+json` with its problem's status — `not_found` here, `vendor_error` as 502 — and the Run envelope alongside, so the Run id is kept. A failed Run is never charged. - `402` — `insufficient_balance` — the Balance cannot cover the quoted price (the worst case for Research and enriched search). No Run is created and nothing is charged. A multi-item Run whose Balance runs short later charges its completed items up to the Balance and ends `partial`. - `409` — `idempotency_conflict` — the same Idempotency-Key with a different payload. - `429` — `rate_limit_exceeded` or `concurrency_limit_exceeded`, with `Retry-After`. ### `GET /v1/sounds/{platform}/{id}/videos` — List a sound's videos Pages the videos using a sound, in the platform's own ordering. Always fresh, for the same reason as the hashtag videos page. **Price:** $0.02 per page. Parameters: - `platform` (path) (required) — Platform the subject lives on. - `id` (path) (required) — Sound id. - `limit` (query) — Items per page. - `cursor` (query) — Opaque `next_cursor` from the previous page. - `wait` (query) — Seconds to hold the response open before handing back a running Run to poll. Default 30, max 120. A POST may send it in the body instead; given in both places with different values it is a 400. - `estimate` (query) — `true` returns `{ estimate_usd, cached }` computed from Corpus state and creates no Run. Nothing is charged. Only `true` or `false`; anything else is a 400. A POST may send it in the body instead; given in both places with different values it is a 400. - `Idempotency-Key` (header) — 1-255 printable ASCII characters. Replaying this key with the same payload returns the original Run (202 while it is still running); a different payload, or a different operation, is a 409 `idempotency_conflict`. Responses: - `200` — The Run finished inside the wait budget: `{ run, result }`. With `estimate=true`, `{ estimate_usd, cached }` instead. - `202` — Still running: `{ run }`. Poll `GET /v1/runs/{id}`. - `400` — `invalid_request` (a malformed id, `wait`, `estimate` or Idempotency-Key) or `unsupported_platform`, refused before any Run opens. Nothing is charged. - `404` — A Run that opened and then failed is answered as `application/problem+json` with its problem's status — `not_found` here, `vendor_error` as 502 — and the Run envelope alongside, so the Run id is kept. A failed Run is never charged. - `402` — `insufficient_balance` — the Balance cannot cover the quoted price (the worst case for Research and enriched search). No Run is created and nothing is charged. A multi-item Run whose Balance runs short later charges its completed items up to the Balance and ends `partial`. - `409` — `idempotency_conflict` — the same Idempotency-Key with a different payload. - `429` — `rate_limit_exceeded` or `concurrency_limit_exceeded`, with `Retry-After`. ## Ads Keyword and advertiser search over the Meta and TikTok ad libraries. ### `POST /v1/ads/search` — Search ad libraries One page of the Meta or TikTok ad library, by keyword or by advertiser — exactly one of `query` or `advertiser`. Meta takes one `country`; the TikTok library is global and refuses the parameter rather than ignoring it. No enrichment. **Price:** $0.02 per page. Parameters: - `Idempotency-Key` (header) — 1-255 printable ASCII characters. Replaying this key with the same payload returns the original Run (202 while it is still running); a different payload, or a different operation, is a 409 `idempotency_conflict`. Body fields: - `library` (required) - `query` (optional) - `advertiser` (optional) - `country` (optional) — Two-letter country code. Meta only; the TikTok library is global and refuses it. - `limit` (optional) - `cursor` (optional) - `wait` (optional) - `estimate` (optional) Responses: - `200` — The Run finished inside the wait budget: `{ run, result }`. With `estimate=true`, `{ estimate_usd, cached }` instead. - `202` — Still running: `{ run }`. Poll `GET /v1/runs/{id}`. - `400` — `invalid_request` (a malformed id, `wait`, `estimate` or Idempotency-Key) or `unsupported_platform`, refused before any Run opens. Nothing is charged. - `404` — A Run that opened and then failed is answered as `application/problem+json` with its problem's status — `not_found` here, `vendor_error` as 502 — and the Run envelope alongside, so the Run id is kept. A failed Run is never charged. - `402` — `insufficient_balance` — the Balance cannot cover the quoted price (the worst case for Research and enriched search). No Run is created and nothing is charged. A multi-item Run whose Balance runs short later charges its completed items up to the Balance and ends `partial`. - `409` — `idempotency_conflict` — the same Idempotency-Key with a different payload. - `429` — `rate_limit_exceeded` or `concurrency_limit_exceeded`, with `Retry-After`. ## Trackers Daily watches on a creator, with Snapshots kept forever and a Change Report only when something notable moves. Creator Trackers are the only kind available today. ### `POST /v1/trackers` — Start tracking a target Creates a daily creator Tracker and runs its first cycle immediately, so you have a baseline at once. Creating twice on the same target is idempotent: it returns 200 with the existing Tracker, is not a conflict, and is not charged. **Price:** The first cycle is charged on create: $0.05, plus $0.02 per new video when `intelligence` is on. Each later daily cycle costs the same. Body fields: - `kind` (required) — Only `creator` today. `video`, `hashtag` and `sound` are refused with 422 `unsupported_kind`. - `platform` (required) — Facebook is refused with 400 `unsupported_platform`. - `target` (required) — The creator's handle. - `intelligence` (optional) — Run Intelligence on each new video the cycle finds. Responses: - `201` — `{ tracker, created: true, run, change_report }` — newly created, with its first (charged) cycle. - `200` — `{ tracker, created: false }` — you were already tracking that target. Not charged. - `400` — `unsupported_platform` — Facebook, or a platform this API does not serve. - `402` — `insufficient_balance` — the Balance cannot pay for the first cycle. Nothing is created. - `409` — `tracker_limit_exceeded` at 100 active Trackers. - `422` — `unsupported_kind` — `video`, `hashtag` or `sound`. Only creator Trackers are available today; nothing is created or charged. Example: ```bash curl -X POST https://www.shortsintel.com/v1/trackers \ -H "Authorization: Bearer $SHORTSINTEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"kind":"creator","platform":"tiktok","target":"someone","intelligence":true}' ``` ### `GET /v1/trackers` — List trackers The Customer's Trackers, newest first. **Price:** Free. Parameters: - `limit` (query) — Items per page. - `cursor` (query) — Opaque `next_cursor` from the previous page. - `state` (query) — `active` or `paused`. - `kind` (query) — `creator`, `video`, `hashtag` or `sound`. Responses: - `200` — `{ items, next_cursor }`. - `400` — `invalid_request` for a `cursor` that is not a `next_cursor` this API issued. Nothing is charged. - `404` — `not_found` — including anything belonging to another Customer. ### `GET /v1/trackers/{id}` — Get a tracker One Tracker. Another Customer's id is a 404, never a 403. **Price:** Free. Parameters: - `id` (path) (required) — Tracker id. Responses: - `200` — `{ tracker }`. - `404` — `not_found`. ### `PATCH /v1/trackers/{id}` — Pause or resume a tracker Sets the Tracker's state. A paused Tracker runs no cycles and is charged nothing. **Price:** Free. Parameters: - `id` (path) (required) — Tracker id. Body fields: - `state` (required) Responses: - `200` — `{ tracker }`. - `409` — `tracker_limit_exceeded` when resuming would exceed the cap. ### `DELETE /v1/trackers/{id}` — Stop tracking Stops the cycles. Snapshots and Change Reports are kept, and the Tracker's change reports stay readable at `GET /v1/trackers/{id}/changes` after deletion. **Price:** Free. Parameters: - `id` (path) (required) — Tracker id. Responses: - `200` — `{ tracker }` with state `deleted`. ### `POST /v1/trackers/{id}/refresh` — Run a tracker cycle now Runs the Tracker's cycle immediately, forcing the vendor call rather than reading a fresh Snapshot. `change_report` is null on a quiet cycle. **Price:** $0.05, plus $0.02 per new video when the Tracker opted into Intelligence. Same price as a scheduled cycle. Parameters: - `id` (path) (required) — Tracker id. - `Idempotency-Key` (header) — 1-255 printable ASCII characters. Replaying this key with the same payload returns the original Run (202 while it is still running); a different payload, or a different operation, is a 409 `idempotency_conflict`. Responses: - `200` — `{ run, tracker, change_report }`. A replayed Idempotency-Key returns the original Run and is not charged again. - `402` — `insufficient_balance`. - `409` — `idempotency_conflict` — the same Idempotency-Key with a different payload. ### `GET /v1/trackers/{id}/snapshots` — List a tracker's snapshots The shared Corpus Snapshots of this Tracker's target, newest first — not copies owned by this Tracker, so rows written by another Customer's cycle or by a plain lookup are all here. **Price:** Free. Parameters: - `id` (path) (required) — Tracker id. - `limit` (query) — Items per page. - `cursor` (query) — Opaque `next_cursor` from the previous page. Responses: - `200` — `{ items, next_cursor }`. - `400` — `invalid_request` for a `cursor` that is not a `next_cursor` this API issued. Nothing is charged. - `404` — `not_found` — including anything belonging to another Customer. ### `GET /v1/trackers/{id}/changes` — List change reports This Tracker's Change Reports, newest first, readable after the Tracker is deleted. Each item is `{ id, tracker_id, run_id, cycle_key, triggers, changes, summary, created_at }`. `changes` holds one key per tripped trigger, each with before/after numbers: `followers` `{ before, after, change, change_pct }`; `new_videos` `{ since, count, videos }`; `outliers` and `velocity` lists whose entries carry `before` (null for a video new to the Tracker) and `after`. Quiet cycles are absent by design: if a day produced no report, nothing tripped that day. **Price:** Free. Parameters: - `id` (path) (required) — Tracker id. - `limit` (query) — Items per page. - `cursor` (query) — Opaque `next_cursor` from the previous page. Responses: - `200` — `{ items, next_cursor }`. - `400` — `invalid_request` for a `cursor` that is not a `next_cursor` this API issued. Nothing is charged. - `404` — `not_found` — including anything belonging to another Customer. ## Runs Every operation creates a Run. Re-read one to poll a long operation or to fetch a result you already paid for; re-reads are always free. ### `GET /v1/runs` — List runs The Customer's recent Runs with their charges, newest first. **Price:** Free. Parameters: - `limit` (query) — Items per page. - `cursor` (query) — Opaque `next_cursor` from the previous page. - `status` (query) — `running`, `completed`, `partial` or `failed`. - `kind` (query) — Filter by run kind. Responses: - `200` — `{ items, next_cursor }`. - `400` — `invalid_request` for a `cursor` that is not a `next_cursor` this API issued. Nothing is charged. - `404` — `not_found` — including anything belonging to another Customer. ### `GET /v1/runs/{id}` — Get a run Re-reads one Run in the same envelope the original call returned. This is both the poll endpoint for a Run that outlived its wait budget and the permanent free re-read; it never charges and never re-runs the work. Another Customer's Run id is a 404, not a 403. **Price:** Free. Parameters: - `id` (path) (required) — Run id. Responses: - `200` — `{ run, result }` for a completed or partial Run. - `202` — `{ run }` while the Run is still running. - `404` — `not_found`. A Run that itself failed is also answered as `application/problem+json` with its problem's status (404, 502, ...), the problem members beside the unchanged Run envelope. Example: ```bash curl "https://www.shortsintel.com/v1/runs/run_123" \ -H "Authorization: Bearer $SHORTSINTEL_API_KEY" ``` ### `GET /v1/runs/{id}/items` — List run items The items a Run delivered, each with its own status, charge and — when it failed — the problem body explaining why. This is how a `partial` Run says what did and did not arrive. **Price:** Free: the charge happened once, when the work did. Parameters: - `id` (path) (required) — Run id. - `limit` (query) — Items per page. - `cursor` (query) — Opaque `next_cursor` from the previous page. - `type` (query) — `videos`, `creators`, `hashtags` or `sounds` (singular accepted too). Responses: - `200` — `{ items, next_cursor }`. - `400` — `invalid_request` for a `cursor` that is not a `next_cursor` this API issued. Nothing is charged. - `404` — `not_found` — including anything belonging to another Customer. ## Account Prepaid balance and the spend log. ### `GET /v1/account/balance` — Get balance The Customer's prepaid dollar Balance and when any starter credit expires. **Price:** Free. Responses: - `200` — `{ balance_usd, currency, starter_credit_expires_at }`. Example: ```bash curl https://www.shortsintel.com/v1/account/balance \ -H "Authorization: Bearer $SHORTSINTEL_API_KEY" ``` ### `GET /v1/account/usage` — Get usage The spend log, newest first: one row per charged Run (its `charge` ledger entry). Top-ups and other credits are not spend and are not listed; the Balance itself is `GET /v1/account/balance`. Each row is `{ id, kind, amount_usd, balance_after_usd, created_at, run }`; `amount_usd` is signed as stored (a charge is negative) and `balance_after_usd` is the Balance once it applied. `run` is `{ id, kind, status, charge_usd, list_price_usd, cached }`, so list price against charged price can be audited per Run. `total_charged_usd` is every charge on the account, not just this page. Amounts are numbers, in USD. **Price:** Free. Parameters: - `limit` (query) — Items per page. - `cursor` (query) — Opaque `next_cursor` from the previous page. Responses: - `200` — `{ items, next_cursor, total_charged_usd }`. - `400` — `invalid_request` for a `limit` out of range or a `cursor` this API did not issue. ## Webhooks Signed HTTP callbacks for `run.completed` and `tracker.changed`, with a delivery log and replay. The signature header, event types and payload are described under 'Webhook delivery and verification' in llms-full.txt and in the OpenAPI `webhooks` section. ### `POST /v1/webhooks` — Register a webhook endpoint Registers an endpoint and immediately proves it with a signed `webhook.test` event. The endpoint is `active` when the test was acknowledged with a 2xx and `failed` when it was not; a failed endpoint receives no events until it is deleted and re-registered. The `secret` appears in this response and nowhere else, ever. **Price:** Free. Body fields: - `url` (required) — HTTPS only; private ranges are refused. - `description` (optional) Responses: - `201` — `{ webhook, secret, test_delivery }`. - `400` — `invalid_request` for http, private ranges or an unresolvable host. - `409` — `webhook_limit_exceeded` at 5 endpoints. ### `GET /v1/webhooks` — List webhook endpoints The Customer's endpoints, newest first. Secrets are never included. **Price:** Free. Parameters: - `limit` (query) — Items per page. - `cursor` (query) — Opaque `next_cursor` from the previous page. Responses: - `200` — `{ items, next_cursor }`. - `400` — `invalid_request` for a `cursor` that is not a `next_cursor` this API issued. Nothing is charged. - `404` — `not_found` — including anything belonging to another Customer. ### `GET /v1/webhooks/{id}` — Get a webhook endpoint One endpoint. **Price:** Free. Parameters: - `id` (path) (required) — Webhook endpoint id. Responses: - `200` — `{ webhook }`. - `404` — `not_found`. ### `DELETE /v1/webhooks/{id}` — Delete a webhook endpoint Stops delivering and frees a slot. The endpoint is tombstoned rather than removed, so the delivery history stays readable afterwards. **Price:** Free. Parameters: - `id` (path) (required) — Webhook endpoint id. Responses: - `200` — `{ webhook }`. ### `POST /v1/webhooks/{id}/rotate` — Rotate a signing secret Mints a new signing secret. The old one keeps working for 24 h, and during that window every delivery is signed with both: the header carries two `v1=` values over the same `t` and body. A verifier written the documented way — does any `v1` match? — needs no change at all. **Price:** Free. Parameters: - `id` (path) (required) — Webhook endpoint id. Responses: - `200` — `{ webhook, secret, overlap_expires_at }` — `secret` shown once. - `409` — `webhook_rotation_in_progress` while a previous overlap is open. ### `GET /v1/webhooks/{id}/deliveries` — List deliveries What we sent and how it went, newest first. The row carries the response status and error text but not the payload — the payload is reachable through the event's own `links`. **Price:** Free. Parameters: - `id` (path) (required) — Webhook endpoint id. - `limit` (query) — Items per page. - `cursor` (query) — Opaque `next_cursor` from the previous page. - `status` (query) — `pending`, `delivered` or `failed`. Responses: - `200` — `{ items, next_cursor }`. - `400` — `invalid_request` for a `cursor` that is not a `next_cursor` this API issued. Nothing is charged. - `404` — `not_found` — including anything belonging to another Customer. ### `POST /v1/webhooks/{id}/deliveries/{deliveryId}/replay` — Replay a delivery Queues a new delivery row carrying the same event id and the same body. The stable event id lets a receiver that did eventually process the original recognise the replay as a duplicate; the new row keeps "we failed on Tuesday, you replayed on Thursday" visible in the log. **Price:** Free. Parameters: - `id` (path) (required) — Webhook endpoint id. - `deliveryId` (path) (required) — Delivery id to replay. Responses: - `202` — `{ delivery }` — queued. - `400` — `invalid_request` if the original is still being attempted, or the endpoint is not active. - `404` — `not_found`. ## MCP tools Endpoint: `https://www.shortsintel.com/api/mcp`, streamable HTTP, stateless. Authenticate with `Authorization: Bearer sk_live_...` or connect over OAuth 2.1. Tools wait up to 60 s inline, then hand back a run id for `get_run`. Every paid tool accepts `estimate`, and an optional `idempotency_key`: a retry with the same key returns the original Run and is not charged again. Setup for Claude.ai, Claude Code, Cursor and VS Code: https://www.shortsintel.com/docs/mcp ### `research` — Research a niche Run a Research: searches TikTok, Instagram Reels and YouTube Shorts for an intent, de-duplicates, enriches the top videos with Intelligence, ranks them and returns a Digest (themes, hooks that work, sentiment, recommended angles) plus the ranked videos, outlier creators, hashtags and sounds. Costs $0.50 for up to 50 enriched videos, then $0.01 each (at most $2.00). Takes 2 to 10 minutes: this tool waits up to 60 s, then returns a run id to poll with get_run. **Price:** $0.50 for up to 50 enriched videos, then $0.01 each, at most $2.00 ### `search_videos` — Search videos Keyword search across platforms. Returns up to 30 normalised videos per page with stats. Cheap ($0.01/page). Add intelligence=true to enrich the top 20 with Intelligence ($0.02 each, async); a search that finds no videos still completes and is charged the page fee. Instagram results are best-effort (coverage: partial). **Price:** $0.01 per page; +$0.02 per enriched video with intelligence=true (top 20) ### `get_video` — Get a video Look up one video by URL or by platform + id. Returns metadata, stats, the latest Snapshot and Scores when history exists ($0.005, $0.00125 cached). include: ['intelligence'] adds the full Intelligence object (hook, CTA, tone, visual, safety) and is charged $0.02 fresh, $0.005 cached, instead of the lookup price, not on top of it. include: ['transcript'] alone adds the transcript for a flat $0.005, also instead of the lookup price. Each call is charged one price. **Price:** $0.005 plain ($0.00125 cached); with include=intelligence $0.02 ($0.005 cached) instead; transcript only $0.005 instead ### `score_video` — Score a video Judge a video (by URL or platform + id) against its creator's baseline: outlier multiple (views vs the creator's median), velocity (views per hour since posting) and percentile. Fetches the creator's recent videos if no baseline exists ($0.05). **Price:** $0.05 ### `get_creator` — Get a creator Creator profile with follower stats, baseline metrics (median views, engagement rate, posting cadence) and Scores; pass include: ['videos'] to add recent videos ($0.02, cheaper cached). **Price:** $0.02 ### `get_hashtag_or_sound` — Get a hashtag or sound Look up a hashtag or a sound (TikTok music / Instagram audio): totals, trend direction, top videos and the latest Snapshot ($0.02, cheaper cached). **Price:** $0.02 ### `search_ads` — Search ad libraries Search the Meta or TikTok ad library by keyword or advertiser. Normalised ads with headline, media type, active flag and landing URL ($0.02/page). No enrichment. Pass exactly one of query or advertiser; the TikTok library is global and does not accept country. **Price:** $0.02 per page ### `track` — Track a target Create a daily Tracker on a creator (the only kind available today; video, hashtag and sound return unsupported_kind, and Facebook returns unsupported_platform). Creating runs the first cycle immediately and charges it ($0.05); with too little balance nothing is created and you get insufficient_balance. Each daily cycle takes a Snapshot and produces a Change Report only when something notable happens (new videos, outliers, followers +/-10%, velocity spikes). Pass refresh=true with an existing tracker_id to run a cycle now, at the same price. estimate=true quotes the base cycle price only: the +$0.02 per new video with intelligence depends on what the cycle finds. idempotency_key applies to refresh only; create is already idempotent by target. **Price:** $0.05 per cycle, including the first one run on create; +$0.02 per new video with intelligence ### `list_trackers` — List trackers List this Customer's Trackers with state, kind and next cycle time. Free. **Price:** Free ### `get_changes` — Get change reports Change Reports for a Tracker: new videos, outliers, follower moves and velocity spikes with before/after numbers. Free. **Price:** Free ### `get_run` — Get a run Poll a Run by id. Returns status and, once completed or partial, the full result. Free, and never re-runs the work. Poll every 5 to 15 s for research, 2 to 5 s for enrichment. **Price:** Free ### `get_usage` — Get usage Recent charges, one per charged Run, with the Run behind each one: kind, status, charge, list price and when, plus the total charged. Use it to see what has already been paid for before repeating work. Free. **Price:** Free ### `get_balance` — Get balance Prepaid balance in USD, currency and when any starter credit expires. Free. **Price:** Free ## Webhook delivery and verification Register an endpoint with `POST /v1/webhooks`; the response shows its `whsec_...` secret once. Events: - `run.completed` — A Run reached a terminal state: completed, partial or failed. The type is `run.completed` for all three; branch on `status` in the payload. - `tracker.changed` — A Tracker cycle produced a Change Report. Quiet cycles send nothing. - `webhook.test` — Sent once when an endpoint is registered, to prove it. A 2xx makes the endpoint `active`. - Each delivery is a `POST` with `Content-Type: application/json` and `User-Agent: ShortsIntel-Webhooks/1`. - `X-ShortsIntel-Signature: t=,v1=` — `v1` is HMAC-SHA256, keyed with your endpoint secret (`whsec_...`), over the string `"."`. Verify against the raw bytes before parsing JSON. - Accept the delivery when ANY `v1` matches: during the 24 h after a secret rotation the header carries two `v1` values (new first), one per secret. Reject a `t` more than 300 s from your clock to refuse replays. - `X-ShortsIntel-Event-Id` repeats the event id and `X-ShortsIntel-Event-Type` the dotted event type, so you can route and dedupe without parsing the body. - The payload is thin: identifiers and links, never a Run's result. Fetch the detail with `links`. - Only a 2xx acknowledges. Each attempt has a 10 s timeout and redirects are not followed. Three attempts in all: immediately, then about 10 minutes and about 60 minutes after the first. After that the delivery is `failed` and can be replayed with `POST /v1/webhooks/{id}/deliveries/{deliveryId}/replay`, which re-sends the same event id and body with a fresh signature. Payload fields: - `id` (string) — Event id, `evt_...`. Stable for one fact: a replay or a re-emission carries the same id, so dedupe on it. - `type` (string) — `run.completed`, `tracker.changed` or `webhook.test`. - `created_at` (string (date-time)) — When the event was created. - `run_id` (string | null) — The Run behind the event. - `kind` (string | null) — The Run's kind, lower case, e.g. `research`. - `status` (string | null) — `completed`, `partial` or `failed`. - `charge_usd` (number | null) — What the Run cost, as `GET /v1/runs/{id}` shows it. - `tracker_id` (string | null) — On `tracker.changed`. - `change_report_id` (string | null) — On `tracker.changed`. - `links` (object) — Absolute URLs to fetch the detail with your own key: `run`, `tracker`, `changes`, and `webhook` on a test event. Verify in Node: ```js import { createHmac, timingSafeEqual } from "node:crypto"; function verify(rawBody, header, secret) { if (!header) return false; const parts = header.split(",").map((p) => p.trim().split("=")); const t = Number(parts.find(([k]) => k === "t")?.[1]); if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false; const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest(); return parts .filter(([k]) => k === "v1") .some(([, v]) => { const got = Buffer.from(v, "hex"); return got.length === expected.length && timingSafeEqual(got, expected); }); } ``` ## Error codes - `missing_authorization` — HTTP 401 - `invalid_key` — HTTP 401 - `key_revoked` — HTTP 401 - `invalid_token` — HTTP 401 - `organization_not_selected` — HTTP 403 - `organization_access_revoked` — HTTP 403 - `connection_revoked` — HTTP 401 - `invalid_request` — HTTP 400 - `unsupported_platform` — HTTP 400 - `unsupported_kind` — HTTP 422 - `not_found` — HTTP 404 - `idempotency_conflict` — HTTP 409 - `insufficient_balance` — HTTP 402 - `rate_limit_exceeded` — HTTP 429 - `concurrency_limit_exceeded` — HTTP 429 - `tracker_limit_exceeded` — HTTP 409 - `webhook_limit_exceeded` — HTTP 409 - `webhook_rotation_in_progress` — HTTP 409 - `media_unavailable` — HTTP 502 - `vendor_error` — HTTP 502 - `internal_error` — HTTP 500 ## Terms and data - ShortsIntel processes public short-form video data under legitimate interest. See the privacy policy. - Media files are deleted after the Intelligence pass. Raw comments are purged after 90 days; the analysis is kept and never carries usernames. - Derived works — the analyses, reports, scores and dashboards you build on the output — are allowed, including commercially. - Republishing bulk raw records, reselling or mirroring the corpus, and using the data to target or profile individuals are prohibited. Vendor restrictions pass through. Contact: support@shortsintel.com Terms: https://www.shortsintel.com/terms — Privacy: https://www.shortsintel.com/privacy