# Authentication and API keys Canonical URL: https://docs.viralspy.com/docs/authentication ## API keys [#api-keys] REST requests use a bearer API key: ```http Authorization: Bearer vsp_live_prefix_secret ``` Keys are bound to both a user and an organisation. If the user belongs to several organisations, create or reveal a separate key while the intended organisation is selected. ViralSpy stores only a versioned HMAC digest, shows the secret once, masks prefixes in the UI, and records reveal, create, rotate, revoke, and use events. Available scopes are: * `content:read` — videos, feeds, creators, advertisers, trends, and usage. * `agent:request` — the ViralSpy analyst. This is optional when creating additional keys. ## Safe storage [#safe-storage] * Put keys in a server-side secret manager or environment variable. * Never commit keys, embed them in browser JavaScript, place them in URLs, or paste them into support messages. * Use one key per deployed workload. Names make audit and revocation safer. * Rotate immediately if a key may have been exposed. Rotation invalidates the old secret. ## MCP authentication [#mcp-authentication] During the API beta, connect MCP clients with an API key stored in an environment variable. This works with Codex, Claude Code, and any Streamable HTTP client that supports bearer headers. ViralSpy also supports OAuth 2.1 with PKCE for explicitly registered clients. It publishes Protected Resource Metadata and validates issuer, the exact `https://api.viralspy.com/mcp` audience, token type, asymmetric signature, client ID, user, organisation grant, membership, and subscription on every request. Public dynamic client registration is deliberately disabled during beta to prevent unreviewed clients from creating phishing or consent-spam flows. If you are building an interactive integration that needs OAuth, contact support to register the client and redirect URIs. The consent screen names the organisation and capabilities explicitly. Ordinary application sessions and MCP OAuth tokens have different audiences and database roles. An app session is rejected by the MCP endpoint, and an MCP token cannot inherit the app's database privileges. # Errors and retries Canonical URL: https://docs.viralspy.com/docs/errors REST errors use `application/problem+json` and stable machine-readable `code` values. ```json { "type": "https://docs.viralspy.com/problems/rate_limited", "title": "Rate limit reached. Slow down and retry.", "status": 429, "code": "rate_limited", "request_id": "c45257cd-1e55-4550-b92f-e98ee450ee29" } ``` | Status | Retry? | Meaning | | --------: | ------- | ------------------------------------------------------------------------------ | | 400 / 422 | No | Fix request parameters or JSON. | | 401 | No | Missing, malformed, expired, or revoked credential. | | 403 | No | Scope, organisation entitlement, subscription, or OAuth grant is insufficient. | | 404 | No | Resource or route was not found. | | 409 | No | An idempotency key was reused with different analyst input. | | 429 | Yes | Wait for `Retry-After`; do not busy-loop. | | 502 / 503 | Usually | Retry with backoff and the same analyst idempotency key. | ## Analyst idempotency [#analyst-idempotency] Every `POST /v1/agent/answers` requires a caller-generated `Idempotency-Key` of 8–200 characters. Reuse it only for the same body. If the first request is still running, ViralSpy returns `202` plus a status URL. If it completed, the saved result is returned without consuming a second analyst request. ```bash curl 'https://api.viralspy.com/v1/agent/answers' \ --header "Authorization: Bearer $VIRALSPY_API_KEY" \ --header 'Content-Type: application/json' \ --header 'Idempotency-Key: 02d55b8e-075b-48d1-9379-72c6a534264d' \ --data '{"question":"Compare the strongest recent skincare hooks."}' ``` # Build with ViralSpy Canonical URL: https://docs.viralspy.com/docs ViralSpy gives software and coding agents the same core discovery and research capabilities as the application: videos, ranked feeds, creators, advertisers, trend reports, and the ViralSpy analyst.
REST API Build predictable data workflows from any language with an OpenAPI 3.1 contract. MCP server Give Claude, Codex, and other MCP clients purpose-built tools and research prompts. API reference Browse typed parameters, responses, errors, and copyable request examples.
## Product limits [#product-limits] * Search and discovery have no monthly plan quota. Automated access is still protected by burst, per-key, per-organisation, and daily fair-use limits. * The ViralSpy analyst uses the organisation's shared monthly allowance. New organisations start with 100 analyst requests per month. * API keys belong to one user in one organisation. A key never silently follows the user's active organisation. ## What MCP adds [#what-mcp-adds] MCP is the tool interface used by an AI client. ViralSpy describes each operation with a strict schema, adds short instructions and workflow prompts, and returns structured data directly to the client. Your client's model decides when to call a tool; ViralSpy does not receive the rest of your conversation unless the client includes it in a tool argument. For deterministic application code, use the REST API. For agent-driven research, use MCP. Both are backed by the same resource model and limits. # API quickstart Canonical URL: https://docs.viralspy.com/docs/quickstart ## 1. Reveal your default key [#1-reveal-your-default-key] Every organisation membership has a one-time **Default** key slot. Open [API settings](https://app.viralspy.com/settings/api), confirm the organisation shown, then reveal the key. Store it in a secret manager; ViralSpy only stores an HMAC digest and cannot show the secret again. ```bash export VIRALSPY_API_KEY='vsp_live_…' ``` ## 2. Search videos [#2-search-videos] ```bash curl --get 'https://api.viralspy.com/v1/videos' \ --header "Authorization: Bearer $VIRALSPY_API_KEY" \ --data-urlencode 'q=protein snack' \ --data-urlencode 'hook=yes' \ --data-urlencode 'sort=trending' \ --data-urlencode 'limit=5' ``` ```js const url = new URL('https://api.viralspy.com/v1/videos'); url.search = new URLSearchParams({ q: 'protein snack', hook: 'yes', sort: 'trending', limit: '5' }); const response = await fetch(url, { headers: { Authorization: `Bearer ${process.env.VIRALSPY_API_KEY}` }, }); if (!response.ok) throw new Error(await response.text()); const page = await response.json(); console.log(page.data, page.meta.next_cursor); ``` ```python import os import requests response = requests.get( "https://api.viralspy.com/v1/videos", headers={"Authorization": f"Bearer {os.environ['VIRALSPY_API_KEY']}"}, params={"q": "protein snack", "hook": "yes", "sort": "trending", "limit": 5}, timeout=30, ) response.raise_for_status() page = response.json() print(page["data"], page["meta"]["next_cursor"]) ``` ```php 'protein snack', 'hook' => 'yes', 'sort' => 'trending', 'limit' => 5]); $context = stream_context_create(['http' => ['header' => "Authorization: Bearer " . getenv('VIRALSPY_API_KEY')]]); $page = json_decode(file_get_contents("https://api.viralspy.com/v1/videos?$query", false, $context), true); print_r($page['data']); ``` ```go req, _ := http.NewRequest("GET", "https://api.viralspy.com/v1/videos?q=protein+snack&hook=yes&limit=5", nil) req.Header.Set("Authorization", "Bearer "+os.Getenv("VIRALSPY_API_KEY")) res, err := http.DefaultClient.Do(req) if err != nil { log.Fatal(err) } defer res.Body.Close() var page map[string]any if err := json.NewDecoder(res.Body).Decode(&page); err != nil { log.Fatal(err) } ``` ## 3. Paginate safely [#3-paginate-safely] When `meta.has_more` is true, send `meta.next_cursor` back unchanged. Cursors are signed, expire after 24 hours, bind to the original query, and cap a single traversal at 20 pages. Do not parse or construct them. ## Response shape [#response-shape] ```json { "data": [{ "id": "755…", "desc": "…", "play_count": 1834000 }], "meta": { "count": 1, "total": 241, "has_more": true, "next_cursor": "eyJ…" }, "request_id": "9f5f1b55-7c04-4a99-84a5-fce7f2fb6ca6" } ``` Keep `request_id` when contacting support. See [authentication](/docs/authentication), [rate limits](/docs/rate-limits), and the [API reference](/docs/api/reference). # Limits and fair use Canonical URL: https://docs.viralspy.com/docs/rate-limits Search is unlimited as a subscription entitlement: it has no monthly usage counter. To keep that promise useful to everyone, the API prevents flooding and bulk corpus extraction. | Control | Default | Applies to | | ------------------- | ---------------------------------: | ----------------------- | | Burst | 15 requests / 10 seconds | API key or OAuth client | | Sustained key rate | 60 requests / minute | API key or OAuth client | | Organisation rate | 120 requests / minute | Entire organisation | | Search fair use | 2,000 content calls / day | Entire organisation | | Analyst hourly | 10 / credential, 20 / organisation | Analyst requests | | Analyst concurrency | 2 running | Entire organisation | | Analyst monthly | 100 initially | Entire organisation | The daily search ceiling is an anti-scraping protection, not a billable quota. Contact support before a legitimate integration approaches it; approved workloads can be reviewed without changing the product's “unlimited search” entitlement. ## Handling 429 responses [#handling-429-responses] Respect `Retry-After`, add exponential backoff with jitter, and cap concurrency. Repeating the same request more aggressively will extend disruption and may trigger abuse controls. ```js const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); for (let attempt = 0; attempt < 4; attempt++) { const response = await fetch(url, options); if (response.status !== 429) return response; const retryAfter = Number(response.headers.get('retry-after') ?? 1); await delay((retryAfter * 1000) + Math.random() * 500); } ``` Analyst responses include `X-Agent-Limit`, `X-Agent-Remaining`, and `X-Agent-Reset`. `/v1/usage` and the MCP `viralspy_get_usage` tool return the same organisation-level view. # Security model Canonical URL: https://docs.viralspy.com/docs/security ## Boundaries [#boundaries] * API keys are high-entropy one-time secrets stored as versioned HMAC digests. * Each key is bound to a user membership and one explicit organisation. * OAuth access tokens must be asymmetrically signed, short-lived, issued by the configured Supabase issuer, and audience-bound to `https://api.viralspy.com/mcp`. * A custom access-token hook gives MCP tokens a restricted `viralspy_mcp` database role and a distinct token-type claim. The API rechecks the stored OAuth grant and membership. * The MCP server never passes the client's bearer token to Typesense, Supabase data APIs, or the web analyst. Internal service calls use separate credentials and a timestamped HMAC bridge. ## Request protection [#request-protection] The edge enforces failed-authentication, burst, per-credential, and per-organisation limits before expensive work. Supabase provides an authoritative cross-isolate fair-use ledger. Signed cursors bind pagination to a query and expire after 24 hours. Analyst calls require idempotency, claim concurrency before work begins, record terminal status, and stream heartbeats. Identical in-flight work returns status rather than running twice. ## Client responsibilities [#client-responsibilities] * Keep secrets on trusted servers or in environment-backed client stores. * Grant `agent:request` only to workloads that need it. * Rotate keys on staff or system changes and revoke unused credentials. * Validate responses before using fields in automated decisions. * Treat TikTok and ViralSpy-derived intelligence according to applicable platform terms and law. For a suspected credential leak, revoke or rotate the key in [API settings](https://app.viralspy.com/settings/api) immediately, then contact support with request IDs—never the secret itself. # REST API overview Canonical URL: https://docs.viralspy.com/docs/api The production base URL is: ```text https://api.viralspy.com/v1 ``` ## Resources [#resources] * `/videos` and `/feeds/{feed}` expose classified creative and ranked discovery. * `/creators` uses immutable TikTok author UID as the canonical ID. Handles remain searchable but mutable. * `/advertisers` uses ViralSpy canonical UUIDs to avoid handle and display-name ambiguity. * `/trends` exposes published trend reports and examples. * `/agent/answers` streams the same evidence-backed analyst as the application. * `/usage` reports shared organisation analyst usage and fair-use settings. ## Versioning [#versioning] Breaking changes use a new URL version. Additive fields and endpoints may arrive within `v1`; clients should ignore unknown response fields. Input objects are strict so misspelled filters fail loudly. ## Analyst streaming [#analyst-streaming] Successful new analyst requests return Server-Sent Events. Heartbeat events keep intermediaries alive; the terminal `final` event contains the answer. A duplicate in-flight idempotency key returns `202` rather than opening a second stream. Clients that explicitly send `Accept: application/json` receive the completed answer as JSON, but must allow for the full analyst runtime. ## Contract downloads [#contract-downloads] * [OpenAPI 3.1 from the API](https://api.viralspy.com/openapi.json) * [OpenAPI 3.1 from this docs site](/openapi.json) * [LLM documentation index](/llms.txt) * [Complete agent-readable documentation](/llms-full.txt) # Build a research workflow Canonical URL: https://docs.viralspy.com/docs/guides/agent The strongest workflow separates retrieval from synthesis. ## 1. Retrieve narrowly [#1-retrieve-narrowly] Search with a clear subject and a small `limit`. Use ranked feeds when you need discovery without a text query. Add ad or hook filters only when they express a real constraint. ## 2. Resolve canonical entities [#2-resolve-canonical-entities] Save video aweme IDs, creator UIDs, and advertiser UUIDs. Handles and display names can change or collide. Canonical IDs keep bookmarks, caches, and joins stable. ## 3. Inspect evidence [#3-inspect-evidence] Fetch detail records and representative videos. Record the time window and URLs used. ViralSpy classifications can guide research, but a model-assisted label should not be presented as a legal determination. ## 4. Synthesize once [#4-synthesize-once] Call the analyst after you have a focused question. Supply relevant context, use an idempotency key, and retain the final evidence-bearing result. This consumes one shared organisation request; primitive search does not. ```js const idempotencyKey = crypto.randomUUID(); const response = await fetch('https://api.viralspy.com/v1/agent/answers', { method: 'POST', headers: { Authorization: `Bearer ${process.env.VIRALSPY_API_KEY}`, Accept: 'text/event-stream', 'Content-Type': 'application/json', 'Idempotency-Key': idempotencyKey, }, body: JSON.stringify({ question: 'Which three hook mechanics recur across these examples?', context: 'Video IDs: 755…, 754…, 753…', }), }); ``` If the connection drops, retry with the same key or fetch `/v1/agent/answers/{id}`. Do not submit a new key for the same work. SSE is the default and recommended mode for multi-minute analysis. Set `Accept: application/json` only when your HTTP client is configured to wait for the complete answer; idempotency and status polling still apply. # ViralSpy MCP Canonical URL: https://docs.viralspy.com/docs/mcp The remote Streamable HTTP endpoint is: ```text https://api.viralspy.com/mcp ``` MCP is a protocol between an AI client and ViralSpy. ViralSpy publishes tool names, descriptions, strict argument and result schemas, workflow instructions, resources, and prompt templates. Your client exposes those capabilities to its model; the model chooses calls and receives the structured results directly. ## Recommended workflow [#recommended-workflow] 1. Use a narrow search or feed tool. 2. Fetch interesting items by immutable ID. 3. Compare source fields and URLs. 4. Use `viralspy_analyze` only when the high-level analyst adds value; it consumes the shared monthly allowance. The server's first instructions emphasize this workflow so agents can make sensible calls without copying a large prompt into every conversation. Prompts are optional user-invoked templates; resources provide deeper reference material; tools perform the actual work. ## Transport and compatibility [#transport-and-compatibility] ViralSpy implements the current MCP per-request protocol and maintains stateless compatibility with 2025-era Streamable HTTP clients. Responses use an SSE stream, including keep-alive frames for long-running `viralspy_analyze` calls, followed by the terminal JSON-RPC result. During beta, API-key authentication is the default. OAuth 2.1 is available to explicitly registered clients. # Connect your MCP client Canonical URL: https://docs.viralspy.com/docs/mcp/installation ## Codex [#codex] ```bash export VIRALSPY_API_KEY='vsp_live_…' codex mcp add viralspy \ --url https://api.viralspy.com/mcp \ --bearer-token-env-var VIRALSPY_API_KEY ``` ## Claude Code [#claude-code] ```bash export VIRALSPY_API_KEY='vsp_live_…' claude mcp add-json viralspy \ '{"type":"http","url":"https://api.viralspy.com/mcp","headers":{"Authorization":"Bearer ${VIRALSPY_API_KEY}"}}' ``` The single quotes preserve the environment placeholder instead of writing the key into Claude's configuration. ## OAuth clients [#oauth-clients] OAuth 2.1 with PKCE is available to explicitly registered clients during beta. Public dynamic client registration is disabled. Integration developers can contact support with their client metadata and exact redirect URIs; after registration, the browser consent screen shows the organisation and requested capabilities before approval. ## Generic Streamable HTTP configuration [#generic-streamable-http-configuration] ```json { "mcpServers": { "viralspy": { "url": "https://api.viralspy.com/mcp", "headers": { "Authorization": "Bearer ${VIRALSPY_API_KEY}" } } } } ``` The client must support Streamable HTTP and `Authorization: Bearer`. OAuth clients must also support Protected Resource Metadata discovery and PKCE. Never put credentials in the server URL. # Resources and prompts Canonical URL: https://docs.viralspy.com/docs/mcp/resources-prompts ## Resources [#resources] | URI | Contents | | ---------------------------------- | --------------------------------------------------------------------- | | `viralspy://guide/getting-started` | The shortest safe path from discovery to evidence-backed output. | | `viralspy://guide/search` | Tool selection, pagination, filters, and immutable identifiers. | | `viralspy://guide/metrics` | How to interpret observed metrics and model-assisted classifications. | | `viralspy://guide/workflows` | Reliable multi-tool sequences for common research tasks. | | `viralspy://guide/rate-limits` | Fair use, transport protection, and analyst allowance behavior. | | `viralspy://schema/openapi` | The complete OpenAPI 3.1 REST contract. | Resources are reference context. Reading one does not execute a search or consume an analyst request. ## Prompts [#prompts] * `find_breakout_ads(topic, region?)` * `analyze_hook_pattern(topic)` * `research_advertiser(advertiser)` * `compare_creators(creators, objective?)` * `build_content_brief(niche, objective?)` Prompts produce a user message that teaches the client a reliable workflow: discover, resolve immutable IDs, fetch evidence, separate observation from inference, and retain source URLs. They are optional shortcuts, not hidden system prompts, and invoking one does not itself call ViralSpy. # MCP tools Canonical URL: https://docs.viralspy.com/docs/mcp/tools ## Content tools [#content-tools] | Tool | Use it for | | --------------------------------- | ------------------------------------------------------------------------------------- | | `viralspy_search_videos` | Filter classified videos by query, ad, hook, viral hook, media, region, and language. | | `viralspy_get_video` | Fetch a video by immutable aweme ID. | | `viralspy_find_similar_hooks` | Find semantically similar opening hooks from a strong seed. | | `viralspy_list_feed` | Browse breakout, early-breakout, trending, or newest rankings. | | `viralspy_search_creators` | Discover creators and their immutable UIDs. | | `viralspy_get_creator` | Fetch by UID; handle is a mutable discovery fallback. | | `viralspy_list_creator_videos` | Inspect a creator's videos or ads using UID. | | `viralspy_search_advertisers` | Discover canonical advertiser entities. | | `viralspy_get_advertiser` | Fetch by canonical advertiser UUID. | | `viralspy_list_advertiser_videos` | Inspect videos assigned to that canonical entity. | | `viralspy_list_trends` | Browse current trend reports. | | `viralspy_get_trend` | Fetch one report and its examples. | All content tools require `content:read` and count toward fair-use protection, not the monthly analyst allowance. ## Usage and analyst tools [#usage-and-analyst-tools] `viralspy_get_usage` returns remaining monthly analyst requests and the current search fair-use policy. `viralspy_analyze` asks the ViralSpy analyst a high-level question. It requires `agent:request` and an `idempotency_key`. Generate a UUID, then reuse it for an identical retry. The server permits two concurrent requests per organisation and returns existing status/result for a duplicate. ```text Use viralspy_analyze to compare the evidence you found for three advertisers. Do not use it merely to fetch a record that a primitive read tool can return. ``` Tool results include a short text summary and validated `structuredContent`, allowing clients to reason over stable JSON without scraping prose. # Get a canonical advertiser Canonical URL: https://docs.viralspy.com/docs/api/reference/advertisers/getAdvertiser {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List an advertiser's videos Canonical URL: https://docs.viralspy.com/docs/api/reference/advertisers/listAdvertiserVideos {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Search advertisers Canonical URL: https://docs.viralspy.com/docs/api/reference/advertisers/searchAdvertisers {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Request an evidence-backed ViralSpy analyst answer Canonical URL: https://docs.viralspy.com/docs/api/reference/analyst/createAgentAnswer {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get analyst request status or result Canonical URL: https://docs.viralspy.com/docs/api/reference/analyst/getAgentAnswer {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get a creator by immutable TikTok UID Canonical URL: https://docs.viralspy.com/docs/api/reference/creators/getCreator {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List a creator's videos Canonical URL: https://docs.viralspy.com/docs/api/reference/creators/listCreatorVideos {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Search creators Canonical URL: https://docs.viralspy.com/docs/api/reference/creators/searchCreators {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get a trend report Canonical URL: https://docs.viralspy.com/docs/api/reference/trends/getTrend {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List trend reports Canonical URL: https://docs.viralspy.com/docs/api/reference/trends/listTrends {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get organisation usage Canonical URL: https://docs.viralspy.com/docs/api/reference/usage/getUsage {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Find videos with similar hooks Canonical URL: https://docs.viralspy.com/docs/api/reference/videos/findSimilarHooks {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Get a video Canonical URL: https://docs.viralspy.com/docs/api/reference/videos/getVideo {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # List a ranked creative feed Canonical URL: https://docs.viralspy.com/docs/api/reference/videos/listFeed {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} # Search videos Canonical URL: https://docs.viralspy.com/docs/api/reference/videos/searchVideos {/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}