{"openapi": "3.1.0", "info": {"title": "Frontrun API", "version": "1.0.0", "description": "Frontrun tracks the follow graphs of 2,000+ venture investors on X and flags companies months before their round is announced. Pre-round deal signal for investors and AI agents.\n\nAPI + MCP access is included with the Pro plan ($99/mo, pay upfront, no trial on Pro, API access unlocks immediately on payment, includes 10,000 monthly credits). Top-up credit packs are available for usage beyond the monthly allowance and never expire. Get a key at https://www.frontrun.vc (Settings > API Keys).\n\nAuthenticate every request with your API key in the `X-API-Key` header. Every charged response includes `cost_credits` (credits spent on that call) and `balance_credits` (your remaining balance). A `402` response means insufficient credits; a `429` means you hit a rate limit or daily credit cap. Several expensive operations never charge for empty answers: /convergence, /search/thesis, /classify, /company/{handle}/founders, /company/{handle}/funding, /company/{handle}/signals, and /vc/{handle}/similar all return cost_credits 0 when nothing is found.\n\nRecommended agent workflow: (1) GET /status to verify the key and check balance and per-operation pricing. (2) Scan for signal with GET /trending or GET /convergence (convergence, multiple tracked investors following the same company independently, is the strongest pre-round signal). (3) Deep-dive candidates: GET /company/{handle} for the overview, then /company/{handle}/signals, /founders, and /funding. (4) For thesis-driven sourcing, GET /search/thesis with a plain-language investment thesis. Signal endpoints read from the accounts you track; manage coverage with POST /track, GET /preview/{handle}, and GET /discover.\n\nSame data over MCP: hosted server at https://www.frontrun.vc/mcp (streamable HTTP, Authorization: Bearer <api key>) or local `npx frontrun-mcp-server --login` then `--setup`. Machine-readable index: https://www.frontrun.vc/llms.txt", "contact": {"name": "Frontrun", "url": "https://frontrun.vc", "email": "hello@frontrun.vc"}}, "servers": [{"url": "https://frontrun.vc/v1"}], "security": [{"ApiKeyAuth": []}], "tags": [{"name": "Account", "description": "API key status, balance, and usage."}, {"name": "Tracking", "description": "Manage which X accounts you monitor for follow activity."}, {"name": "Follows", "description": "New-follow detection and follow snapshots."}, {"name": "Signal", "description": "Computed intelligence: convergence, trending, feed, sectors, discovery."}, {"name": "Search", "description": "Keyword and semantic search over your database."}, {"name": "Classification", "description": "AI classification, custom rules, and custom tags."}, {"name": "Company", "description": "Company deep-dives: overview, founders, signals, resources, funding."}, {"name": "Account Intelligence", "description": "Investor activity profiles and similarity."}, {"name": "Webhooks", "description": "Push-based delivery of signals."}, {"name": "Reports", "description": "Historical daily discovery reports."}], "paths": {"/status": {"get": {"operationId": "get_status", "summary": "Account status", "description": "Returns your tier, credit balance (monthly + top-up), tracked account count, daily spend, per-operation pricing at your tier rate, and a 30-day usage breakdown. Free, costs 0 credits. Call this first in any session: it verifies the key works and tells you what each operation will cost before you spend credits.", "tags": ["Account"], "responses": {"200": {"description": "Account status.", "content": {"application/json": {"schema": {"type": "object", "properties": {"tier": {"type": "string", "enum": ["free", "starter", "pro", "enterprise"]}, "mcp_access": {"type": "boolean"}, "tracked_accounts": {"type": "integer"}, "balance": {"type": "object", "properties": {"credits": {"description": "Available credits (number, or \"Unlimited\" on Enterprise).", "oneOf": [{"type": "integer"}, {"type": "string"}]}, "monthly_credits": {"oneOf": [{"type": "integer"}, {"type": "string"}]}, "topup_credits": {"type": "integer"}}, "additionalProperties": true}, "daily_spend": {"type": "object", "properties": {"credits": {"type": "integer"}, "cap_credits": {"description": "Daily cap in credits (null when unlimited).", "oneOf": [{"type": "integer"}, {"type": "null"}]}}, "additionalProperties": true}, "usage_30d": {"type": "object", "properties": {"total_calls": {"type": "integer"}, "total_credits": {"type": "integer"}, "by_operation": {"type": "object", "properties": {}, "additionalProperties": true, "description": "Per-operation `{ calls, credits }` map."}}, "additionalProperties": true}, "pricing_credits": {"type": "object", "properties": {}, "additionalProperties": true, "description": "Per-operation price list in credits at your tier rate."}, "rate_multiplier": {"type": "number", "description": "1.0 for subscribers; 2.5 on the free tier."}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/track": {"post": {"operationId": "track_account", "summary": "Track an account", "description": "Add an X account (typically an investor) to your tracked set so its new follows feed every signal endpoint (/follows/new, /follows/enriched, /convergence, /trending, /feed, /discover). Costs 4 credits when the account is new to the platform (a follow baseline is fetched); accounts already in the shared coverage pool are added free. Returns 201 with `ready` indicating whether a follow baseline already exists; while ready=false the account produces no signal until its first snapshot completes. Handles that fail to resolve on X return 404 and are never billed. New-account introductions are capped per key per day by tier (free 20, starter 100, pro 250, enterprise 2000); adding accounts already in the coverage pool does not count against the cap. Batch: send `usernames` (alias `handles`) as an array of up to 50 handles instead of a single `username` to track them in one call. The single-username shape is unchanged. The batch path always answers 200 with a per-item result because partial success is normal; read each item's `status` rather than the HTTP code. You are charged per account actually NEW to the platform, not per item submitted. At most 10 new-account introductions are attempted per request (handles beyond that return status \"deferred\" - resubmit them), and the per-tier daily introduction cap is shared across the whole batch.", "tags": ["Tracking"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"username": {"type": "string", "description": "X username to track (with or without @)."}, "usernames": {"type": "array", "items": {"type": "string"}, "maxItems": 50, "description": "Batch form: X usernames to track (with or without @). Max 50 per request; duplicates are collapsed. Mutually exclusive with `username`."}, "handles": {"type": "array", "items": {"type": "string"}, "maxItems": 50, "description": "Alias for `usernames`."}, "max_credits": {"type": "integer", "minimum": 0, "description": "Batch form only. Ceiling on what this call may cost, in credits. FAILS CLOSED before any upstream lookup: over the ceiling, nothing is tracked and nothing is charged (400, error_code max_credits_exceeded). Only handles new to the platform bill, so the actual charge is usually well under the ceiling."}}, "additionalProperties": true}}}}, "responses": {"200": {"description": "Batch result. Always 200 when an array was submitted, including when some items failed.", "content": {"application/json": {"schema": {"type": "object", "properties": {"batch": {"type": "boolean"}, "results": {"type": "array", "items": {"type": "object", "properties": {"username": {"type": "string"}, "status": {"type": "string", "enum": ["tracked", "already_tracked", "not_found", "invalid", "deferred", "intro_cap", "insufficient_credits", "tracked_limit", "upstream_unavailable", "failed"]}, "tracking": {"type": "boolean"}, "ready": {"type": "boolean"}, "follow_count": {"type": "integer"}, "new_introduction": {"type": "boolean", "description": "True when this handle was new to the platform, which is the only case that bills."}, "cost_credits": {"type": "integer"}, "error": {"type": "string"}}, "additionalProperties": true}}, "summary": {"type": "object", "additionalProperties": {"type": "integer"}, "description": "Counts keyed by status, plus `submitted`."}, "cost_credits": {"type": "integer", "description": "Total charged across the batch."}, "max_cost_credits": {"type": "integer"}, "balance_credits": {"type": "integer"}, "deferred_note": {"type": "string"}, "intro_cap_note": {"type": "string"}}, "additionalProperties": true}}}}, "201": {"description": "Tracking registered.", "content": {"application/json": {"schema": {"type": "object", "properties": {"username": {"type": "string"}, "tracking": {"type": "boolean"}, "ready": {"type": "boolean", "description": "Whether a follow baseline is already established."}, "follow_count": {"type": "integer"}, "message": {"type": "string"}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}}, "additionalProperties": true}}}}, "400": {"description": "Missing or invalid username, batch too large, or max_credits exceeded.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "404": {"description": "X account not found.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "409": {"description": "Already tracking this account.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"$ref": "#/components/responses/RateLimited"}}}, "get": {"operationId": "list_tracked_accounts", "summary": "List tracked accounts", "description": "List every account you are tracking, with readiness state (ready=false means the follow baseline is still being established, so that account is not yet producing signal). Free, costs 0 credits. Use it to verify coverage before interpreting empty results from signal endpoints.", "tags": ["Tracking"], "responses": {"200": {"description": "Tracked accounts.", "content": {"application/json": {"schema": {"type": "object", "properties": {"count": {"type": "integer"}, "accounts": {"type": "array", "items": {"type": "object", "properties": {"username": {"type": "string"}, "ready": {"type": "boolean", "description": "Whether the follow baseline is established."}, "created_at": {"type": "string", "format": "date-time"}}, "additionalProperties": true}}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/track/{username}": {"delete": {"operationId": "untrack_account", "summary": "Stop tracking an account", "description": "Remove an account from your tracked set so it stops feeding your signal endpoints. Free, costs 0 credits. Returns 404 if you are not tracking the account.", "tags": ["Tracking"], "parameters": [{"name": "username", "in": "path", "required": true, "schema": {"type": "string"}, "description": "X username to stop tracking (with or without @)."}], "responses": {"200": {"description": "Tracking removed.", "content": {"application/json": {"schema": {"type": "object", "properties": {"username": {"type": "string"}, "tracking": {"type": "boolean"}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "404": {"description": "Not tracking this account.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/follows/new": {"get": {"operationId": "get_new_follows", "summary": "New follows", "description": "Detect new follows across your tracked accounts within a time window, grouped per tracked account. Costs 16 credits. Only tracked accounts with an established baseline are checked; with no ready accounts you get an empty result plus a hint message, not an error. Set classify=true to include cached AI classification on each new follow at no extra cost. Prefer /follows/enriched (same 16-credit price) when you want sector, keyword, entity-type, or tag filters and your custom rules merged in.", "tags": ["Follows"], "parameters": [{"name": "since", "in": "query", "required": false, "schema": {"type": "string", "default": "24h", "examples": ["24h", "7d", "2026-07-01"]}, "description": "Time window: relative (\"24h\", \"7d\") or absolute date (\"YYYY-MM-DD\"). Default \"24h\"."}, {"name": "hours", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Alternative to `since` - window size in hours (e.g. `hours=48` equals `since=48h`)."}, {"name": "username", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Restrict to a single tracked account."}, {"name": "classify", "in": "query", "required": false, "schema": {"type": "string", "enum": ["true", "false"]}, "description": "Set to \"true\" to include cached AI classification on each new follow (no extra charge)."}, {"name": "offset", "in": "query", "required": false, "schema": {"type": "integer", "default": 0, "minimum": 0}, "description": "Pagination offset (default 0)."}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 50, "maximum": 200, "minimum": 1}, "description": "Maximum results to return (default 50, max 200)."}, {"name": "cursor", "in": "query", "required": false, "schema": {"type": "string", "examples": ["frc1_eyJ2IjoxLCJzIjoiOWY0YyIsImQiOiIyMDI2LTA4LTA3In0.Xq9"]}, "description": "Opaque watermark from a previous response's next_cursor. Resumes exactly where that run stopped, so a scheduled job that retries neither double counts nor drops records. Overrides since when present. Cursors are signed and scoped to the endpoint, API key and filters; a modified or cross-scope cursor returns 400 with error_code invalid_cursor. Persist next_cursor only AFTER you have processed the response."}], "responses": {"200": {"description": "New follows grouped by tracked account.", "content": {"application/json": {"schema": {"type": "object", "properties": {"since": {"type": "string", "format": "date-time"}, "accounts_checked": {"type": "integer"}, "total_new_follows": {"type": "integer"}, "total": {"type": "integer", "description": "Total tracked-account groups with new follows."}, "returned": {"type": "integer"}, "offset": {"type": "integer"}, "has_more": {"type": "boolean"}, "results": {"type": "array", "items": {"type": "object", "properties": {"tracked_account": {"type": "string"}, "as_of": {"type": "string", "description": "Snapshot date the diff is computed against."}, "new_follows_count": {"type": "integer"}, "new_follows": {"type": "array", "items": {"$ref": "#/components/schemas/FollowEntity"}}}, "additionalProperties": true}}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}, "next_cursor": {"type": ["string", "null"], "description": "Opaque cursor for the next run. Null while has_more is true, by design, not an error: the cursor marks the end of the whole matched window, so it is issued on the final page only. Call again with offset set to next_offset until has_more is false, then take next_cursor from that final page."}, "next_offset": {"type": "integer", "description": "Present while has_more is true: the offset value for the next page of this window (offset + returned)."}, "next_cursor_note": {"type": "string"}, "cursor_note": {"type": "string"}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/follows/snapshot/{username}": {"get": {"operationId": "get_follow_snapshot", "summary": "Follow snapshot", "description": "Returns the most recent stored full follow list for a tracked account (latest snapshot, not a live X fetch). Costs 4 credits. Useful for auditing what an investor already follows; for change detection use /follows/new instead. The username must be one of your tracked accounts.", "tags": ["Follows"], "parameters": [{"name": "username", "in": "path", "required": true, "schema": {"type": "string"}, "description": "Tracked account username (with or without @)."}], "responses": {"200": {"description": "Latest follow snapshot.", "content": {"application/json": {"schema": {"type": "object", "properties": {"username": {"type": "string"}, "as_of": {"type": "string", "description": "Snapshot date."}, "follow_count": {"type": "integer"}, "follows": {"type": "array", "items": {"$ref": "#/components/schemas/FollowEntity"}}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/follows/enriched": {"get": {"operationId": "get_enriched_follows", "summary": "Enriched new follows", "description": "The most capable follow-signal endpoint: new follows with AI classification plus your custom classification rules and custom tags merged into each result, filterable by sector, keyword, entity type, or custom tag. Same 16-credit price as /follows/new, so use this whenever you want filtered results (e.g. sector=AI follows from the last 7d). Filters are substring matches; valid sector strings can be discovered via /sectors.", "tags": ["Follows", "Classification"], "parameters": [{"name": "since", "in": "query", "required": false, "schema": {"type": "string", "default": "24h", "examples": ["24h", "7d", "2026-07-01"]}, "description": "Time window: relative (\"24h\", \"7d\") or absolute date (\"YYYY-MM-DD\"). Default \"24h\"."}, {"name": "hours", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Alternative to `since` - window size in hours (e.g. `hours=48` equals `since=48h`)."}, {"name": "username", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Restrict to a single tracked account."}, {"name": "sector", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Filter by classified sector (substring match)."}, {"name": "keyword", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Filter by keyword across username, name, description, and sector."}, {"name": "entity_type", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Filter by entity type (substring match)."}, {"name": "tag", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Filter by your custom tag / matched rule name."}, {"name": "offset", "in": "query", "required": false, "schema": {"type": "integer", "default": 0, "minimum": 0}, "description": "Pagination offset (default 0)."}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 50, "maximum": 200, "minimum": 1}, "description": "Maximum results to return (default 50, max 200)."}, {"name": "cursor", "in": "query", "required": false, "schema": {"type": "string", "examples": ["frc1_eyJ2IjoxLCJzIjoiOWY0YyIsImQiOiIyMDI2LTA4LTA3In0.Xq9"]}, "description": "Opaque watermark from a previous response's next_cursor. Resumes exactly where that run stopped, so a scheduled job that retries neither double counts nor drops records. Overrides since when present. Cursors are signed and scoped to the endpoint, API key and filters; a modified or cross-scope cursor returns 400 with error_code invalid_cursor. Persist next_cursor only AFTER you have processed the response."}], "responses": {"200": {"description": "Enriched new follows grouped by tracked account.", "content": {"application/json": {"schema": {"type": "object", "properties": {"since": {"type": "string", "format": "date-time"}, "accounts_checked": {"type": "integer"}, "total_new_follows": {"type": "integer"}, "total": {"type": "integer"}, "returned": {"type": "integer"}, "offset": {"type": "integer"}, "has_more": {"type": "boolean"}, "results": {"type": "array", "items": {"type": "object", "properties": {"tracked_account": {"type": "string"}, "as_of": {"type": "string"}, "new_follows_count": {"type": "integer"}, "new_follows": {"type": "array", "items": {"type": "object", "properties": {"twitter_user_id": {"type": "string"}, "username": {"type": "string"}, "name": {"type": "string"}, "description": {"type": "string"}, "verified": {"type": "boolean"}, "classification": {"$ref": "#/components/schemas/Classification"}, "custom": {"$ref": "#/components/schemas/CustomData"}}, "additionalProperties": true}}}, "additionalProperties": true}}, "filters": {"type": "object", "properties": {"sector": {"type": "string"}, "keyword": {"type": "string"}, "entity_type": {"type": "string"}, "tag": {"type": "string"}}, "additionalProperties": true}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}, "next_cursor": {"type": ["string", "null"], "description": "Opaque cursor for the next run. Null while has_more is true, by design, not an error: the cursor marks the end of the whole matched window, so it is issued on the final page only. Call again with offset set to next_offset until has_more is false, then take next_cursor from that final page."}, "next_offset": {"type": "integer", "description": "Present while has_more is true: the offset value for the next page of this window (offset + returned)."}, "next_cursor_note": {"type": "string"}, "cursor_note": {"type": "string"}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/convergence": {"get": {"operationId": "get_convergence", "summary": "Convergence detection", "description": "Detect entities that multiple tracked accounts followed independently within a time window: the strongest pre-round signal in the product. Results are ranked by how many tracked accounts converged (`followed_by`). Costs 60 credits; zero convergences cost 0 credits. `threshold` (alias `min_accounts`) sets the minimum converging accounts, default 2; use 3+ for high conviction. Typical chain: /convergence, then /company/{handle} and /company/{handle}/founders on the top hits.", "tags": ["Signal"], "parameters": [{"name": "threshold", "in": "query", "required": false, "schema": {"type": "integer", "default": 2, "minimum": 2}, "description": "Minimum number of tracked accounts that must have followed the entity (alias: min_accounts). Default 2."}, {"name": "since", "in": "query", "required": false, "schema": {"type": "string", "default": "7d", "examples": ["24h", "7d", "2026-07-01"]}, "description": "Time window: relative (\"24h\", \"7d\") or absolute date (\"YYYY-MM-DD\"). Default \"7d\"."}, {"name": "hours", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Alternative to `since` - window size in hours (e.g. `hours=48` equals `since=48h`)."}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 50, "maximum": 500, "minimum": 1}, "description": "Maximum results to return (default 50, max 500)."}], "responses": {"200": {"description": "Convergence results ranked by follower count.", "content": {"application/json": {"schema": {"type": "object", "properties": {"threshold": {"type": "integer"}, "since": {"type": "string", "format": "date-time"}, "limit": {"type": "integer"}, "total": {"type": "integer", "description": "Total entities matching the threshold."}, "returned": {"type": "integer"}, "convergences": {"type": "array", "items": {"type": "object", "properties": {"twitter_user_id": {"type": "string"}, "username": {"type": "string"}, "name": {"type": "string"}, "description": {"type": "string"}, "followed_by": {"type": "array", "items": {"type": "string"}, "description": "Tracked accounts that followed this entity in the window."}}, "additionalProperties": true}}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/trending": {"get": {"operationId": "get_trending", "summary": "Trending companies", "description": "Entities ranked by how many of your tracked accounts followed them within the window: the standard first scan of the day. Costs 24 credits. classify=true attaches cached AI classification and adds 16 credits per classified entity returned, so keep `limit` small when classifying. Returns an empty hint response if you track no accounts; add coverage with POST /track first.", "tags": ["Signal"], "parameters": [{"name": "since", "in": "query", "required": false, "schema": {"type": "string", "default": "7d", "examples": ["24h", "7d", "2026-07-01"]}, "description": "Time window: relative (\"24h\", \"7d\") or absolute date (\"YYYY-MM-DD\"). Default \"7d\"."}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 25, "maximum": 100, "minimum": 1}, "description": "Maximum results to return (default 25, max 100)."}, {"name": "offset", "in": "query", "required": false, "schema": {"type": "integer", "default": 0, "minimum": 0}, "description": "Pagination offset (default 0)."}, {"name": "classify", "in": "query", "required": false, "schema": {"type": "string", "enum": ["true", "false"]}, "description": "Set to \"true\" to attach cached AI classification (adds 16 credits per classified entity)."}, {"name": "max_credits", "in": "query", "required": false, "schema": {"type": "integer", "minimum": 0}, "description": "Ceiling on what this single call may cost, in credits. FAILS CLOSED: checked against the maximum the call could cost before any billable work runs, so if it would exceed the ceiling nothing is computed and nothing is charged (400, error_code max_credits_exceeded, with max_cost_credits telling you the real number). You are still billed only for what was actually computed, so a call that clears the ceiling usually lands under it. Relevant here because classify=true adds 16 credits per entity returned, so limit=100 is a call that can reach 1,624 credits."}], "responses": {"200": {"description": "Trending entities.", "content": {"application/json": {"schema": {"type": "object", "properties": {"since": {"type": "string", "format": "date-time"}, "accounts_scanned": {"type": "integer"}, "total": {"type": "integer"}, "returned": {"type": "integer"}, "offset": {"type": "integer"}, "has_more": {"type": "boolean"}, "trending": {"type": "array", "items": {"type": "object", "properties": {"twitter_user_id": {"type": "string"}, "username": {"type": "string"}, "name": {"type": "string"}, "description": {"type": "string"}, "verified": {"type": "boolean"}, "followed_by": {"type": "array", "items": {"type": "string"}}, "follower_count": {"type": "integer", "description": "Number of tracked accounts that followed this entity in the window."}, "classification": {"$ref": "#/components/schemas/Classification"}}, "additionalProperties": true}}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}, "max_cost_credits": {"type": "integer", "description": "The most this call could have cost with the parameters you sent. Budget the next call against it. cost_credits is what you were actually charged."}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/feed": {"get": {"operationId": "get_feed", "summary": "Activity feed", "description": "Chronological event stream across all tracked accounts, newest first: new_follow and convergence events with classification on the target, filterable by event type, sector, and time window. Costs 16 credits. Good for polling-based monitoring; for push delivery of the same events register a webhook via POST /webhooks instead.", "tags": ["Signal"], "parameters": [{"name": "event_type", "in": "query", "required": false, "schema": {"type": "string", "enum": ["new_follow", "convergence"]}, "description": "Filter to a single event type."}, {"name": "since", "in": "query", "required": false, "schema": {"type": "string", "default": "24h", "examples": ["24h", "7d", "2026-07-01"]}, "description": "Time window: relative (\"24h\", \"7d\") or absolute date (\"YYYY-MM-DD\"). Default \"24h\"."}, {"name": "hours", "in": "query", "required": false, "schema": {"type": "integer"}, "description": "Alternative to `since` - window size in hours (e.g. `hours=48` equals `since=48h`)."}, {"name": "sector", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Filter events by classified sector (substring match)."}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 50, "maximum": 200, "minimum": 1}, "description": "Maximum results to return (default 50, max 200)."}, {"name": "offset", "in": "query", "required": false, "schema": {"type": "integer", "default": 0, "minimum": 0}, "description": "Pagination offset over the sorted event list (default 0)."}, {"name": "cursor", "in": "query", "required": false, "schema": {"type": "string", "examples": ["frc1_eyJ2IjoxLCJzIjoiOWY0YyIsImQiOiIyMDI2LTA4LTA3In0.Xq9"]}, "description": "Opaque watermark from a previous response's next_cursor. Resumes exactly where that run stopped, so a scheduled job that retries neither double counts nor drops records. Overrides since when present. Cursors are signed and scoped to the endpoint, API key and filters; a modified or cross-scope cursor returns 400 with error_code invalid_cursor. Persist next_cursor only AFTER you have processed the response."}], "responses": {"200": {"description": "Feed events, newest first.", "content": {"application/json": {"schema": {"type": "object", "properties": {"since": {"type": "string", "format": "date-time"}, "total": {"type": "integer"}, "returned": {"type": "integer"}, "events": {"type": "array", "items": {"type": "object", "properties": {"type": {"type": "string", "enum": ["new_follow", "convergence"]}, "timestamp": {"type": "string", "format": "date-time"}, "vc": {"type": "object", "properties": {"handle": {"type": "string"}}, "additionalProperties": true, "description": "Present on new_follow events."}, "accounts": {"type": "array", "items": {"type": "string"}, "description": "Present on convergence events - tracked accounts that converged."}, "target": {"type": "object", "properties": {"handle": {"type": "string"}, "name": {"type": "string"}, "classification": {"$ref": "#/components/schemas/Classification"}}, "additionalProperties": true}, "signal_strength": {"type": "number", "description": "Present on convergence events - 0 to 1."}}, "additionalProperties": true}}, "filter": {"type": "object", "properties": {"event_type": {"type": "string"}, "sector": {"type": "string"}}, "additionalProperties": true}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}, "offset": {"type": "integer"}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Opaque cursor for the next run. Null while has_more is true, by design, not an error: the cursor marks the end of the whole matched window, so it is issued on the final page only. Call again with offset set to next_offset until has_more is false, then take next_cursor from that final page."}, "next_offset": {"type": "integer", "description": "Present while has_more is true: the offset value for the next page of this window (offset + returned)."}, "next_cursor_note": {"type": "string"}, "cursor_note": {"type": "string"}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/sectors": {"get": {"operationId": "get_sectors", "summary": "Sector breakdown", "description": "Sector and entity-type breakdown of all discovered entities across your tracked accounts. Costs 4 credits. Cheap way to see what your coverage is surfacing and to discover the exact sector and entity_type strings to pass as filters to /search, /follows/enriched, and /feed.", "tags": ["Signal"], "responses": {"200": {"description": "Sector breakdown.", "content": {"application/json": {"schema": {"type": "object", "properties": {"total_entities": {"type": "integer"}, "classified_entities": {"type": "integer"}, "sectors": {"type": "array", "items": {"type": "object", "properties": {"name": {"type": "string"}, "count": {"type": "integer"}, "percentage": {"type": "integer"}}, "additionalProperties": true}}, "entity_types": {"type": "array", "items": {"type": "object", "properties": {"name": {"type": "string"}, "count": {"type": "integer"}}, "additionalProperties": true}}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/discover": {"get": {"operationId": "discover_accounts", "summary": "Discover recommendations", "description": "Personalized coverage-expansion recommendations: entities followed by 2+ of your tracked accounts that you are not tracking yet, scored by signal strength (0 to 1) and matched against your custom classification rules. Costs 60 credits. Typical flow: /discover, then GET /preview/{handle} on candidates, then POST /track the good ones.", "tags": ["Signal"], "parameters": [{"name": "sector", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Filter recommendations by sector (substring match)."}, {"name": "min_signal", "in": "query", "required": false, "schema": {"type": "number", "default": 0.3, "minimum": 0, "maximum": 1}, "description": "Minimum signal score, 0 to 1 (default 0.3)."}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 20, "maximum": 50, "minimum": 1}, "description": "Maximum results to return (default 20, max 50)."}], "responses": {"200": {"description": "Recommendations.", "content": {"application/json": {"schema": {"type": "object", "properties": {"tracked_accounts_analyzed": {"type": "integer"}, "recommendations": {"type": "array", "items": {"type": "object", "properties": {"handle": {"type": "string"}, "name": {"type": "string"}, "sector": {"type": "string"}, "entity_type": {"type": "string"}, "is_company": {"oneOf": [{"type": "boolean"}, {"type": "null"}]}, "followed_by": {"type": "array", "items": {"type": "string"}}, "signal_score": {"type": "number"}, "matched_rule": {"oneOf": [{"type": "string"}, {"type": "null"}]}, "reason": {"type": "string"}, "is_tracked": {"type": "boolean"}}, "additionalProperties": true}}, "total_candidates": {"type": "integer"}, "filters": {"type": "object", "properties": {"sector": {"type": "string"}}, "additionalProperties": true}, "rules_applied": {"type": "array", "items": {"type": "string"}}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/search": {"get": {"operationId": "search_entities", "summary": "Search entities", "description": "Keyword and metadata search across every entity your tracked accounts follow (the full stored follow graph, not just recent follows). At least one of sector, keyword (alias q), or entity_type is required, otherwise the call fails with 400. All filters are case-insensitive substring matches; companies are ranked first in results. Costs 4 credits. For meaning-based matching (concepts, not substrings) use /search/thesis.", "tags": ["Search"], "parameters": [{"name": "sector", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Sector filter (case-insensitive substring match, e.g. \"AI\"). Data-driven vocabulary, not a fixed enum: call GET /v1/sectors to discover the exact sector strings present in your coverage."}, {"name": "keyword", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Keyword matched against username, sector, and bio."}, {"name": "q", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Alias for keyword."}, {"name": "entity_type", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Entity type filter. Data-driven vocabulary, not a fixed enum: call GET /v1/sectors to discover the values in your coverage (e.g. \"company\", \"person\", \"project\", \"vc_firm\"). Case-insensitive substring match under the default scope=tracked; exact match under scope=catalog."}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 50, "maximum": 200, "minimum": 1}, "description": "Maximum results to return (default 50, max 200)."}, {"name": "offset", "in": "query", "required": false, "schema": {"type": "integer", "default": 0, "minimum": 0}, "description": "Pagination offset (default 0)."}, {"name": "scope", "in": "query", "required": false, "schema": {"type": "string", "enum": ["catalog"]}, "description": "Set to \"catalog\" to keyword-search the full company catalog (the whole output of the global discovery layer) instead of only the companies your own tracked accounts surfaced."}, {"name": "include_founders", "in": "query", "required": false, "schema": {"type": "string", "enum": ["true", "false"]}, "description": "scope=catalog only. Set to \"true\" to join cached founders onto each result so company plus founders is ONE call. Adds 100 credits per company that resolves; companies with no cached founders are free. Pair with max_credits, since the ceiling scales with limit."}, {"name": "max_credits", "in": "query", "required": false, "schema": {"type": "integer", "minimum": 0}, "description": "Ceiling on what this single call may cost, in credits. FAILS CLOSED: checked against the maximum the call could cost before any billable work runs, so if it would exceed the ceiling nothing is computed and nothing is charged (400, error_code max_credits_exceeded, with max_cost_credits telling you the real number). You are still billed only for what was actually computed, so a call that clears the ceiling usually lands under it. Relevant here because scope=catalog with include_founders=true adds 100 credits per company that resolves, so limit=200 has a 20,004 credit ceiling."}], "responses": {"200": {"description": "Search results (companies ranked first).", "content": {"application/json": {"schema": {"type": "object", "properties": {"filters": {"type": "object", "properties": {"sector": {"type": "string"}, "keyword": {"type": "string"}, "entity_type": {"type": "string"}}, "additionalProperties": true}, "total": {"type": "integer"}, "returned": {"type": "integer"}, "offset": {"type": "integer"}, "has_more": {"type": "boolean"}, "results": {"type": "array", "items": {"type": "object", "properties": {"twitter_user_id": {"type": "string"}, "username": {"type": "string"}, "description": {"type": "string"}, "classification": {"$ref": "#/components/schemas/Classification"}}, "additionalProperties": true}}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}, "max_cost_credits": {"type": "integer", "description": "The most this call could have cost with the parameters you sent. Budget the next call against it. cost_credits is what you were actually charged."}, "founders_included": {"type": "boolean", "description": "Present when include_founders=true was honored."}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/search/thesis": {"get": {"operationId": "search_thesis", "summary": "Thesis search (semantic)", "description": "Semantic search over your database (companies surfaced by the accounts you track, not the global catalog). Describe an investment thesis in plain text and get companies whose descriptions match the meaning, not the exact words, ranked by cosine similarity. `q` must be at least 10 characters or the call fails with 400. Costs 40 credits; zero matches cost 0 credits. Each result includes `first_flagged_at` (when Frontrun first flagged the company) and `convergence_count`: cite these as provenance for how early the signal is.", "tags": ["Search"], "parameters": [{"name": "q", "in": "query", "required": true, "schema": {"type": "string", "minLength": 10}, "description": "Investment thesis in plain text, at least 10 characters (e.g. \"information markets - platforms where people trade on what they know\")."}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 25, "maximum": 50, "minimum": 1}, "description": "Maximum results to return (default 25, max 50)."}], "responses": {"200": {"description": "Semantically matched companies.", "content": {"application/json": {"schema": {"type": "object", "properties": {"query": {"type": "string"}, "returned": {"type": "integer"}, "results": {"type": "array", "items": {"type": "object", "properties": {"username": {"type": "string"}, "name": {"type": "string"}, "description": {"type": "string"}, "website": {"type": "string"}, "sector": {"type": "string"}, "entity_type": {"type": "string"}, "followers_count": {"type": "integer"}, "first_flagged_at": {"type": "string", "format": "date-time", "description": "When Frontrun first flagged this company."}, "convergence_count": {"type": "integer"}, "similarity": {"type": "number", "description": "Cosine similarity to your thesis, 0 to 1."}}, "additionalProperties": true}}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/classify": {"post": {"operationId": "classify_entities", "summary": "Classify entities", "description": "Fetch classification on demand for up to 100 entities (by twitter_user_ids and/or usernames, combined max 100): AI classification with your custom rules and custom tags merged in. Served from the classification cache; entities not in the cache are silently omitted. Costs 16 credits per entity actually returned, so zero results cost 0 credits.", "tags": ["Classification"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"twitter_user_ids": {"type": "array", "items": {"type": "string"}, "maxItems": 100, "description": "Entity X user IDs."}, "usernames": {"type": "array", "items": {"type": "string"}, "maxItems": 100, "description": "Entity X usernames."}, "handles": {"type": "array", "items": {"type": "string"}, "maxItems": 100, "description": "Alias for usernames."}, "max_credits": {"type": "integer", "minimum": 0, "description": "Ceiling on what this single call may cost, in credits. FAILS CLOSED: checked against the maximum the call could cost before any billable work runs, so if it would exceed the ceiling nothing is computed and nothing is charged (400, error_code max_credits_exceeded, with max_cost_credits telling you the real number). You are still billed only for what was actually computed, so a call that clears the ceiling usually lands under it. Relevant here because cost is 16 credits per entity found and the ceiling is 100 entities (1,600 credits)."}}, "additionalProperties": true, "description": "Provide twitter_user_ids and/or usernames (max 100 combined)."}}}}, "responses": {"200": {"description": "Classified entities.", "content": {"application/json": {"schema": {"type": "object", "properties": {"total": {"type": "integer"}, "results": {"type": "array", "items": {"type": "object", "properties": {"twitter_user_id": {"type": "string"}, "username": {"type": "string"}, "description": {"type": "string"}, "classification": {"type": "object", "properties": {"is_company": {"type": "boolean"}, "confidence": {"type": "string"}, "sector": {"type": "string"}, "entity_type": {"type": "string"}, "sub_category": {"type": "string"}, "tags": {"type": "array", "items": {"type": "string"}}}, "additionalProperties": true}, "custom": {"$ref": "#/components/schemas/CustomData"}}, "additionalProperties": true}}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}, "max_cost_credits": {"type": "integer", "description": "The most this call could have cost with the parameters you sent. Budget the next call against it. cost_credits is what you were actually charged."}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/classify/rules": {"post": {"operationId": "create_classification_rule", "summary": "Create classification rule", "description": "Create a custom classification rule. Matching entities in /follows/enriched, /classify, and /discover automatically get your custom sector, entity type, tags, and priority applied. Conditions support bio_keywords (any match), username_pattern (regex, max 100 chars, no nested quantifiers), sector_contains, and must_be_company. Free, costs 0 credits. Limit 100 rules per account (500 on Enterprise).", "tags": ["Classification"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string", "description": "Rule name."}, "conditions": {"type": "object", "properties": {"bio_keywords": {"type": "array", "items": {"type": "string"}}, "username_pattern": {"type": "string"}, "sector_contains": {"type": "string"}, "must_be_company": {"type": "boolean"}}, "additionalProperties": true, "description": "Match conditions (valid keys: bio_keywords, username_pattern, sector_contains, must_be_company)."}, "actions": {"type": "object", "properties": {"custom_sector": {"type": "string"}, "custom_entity_type": {"type": "string"}, "tags": {"type": "array", "items": {"type": "string"}}, "priority": {"type": "string"}}, "additionalProperties": true, "description": "Actions applied on match (valid keys: custom_sector, custom_entity_type, tags, priority)."}}, "additionalProperties": true, "required": ["name", "conditions", "actions"]}}}}, "responses": {"201": {"description": "Rule created.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ClassificationRule"}}}}, "400": {"description": "Missing or invalid fields.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "429": {"$ref": "#/components/responses/RateLimited"}}}, "get": {"operationId": "list_classification_rules", "summary": "List classification rules", "description": "List your active classification rules, including per-rule match counts. Free, costs 0 credits.", "tags": ["Classification"], "responses": {"200": {"description": "Active rules.", "content": {"application/json": {"schema": {"type": "object", "properties": {"count": {"type": "integer"}, "rules": {"type": "array", "items": {"$ref": "#/components/schemas/ClassificationRule"}}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/classify/rules/{id}": {"put": {"operationId": "update_classification_rule", "summary": "Update classification rule", "description": "Update a classification rule: name, conditions, actions, or the active flag (set active=false to disable without deleting). Free, costs 0 credits.", "tags": ["Classification"], "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}, "description": "Rule ID."}], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"name": {"type": "string"}, "conditions": {"type": "object", "properties": {}, "additionalProperties": true}, "actions": {"type": "object", "properties": {}, "additionalProperties": true}, "active": {"type": "boolean"}}, "additionalProperties": true}}}}, "responses": {"200": {"description": "Updated rule.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ClassificationRule"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "404": {"description": "Rule not found.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"$ref": "#/components/responses/RateLimited"}}}, "delete": {"operationId": "delete_classification_rule", "summary": "Delete classification rule", "description": "Delete a classification rule. Free, costs 0 credits. Entities previously tagged by the rule are not retroactively untagged.", "tags": ["Classification"], "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}, "description": "Rule ID."}], "responses": {"200": {"description": "Rule deleted.", "content": {"application/json": {"schema": {"type": "object", "properties": {"deleted": {"type": "boolean"}, "id": {"type": "string"}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "404": {"description": "Rule not found.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/tags": {"post": {"operationId": "add_tags", "summary": "Add or update custom tags", "description": "Tag an entity with your own tags, custom sector, custom entity type, and notes. Tags are merged into /follows/enriched and /classify responses and matched by the `tag` filter. Upserts on the entity, so repeat calls update the record. Identify the entity by twitter_user_id or username (username is resolved via the classification cache and returns 404 if unknown). Free, costs 0 credits. Limit 10,000 tagged entities (100,000 on Enterprise).", "tags": ["Classification"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"twitter_user_id": {"type": "string", "description": "Entity X user ID (this or username required)."}, "username": {"type": "string", "description": "Entity X username (resolved via the classification cache)."}, "tags": {"type": "array", "items": {"type": "string"}}, "custom_sector": {"type": "string"}, "custom_entity_type": {"type": "string"}, "notes": {"type": "string"}}, "additionalProperties": true}}}}, "responses": {"201": {"description": "Tag record created or updated.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/TagRecord"}}}}, "400": {"description": "twitter_user_id or username required.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "404": {"description": "Entity not found in the classification cache.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"$ref": "#/components/responses/RateLimited"}}}, "get": {"operationId": "list_tags", "summary": "List custom tags", "description": "List your custom tag records, optionally filtered by tag or custom sector. Free, costs 0 credits.", "tags": ["Classification"], "parameters": [{"name": "tag", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Return only records containing this tag."}, {"name": "sector", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Filter by custom sector (substring match)."}], "responses": {"200": {"description": "Tag records.", "content": {"application/json": {"schema": {"type": "object", "properties": {"count": {"type": "integer"}, "tags": {"type": "array", "items": {"$ref": "#/components/schemas/TagRecord"}}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/tags/{twitter_user_id}": {"delete": {"operationId": "delete_tags", "summary": "Delete custom tags", "description": "Remove your custom tags from an entity by its X user ID. Free, costs 0 credits.", "tags": ["Classification"], "parameters": [{"name": "twitter_user_id", "in": "path", "required": true, "schema": {"type": "string"}, "description": "Entity X user ID."}], "responses": {"200": {"description": "Tags deleted.", "content": {"application/json": {"schema": {"type": "object", "properties": {"deleted": {"type": "boolean"}, "twitter_user_id": {"type": "string"}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "404": {"description": "Tag not found.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/preview/{handle}": {"get": {"operationId": "preview_account", "summary": "Preview an account", "description": "Preview any X account before tracking it: live profile summary, computed signal-potential score (0 to 1), a tracking recommendation (strong_track, consider_tracking, or low_signal), and a sector hint. Costs 4 credits. Use it to qualify /discover candidates before adding them with POST /track. Rate limited to 20 requests/hour per key (429 beyond that), separate from credit charges.", "tags": ["Company", "Tracking"], "parameters": [{"name": "handle", "in": "path", "required": true, "schema": {"type": "string"}, "description": "X handle to preview (with or without @)."}], "responses": {"200": {"description": "Account preview.", "content": {"application/json": {"schema": {"type": "object", "properties": {"handle": {"type": "string"}, "name": {"type": "string"}, "bio": {"type": "string"}, "followers": {"type": "integer"}, "following": {"type": "integer"}, "profile_image": {"type": "string"}, "created_at": {"type": "string"}, "sector_hint": {"oneOf": [{"type": "string"}, {"type": "null"}]}, "signal_potential": {"type": "object", "properties": {"score": {"type": "number", "description": "0 to 1."}, "hints": {"type": "array", "items": {"type": "string"}}, "recommendation": {"type": "string", "enum": ["strong_track", "consider_tracking", "low_signal"]}}, "additionalProperties": true}, "is_tracked": {"type": "boolean"}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/company/{handle}": {"get": {"operationId": "get_company_overview", "summary": "Company overview", "description": "Synthesized company overview: profile data, sector classification, entity type, a live website scrape summary, and recent posts. Works for any public X handle, not just companies already in your database (the profile is fetched live). Costs 60 credits. This is the standard first deep-dive after /trending, /convergence, or /search/thesis surfaces a candidate; follow with /company/{handle}/founders and /company/{handle}/funding.", "tags": ["Company"], "parameters": [{"name": "handle", "in": "path", "required": true, "schema": {"type": "string"}, "description": "Company X handle (with or without @)."}, {"name": "include", "in": "query", "required": false, "schema": {"type": "string", "examples": ["resources", "founders,funding", "resources,founders,funding"]}, "description": "Comma-separated sections to bundle into this one call: resources, founders, funding. Folds what used to be four separately priced calls into one round trip. Each section is priced at its standalone rate (resources 60, founders 100, funding 60); bundling is a latency win, not a discount. The zero-cost-on-empty rules are preserved: founders cost 0 credits when none are found, funding costs 0 when nothing resolves. cost_breakdown_credits shows the per-section charge and everything is deducted in one atomic charge. include=founders uses the fast cached founder join (same as /search?include_founders=true) and does NOT perform the blocking live discovery that GET /company/{handle}/founders does, so a cold company returns resolution_status \"resolving\" in that section. An unknown section returns 400 with error_code invalid_include."}, {"name": "max_credits", "in": "query", "required": false, "schema": {"type": "integer", "minimum": 0}, "description": "Ceiling on what this single call may cost, in credits. FAILS CLOSED: checked against the worst case for the sections you requested, before the first upstream call, so an over-budget request computes nothing and charges nothing (400, error_code max_credits_exceeded). Sections that come back empty are still free, so the actual charge is often below the ceiling."}], "responses": {"200": {"description": "Company overview.", "content": {"application/json": {"schema": {"type": "object", "properties": {"company": {"type": "object", "properties": {"handle": {"type": "string"}, "name": {"type": "string"}, "bio": {"type": "string"}, "website": {"oneOf": [{"type": "string"}, {"type": "null"}]}, "profile_image": {"type": "string"}, "followers": {"type": "integer"}, "following": {"type": "integer"}, "created_at": {"type": "string"}, "sector": {"type": "string"}, "entity_type": {"type": "string"}, "is_company": {"oneOf": [{"type": "boolean"}, {"type": "null"}]}, "confidence": {"type": "string"}, "about": {"type": "string"}, "website_summary": {"oneOf": [{"type": "string"}, {"type": "null"}]}, "recent_activity": {"type": "array", "items": {"type": "object", "properties": {"text": {"type": "string"}, "date": {"type": "string"}}, "additionalProperties": true}}}, "additionalProperties": true}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}, "included": {"type": "array", "items": {"type": "string", "enum": ["resources", "founders", "funding"]}, "description": "Sections bundled into this response. Present only when include was used."}, "cost_breakdown_credits": {"type": "object", "additionalProperties": {"type": "integer"}, "description": "Credits charged per section. Sections that returned nothing show 0."}, "max_cost_credits": {"type": "integer", "description": "The most this call could have cost with the sections requested."}, "resources": {"type": "object", "additionalProperties": true, "description": "Same shape as GET /company/{handle}/resources. Present only with include=resources."}, "founders": {"type": "object", "additionalProperties": true, "description": "Same shape as GET /company/{handle}/founders. Present only with include=founders."}, "funding": {"type": "object", "additionalProperties": true, "description": "Same schema as GET /company/{handle}/funding, so one parser works either way. Its nested cost_credits is THIS section's cost; the top-level cost_credits is the whole call. Present only with include=funding."}}, "additionalProperties": true}}}}, "400": {"description": "Unknown include section, or max_credits exceeded.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/company/{handle}/founders": {"get": {"operationId": "get_company_founders", "summary": "Company founders", "description": "Founder intelligence for a company: identifies founders via a layered discovery waterfall (verified cache, founder links, person discovery, social graph, tweet search) and enriches with LinkedIn data (role, work history, founder score, confidence, evidence). Costs 100 credits, the most expensive operation in the API, but zero founders found costs 0 credits. Reserve it for companies that already passed your screen.", "tags": ["Company"], "parameters": [{"name": "handle", "in": "path", "required": true, "schema": {"type": "string"}, "description": "Company X handle (with or without @)."}], "responses": {"200": {"description": "Founders found for the company.", "content": {"application/json": {"schema": {"type": "object", "properties": {"company": {"type": "string"}, "company_name": {"type": "string"}, "founders": {"type": "array", "items": {"$ref": "#/components/schemas/Founder"}}, "sources_used": {"type": "array", "items": {"type": "string"}}, "total_found": {"type": "integer"}, "verified": {"type": "boolean", "description": "Present (true) when served from the verified founder cache."}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/company/{handle}/signals": {"get": {"operationId": "get_company_signals", "summary": "Company social signals", "description": "Social signal analysis for a company: buzz score, sentiment, mention count, notable mentions, and, most usefully, `tracked_vc_followers`, the list of your tracked accounts that follow it. Costs 16 credits, and nothing at all when neither a mention nor a tracked-account follower is found. The cheapest way to check how much of your coverage has converged on a specific company.", "tags": ["Company"], "parameters": [{"name": "handle", "in": "path", "required": true, "schema": {"type": "string"}, "description": "Company X handle (with or without @)."}], "responses": {"200": {"description": "Company signals.", "content": {"application/json": {"schema": {"type": "object", "properties": {"handle": {"type": "string"}, "name": {"type": "string"}, "signals": {"type": "object", "properties": {"buzz_score": {"type": "integer"}, "sentiment": {"type": "string", "enum": ["positive", "neutral", "low_activity"]}, "mention_count": {"type": "integer"}, "notable_mentions": {"type": "array", "items": {"type": "object", "properties": {"author": {"type": "string"}, "text": {"type": "string"}, "date": {"type": "string"}, "likes": {"type": "integer"}, "retweets": {"type": "integer"}}, "additionalProperties": true}}, "tracked_vc_followers": {"type": "array", "items": {"type": "string"}}, "tracked_vc_count": {"type": "integer"}}, "additionalProperties": true}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/company/{handle}/resources": {"get": {"operationId": "get_company_resources", "summary": "Company resources", "description": "Discovered links and resources for a company from a live website scrape plus bio links: website, GitHub, documentation, Discord, Telegram, and all site links. Costs 60 credits. Useful for building diligence dossiers or finding a contact surface.", "tags": ["Company"], "parameters": [{"name": "handle", "in": "path", "required": true, "schema": {"type": "string"}, "description": "Company X handle (with or without @)."}], "responses": {"200": {"description": "Company resources.", "content": {"application/json": {"schema": {"type": "object", "properties": {"handle": {"type": "string"}, "name": {"type": "string"}, "resources": {"type": "object", "properties": {"website": {"oneOf": [{"type": "string"}, {"type": "null"}]}, "twitter": {"type": "string"}, "github": {"type": "string"}, "documentation": {"type": "string"}, "discord": {"type": "string"}, "telegram": {"type": "string"}, "all_links": {"type": "array", "items": {"type": "string"}}, "bio_links": {"type": "array", "items": {"type": "string"}}}, "additionalProperties": true}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/company/{handle}/funding": {"get": {"operationId": "get_company_funding", "summary": "Company funding", "description": "Funding and deal information for a company (rounds, investors, lead investors, founders, dates, source URL with confidence), cross-referenced with `vc_follow_signal`, the tracked accounts that follow it. Costs 60 credits; when no funding can be resolved you are not charged (found=false, funding=null, cost_credits 0). No resolved funding for a heavily-followed company often means the round has not been announced yet, which is exactly the window Frontrun exists to surface.", "tags": ["Company"], "parameters": [{"name": "handle", "in": "path", "required": true, "schema": {"type": "string"}, "description": "Company X handle (with or without @)."}], "responses": {"200": {"description": "Funding information (found=false with funding=null when nothing resolves).", "content": {"application/json": {"schema": {"type": "object", "properties": {"handle": {"type": "string"}, "name": {"type": "string"}, "found": {"type": "boolean"}, "funding": {"oneOf": [{"type": "object", "properties": {"source": {"type": "string"}, "name": {"type": "string"}, "total_funding": {"description": "Total funding raised.", "oneOf": [{"type": "number"}, {"type": "string"}]}, "num_rounds": {"type": "integer"}, "rounds": {"type": "array", "items": {"type": "object", "properties": {}, "additionalProperties": true, "description": "Funding round."}}, "investors": {"type": "array", "items": {"type": "string"}}, "lead_investors": {"type": "array", "items": {"type": "string"}}, "founders": {"type": "array", "items": {"type": "string"}}, "founded_on": {"type": "string"}, "crunchbase_url": {"type": "string"}, "announced_at": {"oneOf": [{"type": "string"}, {"type": "null"}]}, "source_url": {"oneOf": [{"type": "string"}, {"type": "null"}]}, "confidence": {"oneOf": [{"type": "string"}, {"type": "null"}]}}, "additionalProperties": true}, {"type": "null"}]}, "vc_follow_signal": {"type": "array", "items": {"type": "string"}, "description": "Your tracked accounts that follow this company."}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/vc/{username}/activity": {"get": {"operationId": "get_vc_activity", "summary": "Account activity profile", "description": "Activity profile for one of your tracked accounts: follow velocity (follows per week), sector distribution of new follows in the window, and the recent follows themselves. Costs 24 credits. Returns 404 if you are not tracking the username. Use it to understand what an individual investor is looking at right now.", "tags": ["Account Intelligence"], "parameters": [{"name": "username", "in": "path", "required": true, "schema": {"type": "string"}, "description": "Tracked account username (with or without @)."}, {"name": "since", "in": "query", "required": false, "schema": {"type": "string", "default": "30d", "examples": ["24h", "7d", "2026-07-01"]}, "description": "Time window: relative days (\"30d\") or absolute date (\"YYYY-MM-DD\"). Default \"30d\"."}], "responses": {"200": {"description": "Activity profile.", "content": {"application/json": {"schema": {"type": "object", "properties": {"username": {"type": "string"}, "name": {"type": "string"}, "profile_image_url": {"type": "string"}, "since": {"type": "string", "format": "date-time"}, "current_following": {"type": "integer"}, "new_follows_in_window": {"type": "integer"}, "follows_per_week": {"type": "number"}, "snapshots_in_window": {"type": "integer"}, "sector_breakdown": {"type": "object", "properties": {}, "additionalProperties": true, "description": "Sector name \u2192 count of new follows."}, "recent_follows": {"type": "array", "items": {"$ref": "#/components/schemas/FollowEntity"}}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/vc/{handle}/similar": {"get": {"operationId": "get_similar_vcs", "summary": "Similar tracked accounts", "description": "Find accounts with follow patterns similar to a given tracked account, computed from follow-graph overlap across the coverage pool. Costs 60 credits; zero results cost 0 credits. Results include is_tracked, so this doubles as a coverage-expansion tool: preview untracked similar accounts with /preview/{handle}, then POST /track them.", "tags": ["Account Intelligence"], "parameters": [{"name": "handle", "in": "path", "required": true, "schema": {"type": "string"}, "description": "Tracked account handle (with or without @)."}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 20, "maximum": 50, "minimum": 1}, "description": "Maximum results to return (default 20, max 50)."}, {"name": "min_overlap", "in": "query", "required": false, "schema": {"type": "number", "default": 0.1, "minimum": 0, "maximum": 1}, "description": "Minimum overlap score, 0 to 1 (default 0.1)."}], "responses": {"200": {"description": "Similar accounts ranked by overlap.", "content": {"application/json": {"schema": {"type": "object", "properties": {"vc": {"type": "string"}, "compared_against": {"type": "integer"}, "similar_accounts": {"type": "array", "items": {"type": "object", "properties": {"handle": {"type": "string"}, "overlap_score": {"type": "number"}, "shared_follows": {"type": "integer"}, "total_follows": {"type": "integer"}, "unique_follows": {"type": "integer"}, "is_tracked": {"type": "boolean"}}, "additionalProperties": true}}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/webhooks": {"post": {"operationId": "create_webhook", "summary": "Register a webhook", "description": "Register a webhook URL to receive push-delivered signal events instead of polling /feed. Subscribe to event types (new_follow, convergence) with optional filters (e.g. sector); provide `secret` to get signed deliveries. Up to 10 active webhooks per API key (400 when the limit is reached). Costs 40 credits to register, then 8 credits per delivered event. Failed deliveries are retried up to 5 times with increasing backoff.", "tags": ["Webhooks"], "requestBody": {"required": true, "content": {"application/json": {"schema": {"type": "object", "properties": {"url": {"type": "string", "format": "uri", "description": "HTTPS endpoint to receive event POSTs."}, "events": {"type": "array", "items": {"type": "string"}, "description": "Event types to subscribe to (e.g. \"new_follow\", \"convergence\")."}, "filters": {"type": "object", "properties": {}, "additionalProperties": true, "description": "Optional event filters (e.g. sector)."}, "secret": {"type": "string", "description": "Optional signing secret for verifying deliveries."}}, "additionalProperties": true, "required": ["url", "events"]}}}}, "responses": {"201": {"description": "Webhook registered.", "content": {"application/json": {"schema": {"type": "object", "properties": {"id": {"type": "string"}, "url": {"type": "string"}, "events": {"type": "array", "items": {"type": "string"}}, "filters": {"type": "object", "properties": {}, "additionalProperties": true}, "status": {"type": "string"}, "created_at": {"type": "string", "format": "date-time"}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "balance_credits": {"type": "integer", "description": "Remaining credit balance after this call."}}, "additionalProperties": true}}}}, "400": {"description": "Invalid webhook configuration or webhook limit reached.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}, "get": {"operationId": "list_webhooks", "summary": "List webhooks", "description": "List your registered webhooks with status, consecutive failure count, and last delivery time. Free, costs 0 credits.", "tags": ["Webhooks"], "responses": {"200": {"description": "Webhooks.", "content": {"application/json": {"schema": {"type": "object", "properties": {"count": {"type": "integer"}, "webhooks": {"type": "array", "items": {"$ref": "#/components/schemas/Webhook"}}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/webhooks/{id}": {"delete": {"operationId": "delete_webhook", "summary": "Delete a webhook", "description": "Delete a webhook. Free, costs 0 credits.", "tags": ["Webhooks"], "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}, "description": "Webhook ID."}], "responses": {"200": {"description": "Webhook deleted.", "content": {"application/json": {"schema": {"type": "object", "properties": {"deleted": {"type": "boolean"}, "id": {"type": "string"}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "404": {"description": "Webhook not found.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/webhooks/{id}/deliveries": {"get": {"operationId": "list_webhook_deliveries", "summary": "Webhook delivery history", "description": "Delivery history for a webhook, newest first: event type, HTTP status code, latency, and attempt number per delivery. Free, costs 0 credits. Check this first when your endpoint seems to be missing events.", "tags": ["Webhooks"], "parameters": [{"name": "id", "in": "path", "required": true, "schema": {"type": "string"}, "description": "Webhook ID."}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 50, "maximum": 200, "minimum": 1}, "description": "Maximum results to return (default 50, max 200)."}], "responses": {"200": {"description": "Deliveries, newest first.", "content": {"application/json": {"schema": {"type": "object", "properties": {"webhook_id": {"type": "string"}, "count": {"type": "integer"}, "deliveries": {"type": "array", "items": {"type": "object", "properties": {"id": {"type": "string"}, "event_type": {"type": "string"}, "status_code": {"type": "integer"}, "response_ms": {"type": "integer"}, "attempt": {"type": "integer"}, "delivered_at": {"type": "string", "format": "date-time"}}, "additionalProperties": true}}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "404": {"description": "Webhook not found.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "429": {"$ref": "#/components/responses/RateLimited"}}}}, "/reports": {"get": {"operationId": "get_reports", "summary": "Historical reports", "description": "Historical daily report data: the companies Frontrun flagged for you each day (the same companies as your daily report email), for a date range or a specific date, optionally filtered by sector. Costs 4 credits. The cheapest way to replay past discoveries or backfill a CRM.", "tags": ["Reports"], "parameters": [{"name": "date", "in": "query", "required": false, "schema": {"type": "string", "format": "date"}, "description": "Return the report for a specific date (YYYY-MM-DD)."}, {"name": "since", "in": "query", "required": false, "schema": {"type": "string", "default": "7d", "examples": ["24h", "7d", "2026-07-01"]}, "description": "Time window: relative (\"24h\", \"7d\") or absolute date (\"YYYY-MM-DD\"). Default \"7d\"."}, {"name": "sector", "in": "query", "required": false, "schema": {"type": "string"}, "description": "Filter companies by sector, description, or name (substring match)."}, {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer", "default": 7, "maximum": 30, "minimum": 1}, "description": "Maximum results to return (default 7, max 30)."}, {"name": "cursor", "in": "query", "required": false, "schema": {"type": "string", "examples": ["frc1_eyJ2IjoxLCJzIjoiOWY0YyIsImQiOiIyMDI2LTA4LTA3In0.Xq9"]}, "description": "Opaque watermark from a previous response's next_cursor. Resumes exactly where that run stopped, so a scheduled job that retries neither double counts nor drops records. Overrides since when present. Cursors are signed and scoped to the endpoint, API key and filters; a modified or cross-scope cursor returns 400 with error_code invalid_cursor. Persist next_cursor only AFTER you have processed the response. Cannot be combined with date. Walks forward from the day after the newest report already returned."}], "responses": {"200": {"description": "Reports.", "content": {"application/json": {"schema": {"type": "object", "properties": {"reports": {"type": "array", "items": {"type": "object", "properties": {"date": {"type": "string", "format": "date"}, "total_companies": {"type": "integer"}, "companies": {"type": "array", "items": {"type": "object", "properties": {"handle": {"type": "string"}, "name": {"type": "string"}, "sector": {"type": "string"}, "description": {"type": "string"}, "confidence": {"type": "string"}, "tracked_by": {"type": "array", "items": {"type": "string"}, "description": "Your tracked accounts that surfaced this company."}, "website": {"type": "string"}, "profile_image": {"type": "string"}}, "additionalProperties": true}}}, "additionalProperties": true}}, "total_reports": {"type": "integer"}, "total_companies": {"type": "integer"}, "filter": {"oneOf": [{"type": "string"}, {"type": "null"}]}, "cost_credits": {"type": "integer", "description": "Credits charged for this call."}, "has_more": {"type": "boolean"}, "next_cursor": {"type": ["string", "null"], "description": "Opaque cursor for the next run. Null while has_more is true: page to the end with offset first, then take the cursor from the final page, otherwise the watermark would skip the pages you never read."}, "cursor_note": {"type": "string"}}, "additionalProperties": true}}}}, "401": {"$ref": "#/components/responses/Unauthorized"}, "402": {"$ref": "#/components/responses/InsufficientCredits"}, "429": {"$ref": "#/components/responses/RateLimited"}}}}}, "components": {"securitySchemes": {"ApiKeyAuth": {"type": "apiKey", "in": "header", "name": "X-API-Key", "description": "Your Frontrun API key. Generate one from the developer page after subscribing to Pro."}}, "schemas": {"Error": {"type": "object", "properties": {"error": {"type": "string", "description": "Human-readable error message."}}, "additionalProperties": true, "required": ["error"]}, "InsufficientCreditsError": {"type": "object", "properties": {"error": {"type": "string", "examples": ["Insufficient credits"]}, "required_credits": {"type": "integer", "description": "Credits required for this operation."}, "balance_credits": {"type": "integer", "description": "Your current credit balance."}, "monthly_credits": {"type": "integer"}, "topup_credits": {"type": "integer"}, "tier": {"type": "string"}, "top_up": {"type": "string", "description": "URL to add credits."}, "upgrade": {"type": "string", "description": "Upgrade guidance."}}, "additionalProperties": true}, "RateLimitError": {"type": "object", "properties": {"error": {"type": "string", "description": "Rate limit or daily credit cap message."}, "tier": {"type": "string"}, "daily_spent_credits": {"type": "integer"}, "daily_cap_credits": {"type": "integer", "description": "Daily cap in credits (null for Enterprise)."}, "resets_at": {"type": "string", "format": "date-time"}, "retry_after_seconds": {"type": "integer"}}, "additionalProperties": true}, "Classification": {"type": "object", "properties": {"is_company": {"type": "boolean", "description": "Whether the entity is classified as a company."}, "confidence": {"type": "string", "description": "Classification confidence."}, "sector": {"type": "string", "description": "Primary sector (e.g. \"AI/ML\", \"DeFi\")."}, "entity_type": {"type": "string", "description": "Entity type (e.g. \"startup\", \"investor\")."}}, "additionalProperties": true, "description": "AI classification of the entity."}, "FollowEntity": {"type": "object", "properties": {"twitter_user_id": {"type": "string"}, "username": {"type": "string"}, "name": {"type": "string"}, "description": {"type": "string"}, "verified": {"type": "boolean"}, "profile_image_url": {"type": "string"}, "classification": {"type": "object", "properties": {"is_company": {"type": "boolean", "description": "Whether the entity is classified as a company."}, "confidence": {"type": "string", "description": "Classification confidence."}, "sector": {"type": "string", "description": "Primary sector (e.g. \"AI/ML\", \"DeFi\")."}, "entity_type": {"type": "string", "description": "Entity type (e.g. \"startup\", \"investor\")."}}, "additionalProperties": true, "description": "AI classification of the entity."}}, "additionalProperties": true}, "CustomData": {"type": "object", "properties": {"tags": {"type": "array", "items": {"type": "string"}}, "sector": {"type": "string", "description": "Custom sector override."}, "entity_type": {"type": "string", "description": "Custom entity type override."}, "notes": {"type": "string"}, "priority": {"type": "string"}, "matched_rule": {"type": "string", "description": "Name of the classification rule that matched."}}, "additionalProperties": true, "description": "Your custom tags and rule matches merged into the entity."}, "ClassificationRule": {"type": "object", "properties": {"id": {"type": "string", "description": "Rule ID."}, "name": {"type": "string"}, "conditions": {"type": "object", "properties": {"bio_keywords": {"type": "array", "items": {"type": "string"}, "description": "Match if the bio contains any of these keywords."}, "username_pattern": {"type": "string", "description": "Regex tested against the username (max 100 chars, no nested quantifiers)."}, "sector_contains": {"type": "string", "description": "Match if the classified sector contains this substring."}, "must_be_company": {"type": "boolean", "description": "Require (true) or exclude (false) company-classified entities."}}, "additionalProperties": true}, "actions": {"type": "object", "properties": {"custom_sector": {"type": "string"}, "custom_entity_type": {"type": "string"}, "tags": {"type": "array", "items": {"type": "string"}}, "priority": {"type": "string"}}, "additionalProperties": true}, "active": {"type": "boolean"}, "match_count": {"type": "integer"}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}}, "additionalProperties": true}, "TagRecord": {"type": "object", "properties": {"id": {"type": "string"}, "twitter_user_id": {"type": "string"}, "username": {"type": "string"}, "tags": {"type": "array", "items": {"type": "string"}}, "custom_sector": {"type": "string"}, "custom_entity_type": {"type": "string"}, "notes": {"type": "string"}, "created_at": {"type": "string", "format": "date-time"}, "updated_at": {"type": "string", "format": "date-time"}}, "additionalProperties": true}, "Founder": {"type": "object", "properties": {"handle": {"type": "string", "description": "Founder X handle."}, "name": {"type": "string"}, "bio": {"type": "string"}, "role": {"type": "string", "description": "e.g. \"Founder\", \"Co-Founder\", \"CEO\", \"CTO\"."}, "previous_companies": {"type": "array", "items": {"type": "string"}}, "linkedin_url": {"type": "string"}, "linkedin_headline": {"type": "string"}, "work_history": {"type": "array", "items": {"type": "object", "properties": {"company": {"type": "string"}, "title": {"type": "string"}}, "additionalProperties": true}}, "founder_score": {"type": "number"}, "confidence": {"type": "string", "enum": ["high", "medium", "low"]}, "discovered_via": {"type": "string", "description": "Discovery layer (e.g. \"pipeline\", \"company_following\", \"tweet_search\", \"frontrun_v2\")."}, "evidence": {"type": "array", "items": {"type": "string"}}}, "additionalProperties": true}, "Webhook": {"type": "object", "properties": {"id": {"type": "string"}, "url": {"type": "string", "format": "uri"}, "events": {"type": "array", "items": {"type": "string"}}, "filters": {"type": "object", "properties": {}, "additionalProperties": true}, "status": {"type": "string"}, "consecutive_failures": {"type": "integer"}, "last_delivery_at": {"type": "string", "format": "date-time"}, "created_at": {"type": "string", "format": "date-time"}}, "additionalProperties": true}}, "responses": {"Unauthorized": {"description": "Missing, invalid, inactive, or expired API key.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/Error"}}}}, "InsufficientCredits": {"description": "Insufficient credits for this operation. Top up from the developer page or enable auto-refill.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/InsufficientCreditsError"}}}}, "RateLimited": {"description": "Rate limit or daily credit cap reached. Retry after the indicated reset time.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/RateLimitError"}}}}}}}