# Authentication Source: https://docs.catchthegoodones.com/api-reference/authentication How to authenticate with the CatchTheGoodOnes API # Authentication All API requests require a valid API key sent as a Bearer token in the `Authorization` header. ## Getting your API key 1. Log in to [CatchTheGoodOnes](https://www.catchthegoodones.com/sign-in) 2. Go to **Settings** - **API Key** 3. Click **Generate API Key** 4. Copy your key immediately - it is only shown once API keys use the prefix `ctgo_` so you can identify them in your configuration. ## Using your API key Include your key in the `Authorization` header of every request: ```bash theme={null} curl https://www.catchthegoodones.com/api/v2/leads \ -H "Authorization: Bearer ctgo_your_api_key_here" ``` ## Key management You can manage your API key from the [API Key page](https://www.catchthegoodones.com/settings/api): * **Regenerate** - Creates a new key and immediately invalidates the old one * **Revoke** - Permanently disables the key. You can generate a new one at any time. ## Error responses If your key is missing, malformed, or revoked, the API returns a `401` status: ```json theme={null} { "error": "Missing or invalid Authorization header. Use: Bearer " } ``` ```json theme={null} { "error": "Invalid API key format." } ``` ```json theme={null} { "error": "Invalid or revoked API key." } ``` # Change an account's tier Source: https://docs.catchthegoodones.com/api-reference/change-tier POST /api/v2/accounts/{id}/change-tier Upgrade or downgrade a tracked account's tier with Stripe proration Changes the tier of a tracked account. Upgrades apply immediately with Stripe proration. Downgrades schedule for end of billing period. If the account already has a pending downgrade and you POST with `newTier` matching the current tier, the pending downgrade is cancelled. ## Body | Field | Type | Description | | --------- | --------------- | ----------------------------------- | | `newTier` | enum (required) | `"starter"`, `"growth"`, or `"pro"` | ## Example ```bash theme={null} curl -X POST https://www.catchthegoodones.com/api/v2/accounts/7/change-tier \ -H "Authorization: Bearer ctgo_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{"newTier": "pro"}' ``` ## Response Upgrade: ```json theme={null} { "account": {...}, "action": "upgraded" } ``` Downgrade: ```json theme={null} { "account": {...}, "action": "downgrade_scheduled", "effectiveAt": "2026-07-01T00:00:00Z" } ``` Cancellation of pending downgrade: ```json theme={null} { "account": {...}, "action": "pending_cancelled" } ``` ## Errors | Code | HTTP | When | | --------------------------------------------- | ---- | -------------------------------- | | `subscription_required` | 403 | No active subscription | | `Account not found` | 404 | | | `Wake this account first to change its tier.` | 400 | Account is asleep | | (Stripe error message) | 502 | Stripe failed the proration call | ## Auth Bearer or session cookie. # Create DM Template Source: https://docs.catchthegoodones.com/api-reference/create-dm-template POST https://www.catchthegoodones.com/api/v2/dm-templates Create a new DM template Creates a new DM template. Template names must be unique per user. ### Headers | Header | Required | Description | | --------------- | -------- | ------------------------------- | | `Authorization` | Yes | `Bearer ctgo_your_api_key_here` | | `Content-Type` | Yes | `application/json` | ### Body | Field | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------------- | | `name` | string | Yes | Template name (1-100 characters). Must be unique. | | `messageBody` | string | Yes | Message content (1-2000 characters) | ### Example ```bash theme={null} curl -X POST https://www.catchthegoodones.com/api/v2/dm-templates \ -H "Authorization: Bearer ctgo_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{"name": "Intro message", "messageBody": "Hi! I noticed you recently followed me. I'\''d love to connect."}' ``` ### Response ```json 201 theme={null} { "template": { "id": 1, "name": "Intro message", "messageBody": "Hi! I noticed you recently followed me. I'd love to connect.", "createdAt": "2026-04-17T12:00:00.000Z", "updatedAt": "2026-04-17T12:00:00.000Z" } } ``` ```json 409 theme={null} { "error": "You already have a template called 'Intro message'" } ``` # Create a segment Source: https://docs.catchthegoodones.com/api-reference/create-segment POST /api/v2/segments Create a reusable segment (filter set) that can be added to streams later Creates a new segment. The returned `id` can be passed as `segmentId` in a `pairs[]` entry on [POST /api/v2/streams](/api-reference/create-stream) or [PATCH /api/v2/streams/](/api-reference/update-stream). If you'd rather author the segment inline as part of stream creation, pass an inline `segment: {...}` object in the pair instead. ## Body | Field | Type | Description | | ---------------------- | ----------------- | ------------------------------------------------------------- | | `description` | string (required) | What this segment is looking for | | `name` | string | Optional display name | | `genderValues` | array | e.g. `["female"]` | | `appliedSearchFilters` | object | e.g. `{ "job_role": ["Founder", "CEO"], "skill": ["AI/ML"] }` | | `followerCountTiers` | array | e.g. `["10k_to_100k"]` | ## Example ```bash theme={null} curl -X POST https://www.catchthegoodones.com/api/v2/segments \ -H "Authorization: Bearer ctgo_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "description": "AI founders building developer tools", "appliedSearchFilters": { "job_role": ["Founder", "CEO"], "skill": ["AI/ML"] } }' ``` ## Response ```json theme={null} { "id": 12, "userFacingId": "seg_12" } ``` ## Errors | Code | HTTP | When | | -------------- | ---- | ------------------------------------------------------------------------------------------------------------ | | `orphan_label` | 400 | `appliedSearchFilters` contained labels with no canonical match. Response includes `orphans[]` listing them. | ## Auth Bearer or session cookie. # Create a stream Source: https://docs.catchthegoodones.com/api-reference/create-stream POST /api/v2/streams One-shot create: accounts + segments + schedule + delivery in one call Creates a new campaign (stream) in one call. Resolves new handles via ScrapeBadger (upserting tracked accounts + incrementing Stripe quantity), creates inline segments if provided, wires pairs through the segment-cap chokepoint. ## Body | Field | Type | Description | | --------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name` | string (optional) | Campaign name. If omitted, the server derives one from the resolved pairs: `"{segment} · @{handle}"` for a single account, `"{segment} · N accounts"` for several. | | `pairs` | array | Up to 50 `(account, segment)` pairs | | `syncTimes` | array | `["HH:MM"]` slots (15-min boundaries). Default: one hash-distributed slot. | | `timezone` | string | IANA timezone. Default: user's default or `"UTC"`. | | `autoEnrichLinkedin` | boolean | Auto-enrich LinkedIn URL on new leads. Default `false`. | | `autoEnrichEmail` | boolean | Auto-enrich email on new leads. Default `false`. | | `emailMode` | enum | `"verified"` or `"any"`. Default `"any"`. | | `emailDigestOverride` | enum | `"after_every_sync"`, `"daily"`, `"weekly"`, `"off"` | | `telegramOverride` | boolean | Override team-default Telegram digest setting | | `deliveryDestination` | enum | `"leads_only"` or `"leads_plus_webhook"` | | `webhookUrl` | string | URL for webhook delivery (when `deliveryDestination = "leads_plus_webhook"`) | | `csvEmailRecipients` | object | `{to, cc?, format}` for "email me a CSV after every run" | Each pair specifies an account (by `accountId` OR `accountHandle`) AND a segment (by `segmentId` OR inline `segment`): ```json theme={null} { "accountId": 7, "segmentId": 12 } ``` OR ```json theme={null} { "accountHandle": "yongfook", "accountTier": "starter", "segment": { "description": "AI founders building developer tools", "appliedSearchFilters": { "job_role": ["Founder", "CEO"] } } } ``` ## Response ```json theme={null} { "id": 42, "accounts": [ { "handle": "yongfook", "accountId": 7, "accountCreated": true, "billingDelta": "stripe_quantity_incremented" } ], "segments": [ { "segmentId": 12, "segmentCreated": true } ] } ``` ## Error codes | Code | HTTP | When | | ----------------------- | ---- | ----------------------------------------------------------------------- | | `handle_not_found` | 404 | X handle didn't resolve | | `handle_protected` | 422 | Handle is a private account | | `handle_lookup_failed` | 503 | X lookup service down | | `free_account_limit` | 403 | Free user adding 2nd account | | `subscription_required` | 403 | Paid user with lapsed sub | | `stream_cap_exceeded` | 400 | Tier cap of automated streams per account exceeded | | `stream_multi_segment` | 400 | Pairs span more than one segment (a stream targets exactly one segment) | | `orphan_label` | 400 | Inline segment had labels with no canonical match | ## Auth Bearer or session cookie. # Delete DM Template Source: https://docs.catchthegoodones.com/api-reference/delete-dm-template DELETE https://www.catchthegoodones.com/api/v2/dm-templates/{id} Delete a DM template Deletes a DM template. ### Headers | Header | Required | Description | | --------------- | -------- | ------------------------------- | | `Authorization` | Yes | `Bearer ctgo_your_api_key_here` | ### Path parameters | Parameter | Type | Description | | --------- | ------- | --------------- | | `id` | integer | The template ID | ### Example ```bash theme={null} curl -X DELETE https://www.catchthegoodones.com/api/v2/dm-templates/1 \ -H "Authorization: Bearer ctgo_your_api_key_here" ``` ### Response ```json 200 theme={null} { "success": true } ``` ```json 404 theme={null} { "error": "Template not found" } ``` # Delete a stream Source: https://docs.catchthegoodones.com/api-reference/delete-stream DELETE /api/v2/streams/{streamId} Archive a campaign (preserves historical lead attribution) Archives the campaign. Sets `isActive=false` and `isArchived=true`. Historical leads attributed to this stream are preserved. To restore: PATCH with `isActive: true, isArchived: false`. ## Response ```json theme={null} { "archived": true } ``` ## Auth Bearer or session cookie. # Enrich a lead Source: https://docs.catchthegoodones.com/api-reference/enrich-lead POST /api/v2/leads/{leadId}/enrich On-demand LinkedIn + email enrichment for a specific lead Enriches a specific lead with LinkedIn URL and/or email. Consumes credits. Same engine as the extension lookup surface, but scoped to a lead row you already have. ## Body | Field | Type | Description | | ------------------- | ---------------- | ----------------------------------------- | | `requested_outputs` | array (required) | `["linkedin_url"]`, `["email"]`, or both | | `email_mode` | enum | `"verified"` or `"any"`. Default `"any"`. | ## Response ```json theme={null} { "leadId": 1234, "x_profile_id": "1234567890", "result": { "ok": true, "kind": "resolved", "linkedin": { "url": "https://linkedin.com/in/alice", "confidence": "high" }, "email": { "address": "alice@acme.com", "status": "verified", "provider": "prospeo" } }, "credits_remaining": 47, "contact_routes": [ { "routeType": "linkedin_url", "value": "...", "confidence": "high" } ] } ``` ## Errors | Code | HTTP | When | | ------------------- | ---- | --------------------------- | | `lead_not_found` | 404 | Lead not owned by user | | `profile_missing` | 404 | x\_profiles row not in DB | | `credits_exhausted` | 429 | Team credit balance too low | | `no_team` | 400 | User has no team membership | ## Auth Bearer API key. # Mark a lead as exported Source: https://docs.catchthegoodones.com/api-reference/export-lead POST /api/v2/leads/{leadId}/export Update a lead's lifecycle to "exported" after moving it into your CRM Marks a lead as exported. Sets `x_leads.exported_at = NOW()` and logs an export event for batch-level audit. After export, the lead appears in `?lifecycle=exported` filters and disappears from `?lifecycle=new`. Re-exporting overwrites `exported_at` to the latest timestamp. ## Body | Field | Type | Description | | ------------ | ----------------- | -------------------------------------------------------------------------------------------- | | `exportType` | string (required) | Free-form tag for the destination tool. Example: `"csv"`, `"heyreach"`, `"apollo"`, `"clay"` | | `platform` | string | Optional platform tag (e.g. `"x"`, `"linkedin"`) | ## Response ```json theme={null} { "leadId": 1234, "exported": true } ``` ## Auth Bearer or session cookie. # Get DM Template Source: https://docs.catchthegoodones.com/api-reference/get-dm-template GET https://www.catchthegoodones.com/api/v2/dm-templates/{id} Returns a single DM template by ID Returns a single DM template by ID. ### Headers | Header | Required | Description | | --------------- | -------- | ------------------------------- | | `Authorization` | Yes | `Bearer ctgo_your_api_key_here` | ### Path parameters | Parameter | Type | Description | | --------- | ------- | --------------- | | `id` | integer | The template ID | ### Example ```bash theme={null} curl https://www.catchthegoodones.com/api/v2/dm-templates/1 \ -H "Authorization: Bearer ctgo_your_api_key_here" ``` ### Response ```json 200 theme={null} { "template": { "id": 1, "name": "Intro message", "messageBody": "Hi! I noticed you recently followed me. I'd love to connect.", "createdAt": "2026-04-10T12:00:00.000Z", "updatedAt": "2026-04-10T12:00:00.000Z" } } ``` ```json 404 theme={null} { "error": "Template not found" } ``` # Get Notification Settings Source: https://docs.catchthegoodones.com/api-reference/get-notification-settings GET https://www.catchthegoodones.com/api/v2/notification-settings Returns the current notification preferences for the authenticated user Returns the current notification preferences for the authenticated user. ### Headers | Header | Required | Description | | --------------- | -------- | ------------------------------- | | `Authorization` | Yes | `Bearer ctgo_your_api_key_here` | ### Example ```bash theme={null} curl https://www.catchthegoodones.com/api/v2/notification-settings \ -H "Authorization: Bearer ctgo_your_api_key_here" ``` ### Response ```json 200 theme={null} { "summaryFrequency": "daily", "emailSummariesEnabled": true, "telegramSummariesEnabled": false, "telegramConnected": false } ``` ### Response fields | Field | Type | Description | | -------------------------- | ------- | ------------------------------------------------------- | | `summaryFrequency` | string | `"after_every_sync"`, `"daily"`, `"weekly"`, or `"off"` | | `emailSummariesEnabled` | boolean | Whether email summaries are enabled | | `telegramSummariesEnabled` | boolean | Whether Telegram summaries are enabled | | `telegramConnected` | boolean | Whether a Telegram chat has been linked | # Get a stream Source: https://docs.catchthegoodones.com/api-reference/get-stream GET /api/v2/streams/{streamId} Inspect a single campaign with all pairs + config Returns one campaign's full config + pairs. ## Response Same shape as one row in [List streams](/api-reference/list-streams). ## Errors | Code | HTTP | | ------------------ | ---- | | `Stream not found` | 404 | ## Auth Bearer or session cookie. # Introduction Source: https://docs.catchthegoodones.com/api-reference/introduction Stream-centred API for managing lead-streaming campaigns, extracting leads, and integrating Catch The Good Ones with your stack. # Catch The Good Ones API The Catch The Good Ones API is **stream-centred**. A **stream** is a campaign: a named operational unit that owns N `(tracked_account, segment)` pairs and a shared schedule + enrichment + delivery config. Three primary verbs frame the surface: * **Create streams** with `POST /api/v2/streams` (one-shot: handles + segments + everything) * **Update streams** with `PATCH /api/v2/streams/{id}` (any setting, including full-replace pairs) * **Extract leads** with `GET /api/v2/streams/{id}/leads` (per-stream) or `GET /api/v2/leads` (cross-stream polling) Use it to pipe your leads into Zapier, Make, n8n, HeyReach, Apollo, Clay, or your own applications. ## Base URL ``` https://www.catchthegoodones.com/api/v2 ``` API v1 paths return **410 Gone**. See the [Migration guide](/api-reference/migration-v1-to-v2) for old-to-new mappings. ## Quick start 1. Generate an API key from your [API Key page](https://www.catchthegoodones.com/settings/api) 2. Verify the key: ```bash theme={null} curl https://www.catchthegoodones.com/api/v2/me \ -H "Authorization: Bearer ctgo_your_api_key_here" ``` 3. Create a campaign in one call: ```bash theme={null} curl -X POST https://www.catchthegoodones.com/api/v2/streams \ -H "Authorization: Bearer ctgo_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "name": "AI founders watching Yongfook", "pairs": [ { "accountHandle": "yongfook", "accountTier": "starter", "segment": { "description": "AI founders building developer tools" } } ], "autoEnrichLinkedin": true, "autoEnrichEmail": true }' ``` 4. Pull leads from the campaign: ```bash theme={null} curl https://www.catchthegoodones.com/api/v2/streams/123/leads?lifecycle=new \ -H "Authorization: Bearer ctgo_your_api_key_here" ``` ## Endpoints by user goal ### Set up a campaign | Method | Endpoint | Purpose | | ------ | ------------------ | ------------------------------------------------------------- | | `POST` | `/api/v2/streams` | Create a campaign (one-shot: handles + segments + everything) | | `POST` | `/api/v2/segments` | Create a reusable segment to add to streams later | | `GET` | `/api/v2/segments` | List the segments you've authored | | `GET` | `/api/v2/accounts` | List your tracked accounts (active + sleeping) | ### Manage campaigns | Method | Endpoint | Purpose | | -------- | ------------------------------ | ------------------------------------------------------------------------ | | `GET` | `/api/v2/streams` | List your active campaigns | | `GET` | `/api/v2/streams/{id}` | Inspect a campaign | | `PATCH` | `/api/v2/streams/{id}` | Update any setting, including full-replace pairs | | `DELETE` | `/api/v2/streams/{id}` | Archive a campaign | | `POST` | `/api/v2/streams/{id}/run` | Trigger a sync across the campaign's pairs | | `POST` | `/api/v2/streams/{id}/re-test` | Re-classify cached followers (optional `{accountId?, segmentId?}` scope) | ### Monitor campaigns | Method | Endpoint | Purpose | | ------ | ----------------------------------- | --------------------------------------- | | `GET` | `/api/v2/streams/{id}/runs` | Sync history for one campaign | | `GET` | `/api/v2/streams/{id}/runs/{runId}` | Single run status + progress log | | `GET` | `/api/v2/runs` | Flat sync-run list across all campaigns | ### Extract leads | Method | Endpoint | Purpose | | ------ | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GET` | `/api/v2/streams/{id}/leads` | Per-campaign leads, with `?lifecycle`, `?matchConfidence`, `?accountId`, `?segmentId`, `?since`, `?limit`, `?offset`. Pass `?summary=true` for counts only. | | `GET` | `/api/v2/leads` | Cross-campaign poll. Supports `?streamId`, `?since`, `?lifecycle`, `?groupBy=stream`. | | `POST` | `/api/v2/leads/{id}/feedback` | Good / bad feedback (informs future classification) | | `POST` | `/api/v2/leads/{id}/enrich` | On-demand enrichment of one lead | | `POST` | `/api/v2/leads/{id}/export` | Mark a lead exported when you move it into your CRM | ### Account management | Method | Endpoint | Purpose | | ------ | ----------------------------------- | --------------------------------------------- | | `GET` | `/api/v2/accounts` | List tracked accounts | | `POST` | `/api/v2/accounts/{id}/sleep` | Sleep an account (decrements Stripe quantity) | | `POST` | `/api/v2/accounts/{id}/wake` | Wake a sleeping account | | `POST` | `/api/v2/accounts/{id}/change-tier` | Upgrade or downgrade an account's tier | ### Account + billing visibility | Method | Endpoint | Purpose | | ------ | ------------ | --------------------------------------------------------------- | | `GET` | `/api/v2/me` | Identity + per-account budgets + credit balance + totals rollup | ### Webhooks | Method | Endpoint | Purpose | | -------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `POST` | `/api/v2/webhooks/subscribe` | Subscribe a webhook for real-time lead delivery. Filter: `{streamId?, accountId?, sourceType?, feedbackStatus?, matchConfidence?}` | | `DELETE` | `/api/v2/webhooks/subscribe` | Unsubscribe | ### DM templates + notification settings | Method | Endpoint | Purpose | | ------------------ | ------------------------------- | ----------------------------------------- | | `GET, POST` | `/api/v2/dm-templates` | List / create canned DM templates | | `GET, PUT, DELETE` | `/api/v2/dm-templates/{id}` | Single-template CRUD | | `GET, PUT` | `/api/v2/notification-settings` | Team-default summary frequency + channels | ### Lookups (Chrome extension surface) One-shot enrichment + segment-from-description for the extension. Distinct from the campaign-centric surface above. | Method | Endpoint | Purpose | | -------- | ------------------------------------------- | ---------------------------------------------------- | | `POST` | `/api/v2/lookups/enrich` | Look up LinkedIn URL + email for an X handle | | `POST` | `/api/v2/lookups/enrich-email` | Email-only lookup | | `GET` | `/api/v2/lookups/enrich/history` | Recent LinkedIn lookups | | `GET` | `/api/v2/lookups/enrich-email/history` | Recent email lookups | | `POST` | `/api/v2/lookups/segments/from-description` | Create a segment from a natural-language description | | `DELETE` | `/api/v2/lookups/api-keys/{id}` | Self-revoke (extension sign-out) | ## Async operations Some operations run asynchronously and return a run ID for polling: * `POST /api/v2/streams/{id}/run` returns `{queued: [{accountId, syncRunId}], skipped: [{accountId, reason}]}`. Poll each with `GET /api/v2/streams/{id}/runs/{runId}`. * `POST /api/v2/streams/{id}/re-test` returns the same shape. Poll the same way. ## Billing effects Several operations affect your Stripe subscription: * `POST /api/v2/streams` with new handles - upserts tracked accounts + increments Stripe quantity (response carries `billingDelta` per account) * `PATCH /api/v2/streams/{id}` with new handles in `pairs` - same * `POST /api/v2/accounts/{id}/wake` - increments Stripe quantity * `POST /api/v2/accounts/{id}/sleep` - decrements Stripe quantity * `POST /api/v2/accounts/{id}/change-tier` - upgrades apply immediately with proration; downgrades schedule for end-of-period ## Rate limits 60 requests per minute per Bearer key. Response headers: | Header | Description | | ----------------------- | --------------------------------------------- | | `X-RateLimit-Limit` | Maximum requests per window (60) | | `X-RateLimit-Remaining` | Requests remaining in current window | | `Retry-After` | Seconds until the window resets (only on 429) | In-app session-cookie calls aren't rate-limited at this layer. ## Errors Paywall and validation errors return a structured `{ error: '', message: '' }` body. Branch on the code, not the message text. See the [Migration guide](/api-reference/migration-v1-to-v2#paywall-error-codes) for the full list. Standard HTTP status codes apply: | Status | Description | | ------ | --------------------------------------------------------- | | `200` | Success | | `201` | Created | | `400` | Invalid request (bad parameters or body) | | `401` | Missing, invalid, or revoked API key | | `403` | Forbidden (subscription required, budget exhausted, etc.) | | `404` | Resource not found | | `409` | Conflict (duplicate, concurrent operation) | | `410` | Endpoint removed (see migration guide) | | `422` | Unprocessable (e.g. private account) | | `429` | Rate limit exceeded or credits exhausted | | `502` | Upstream error (Stripe, etc.) | | `503` | Service temporarily unavailable | # List DM Templates Source: https://docs.catchthegoodones.com/api-reference/list-dm-templates GET https://www.catchthegoodones.com/api/v2/dm-templates Returns all DM templates for the authenticated user Returns all DM templates for the authenticated user. ### Headers | Header | Required | Description | | --------------- | -------- | ------------------------------- | | `Authorization` | Yes | `Bearer ctgo_your_api_key_here` | ### Example ```bash theme={null} curl https://www.catchthegoodones.com/api/v2/dm-templates \ -H "Authorization: Bearer ctgo_your_api_key_here" ``` ### Response ```json 200 theme={null} { "templates": [ { "id": 1, "name": "Intro message", "messageBody": "Hi! I noticed you recently followed me. I'd love to connect.", "createdAt": "2026-04-10T12:00:00.000Z", "updatedAt": "2026-04-10T12:00:00.000Z" } ] } ``` # List leads Source: https://docs.catchthegoodones.com/api-reference/list-leads GET /api/v2/leads Cross-stream lead extraction for Zapier-style polling Returns leads across every campaign owned by the user. Useful for Zapier-style polling. For campaign-scoped extraction use [GET /api/v2/streams//leads](/api-reference/stream-leads). ## Query parameters | Name | Type | Description | | ----------- | -------- | --------------------------------------------------------------------------------------------- | | `streamId` | number | Scope to one campaign | | `segmentId` | number | Scope to one segment | | `accountId` | number | Scope to one source account | | `lifecycle` | enum | `"new"` or `"exported"` | | `since` | ISO 8601 | Leads discovered after this timestamp | | `groupBy` | enum | `"stream"` returns `{groups: [{streamId, streamName, leads: [...]}]}` instead of a flat array | ## Response (flat) ```json theme={null} { "data": [ { "id": 1234, "trackedAccount": { "id": 7, "xHandle": "yongfook" }, "profile": { "xUserId": "...", "handle": "alice", "displayName": "..." }, "matchConfidence": "high", "sourceType": "new_follower", "discoveredAt": "2026-06-02T09:01:00Z" } ] } ``` ## Auth Bearer API key. # List segments Source: https://docs.catchthegoodones.com/api-reference/list-segments GET /api/v2/segments List the reusable filter sets you've authored Lists the segments (reusable filter sets) owned by the user. Code-side these are `segments`; user-facing we call them "segments". To create a new segment, see [POST /api/v2/segments](/api-reference/create-segment). To author + attach to a stream in one shot, see [POST /api/v2/streams](/api-reference/create-stream) (the `pairs` field accepts inline segment objects). ## Query parameters | Name | Type | Description | | ----------------- | ------- | ---------------------------------------- | | `excludeArchived` | boolean | Default `true` - hides archived segments | ## Response ```json theme={null} { "segments": [ { "id": 12, "userFacingId": "seg_12", "name": null, "description": "AI founders building developer tools", "genderValues": [], "appliedSearchFilters": { "job_role": ["Founder", "CEO"] }, "followerCountTiers": [], "linkedStreamCount": 2, "createdAt": "2026-06-01T10:00:00Z", "updatedAt": "2026-06-01T10:00:00Z" } ] } ``` ## Notes Editing a segment's filters in place is intentionally NOT exposed on the public API (mutating a segment affects classification across every stream that references it - too easy to get wrong from an integration). Workflow: 1. `POST /api/v2/segments` to create a new segment with the desired filters 2. `PATCH /api/v2/streams/{id}` to swap the affected streams' pairs to use the new segment ## Auth Bearer or session cookie. # List streams Source: https://docs.catchthegoodones.com/api-reference/list-streams GET /api/v2/streams List all your lead-streaming campaigns Lists the campaigns (streams) owned by the authenticated user. Each row carries the campaign settings + the list of `(account, segment)` pairs. ## Query parameters | Name | Type | Description | | ----------------- | ------- | ------------------------------------------ | | `includeInactive` | boolean | Include paused streams. Default `false`. | | `includeArchived` | boolean | Include archived streams. Default `false`. | ## Response ```json theme={null} { "streams": [ { "id": 42, "name": "AI founders watching Yongfook", "syncTimes": ["09:00"], "timezone": "Europe/London", "autoEnrichLinkedin": true, "autoEnrichEmail": true, "emailMode": "any", "deliveryDestination": "leads_only", "webhookUrl": null, "csvEmailRecipients": null, "isActive": true, "isArchived": false, "createdAt": "2026-06-02T10:00:00Z", "updatedAt": "2026-06-02T10:00:00Z", "pairs": [ { "streamPairId": 101, "trackedAccountId": 7, "xHandle": "yongfook", "profileImageUrl": "...", "tier": "starter", "segmentId": 12, "segmentName": null, "segmentDescription": "AI founders building developer tools" } ] } ] } ``` ## Auth Bearer API key or session cookie. See [Authentication](/api-reference/authentication). ## Example ```bash theme={null} curl https://www.catchthegoodones.com/api/v2/streams \ -H "Authorization: Bearer ctgo_..." ``` # List tracked accounts Source: https://docs.catchthegoodones.com/api-reference/list-tracked-accounts GET /api/v2/accounts List the active + sleeping tracked X accounts for the authenticated user Lists the user's tracked accounts. Discovery surface for the sleep / wake / change-tier verbs. There is no `POST /api/v2/accounts` - tracked accounts are upserted as a side effect of stream creation (see [POST /api/v2/streams](/api-reference/create-stream)). ## Example ```bash theme={null} curl https://www.catchthegoodones.com/api/v2/accounts \ -H "Authorization: Bearer ctgo_your_api_key_here" ``` ## Response ```json theme={null} { "active": [ { "id": 7, "xHandle": "yongfook", "tier": "starter", "isActive": true, "profileImageUrl": "https://pbs.twimg.com/profile_images/...", "lastKnownFollowerCount": 12500, "lastKnownTweetCount": 4321, "tags": [], "addedAt": "2026-03-01T00:00:00Z", "assignedSegmentIds": [12, 13], "pendingTier": null, "pendingTierEffectiveAt": null } ], "sleeping": [] } ``` ## Response fields | Field | Type | Description | | ---------------------- | ------------ | ----------------------------------------------------------------------------------- | | `active[]` | array | Currently-tracked accounts (synced + billed) | | `sleeping[]` | array | Accounts that have been slept (paused; not billed) | | `tier` | enum | `"starter"`, `"growth"`, `"pro"` | | `assignedSegmentIds[]` | number\[] | Segment IDs assigned via any active stream containing this account | | `pendingTier` | enum or null | Scheduled tier change effective at `pendingTierEffectiveAt` (end of billing period) | For subscription summary + credits + budgets, see [GET /api/v2/me](/api-reference/me). ## Auth Bearer or session cookie. # Lookup: enrich an X handle Source: https://docs.catchthegoodones.com/api-reference/lookups-enrich POST /api/v2/lookups/enrich One-shot LinkedIn URL + email enrichment for an X handle or profile ID One-shot enrichment for the Chrome extension and external integrators that don't have a lead row yet. For a known lead row, use [Enrich a lead](/api-reference/enrich-lead). ## Body | Field | Type | Description | | ------------------- | ---------------- | ------------------------------------------- | | `x_handle` | string | Exactly one of `x_handle` or `x_profile_id` | | `x_profile_id` | string | Exactly one of `x_handle` or `x_profile_id` | | `requested_outputs` | array (required) | `["linkedin_url"]`, `["email"]`, or both | | `email_mode` | enum | `"verified"` or `"any"`. Default `"any"`. | | `max_cost_credits` | number | Soft cap. Default 5. | ## Response ```json theme={null} { "enrichment_request_id": 999, "status": "complete", "actual_cost_credits": 2, "credit_consumed": true, "credits_remaining": 47, "contact_routes": [ { "route_type": "linkedin_url", "value": "https://linkedin.com/in/alice", "confidence": "high", "provider": "scrapebadger_serp" } ], "linkedin": { "url": "...", "confidence": "high", "alternatives": [], "provider": "scrapebadger_serp" }, "email": { "value": "alice@acme.com", "status": "verified", "provider": "prospeo" } } ``` ## Errors | Code | HTTP | When | | ---------------------- | ---- | --------------------------------------------- | | `missing_identifier` | 400 | Neither x\_handle nor x\_profile\_id supplied | | `ambiguous_identifier` | 400 | Both supplied | | `x_handle_not_found` | 404 | Handle didn't resolve | | `credits_exhausted` | 429 | Insufficient balance | | `profile_fetch_failed` | 503 | Upstream X lookup down | ## Auth Bearer API key (extension or manual). Both debit from team credit balance. # Lookup: enrich email for an X handle Source: https://docs.catchthegoodones.com/api-reference/lookups-enrich-email POST /api/v2/lookups/enrich-email Email-only one-shot enrichment Email-only enrichment. Sibling of [/lookups/enrich](/api-reference/lookups-enrich) when you only want an email address (skips the LinkedIn SERP work). ## Body | Field | Type | Description | | -------------- | ------ | ------------------------------------------- | | `x_handle` | string | Exactly one of `x_handle` or `x_profile_id` | | `x_profile_id` | string | Exactly one of `x_handle` or `x_profile_id` | | `mode` | enum | `"verified"` or `"any"`. Default `"any"`. | ## Response ```json theme={null} { "enrichment_request_id": 999, "status": "complete", "credit_consumed": true, "credits_remaining": 47, "email": { "value": "alice@acme.com", "status": "verified", "provider": "prospeo" } } ``` ## Errors | Code | HTTP | When | | ---------------------- | ---- | ---- | | `missing_identifier` | 400 | | | `ambiguous_identifier` | 400 | | | `x_handle_not_found` | 404 | | | `credits_exhausted` | 429 | | | `profile_fetch_failed` | 503 | | ## Auth Bearer API key. # Lookup: create a segment from a description Source: https://docs.catchthegoodones.com/api-reference/lookups-segment-from-description POST /api/v2/lookups/segments/from-description Natural-language description → applied filter segment Creates a segment from a natural-language description. Used by the Chrome extension's "Create stream from search" flow. The AI pipeline maps the description to canonical filter labels via the audit-079 auto-approve workflow (LLM-proposed labels are materialized into the controlled vocabulary, then re-mapped, in one call). ## Body | Field | Type | Description | | ------------------ | ----------------- | ------------------------------------------------------------------------------------------- | | `description` | string (required) | Natural-language description, max 1000 chars | | `trackedAccountId` | number | Optional - assign the new segment to this tracked account via the default-stream chokepoint | ## Response ```json theme={null} { "segment": { "id": 12, "name": null, "description": "AI founders building developer tools" }, "assignment": { "assigned": true, "alreadyAssigned": false, "position": 0 }, "assignError": null } ``` `assignment` is null when `trackedAccountId` was not supplied. `assignError` is populated on cap-exceeded / not-found / account-inactive. ## Errors | Code | HTTP | When | | ------------------------------- | ---- | ----------------------------------------------------- | | `no_filters_matched` | 400 | LLM couldn't extract any filters from the description | | `cap_exceeded` (in assignError) | - | Tier cap of segments-per-account exceeded | ## Auth Bearer API key (extension or manual). # Get current user Source: https://docs.catchthegoodones.com/api-reference/me GET /api/v2/me Identity + remaining budgets + credit balance + subscription state in one call The canonical "what can I do right now" endpoint. Verifies your API key AND surfaces every budget the UI needs to render paywall hints. ## Request ```bash theme={null} curl https://www.catchthegoodones.com/api/v2/me \ -H "Authorization: Bearer ctgo_your_api_key_here" ``` ## Response ```json theme={null} { "name": "Jane Smith", "email": "jane@example.com", "subscriptionStatus": "active", "isFreeTier": false, "accounts": [ { "accountId": 7, "xHandle": "yongfook", "tier": "starter", "monthlyRetest": { "used": 1, "budget": 12, "remaining": 11, "billingPeriodStart": "2026-05-15T00:00:00Z" }, "dailySync": { "used": 0, "cap": 1, "remaining": 1 } } ], "credits": { "balance": 47, "lowBalanceThreshold": 10, "isLow": false, "autoTopupEnabled": false }, "totals": { "monthlyRetestRemaining": 11, "dailySyncRemaining": 1 } } ``` ## Response fields | Field | Type | Description | | -------------------------- | ------- | ---------------------------------------------------- | | `name` | string | User's display name | | `email` | string | User's email | | `subscriptionStatus` | enum | `active`, `trialing`, `free_user`, `past_due`, etc. | | `isFreeTier` | boolean | Convenience flag | | `accounts[]` | array | Per-account budgets | | `accounts[].monthlyRetest` | object | Re-test budget for this account, this billing period | | `accounts[].dailySync` | object | Daily sync slot count for this account | | `credits` | object | Team credit balance for enrichment | | `totals` | object | Sum-across-accounts rollup | ## Auth Bearer API key or session cookie. # Migrating from API v1 to v2 Source: https://docs.catchthegoodones.com/api-reference/migration-v1-to-v2 Endpoint and field changes from the legacy API to the stream-centred v2 surface (audit 086 amendment 12, 2026-06). ## Summary API v2 is a **stream-centred** redesign. Three primary verbs frame the surface: * **Create streams** - `POST /api/v2/streams` (one-shot: handles + segments + schedule + delivery) * **Update streams** - `PATCH /api/v2/streams/{id}` (any setting, including full-replace pairs and inline segment creation) * **Extract leads** - `GET /api/v2/streams/{id}/leads` (per-stream) or `GET /api/v2/leads` (cross-stream polling) Everything else (sync runs, re-tests, leads, budgets) is reached via stream sub-resources or the small set of identity / utility endpoints. v1 paths return **410 Gone** with a pointer back here. ## Vocabulary * **Stream** - a campaign. Owns N `(tracked_account, segment)` pairs and shared config (schedule, enrich, delivery, csv-email). The primary resource. * **Segment** - a reusable filter set (lead description + applied labels + gender + follower-count tiers). Code-side identifier is still `segments`. * **Lead** - a discovered profile that survived bot-filtering and (optionally) matched one or more segments. Renamed from "people of interest". * **Lookup** - a one-shot enrichment or NL-to-segment operation for the Chrome extension. Distinct surface at `/api/v2/lookups/*`. ## Endpoint mapping ### Identity | v1 path | v2 path | | ---------------- | -------------------------------------------------------------------------------------------- | | `GET /api/v1/me` | `GET /api/v2/me` (now includes `accounts`, `credits`, `subscriptionStatus`, `totals` rollup) | ### Streams (new primary resource) | Old path or behaviour | New path | | --------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | | `POST /api/v1/tracked-accounts` + `POST /api/v1/saved-searches` + assigning + setting sync schedule | `POST /api/v2/streams` (one-shot: handles + inline segments + everything) | | n/a | `GET /api/v2/streams` (list campaigns) | | n/a | `GET /api/v2/streams/{id}` (single campaign with pairs + full config) | | `PUT /api/v1/saved-searches/{id}` (filter edit) | `PATCH /api/v2/streams/{id}` (settings + full-replace `pairs` array with inline-or-id segments) | | `DELETE /api/v1/saved-searches/{id}` | `DELETE /api/v2/streams/{id}` (archives the campaign) | | `POST /api/v1/sync` | `POST /api/v2/streams/{id}/run` (sync every pair in the campaign) | | `POST /api/v1/explore` | `POST /api/v2/streams/{id}/re-test` (optional `{accountId?, segmentId?}` body to scope) | | `GET /api/v1/sync-status` | `GET /api/v2/streams/{id}/runs/{runId}` | | `GET /api/v1/sync-history` | `GET /api/v2/streams/{id}/runs` (per-stream) OR `GET /api/v2/runs` (flat across all streams) | ### Leads | v1 path | v2 path | | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `GET /api/v1/people-of-interest` | `GET /api/v2/leads` (with `?streamId`, `?lifecycle=new\|exported`, `?matchConfidence=high\|medium`, `?since=ISO`, `?groupBy=stream`). `matchConfidence` enum changed from `'full'/'maybe'` to `'high'/'medium'`. | | n/a | `GET /api/v2/streams/{id}/leads` (scoped to a stream, with `?summary=true` for counts only) | | `POST /api/v1/feedback` | `POST /api/v2/leads/{id}/feedback` | | n/a | `POST /api/v2/leads/{id}/enrich` (per-lead on-demand enrichment) | | n/a | `POST /api/v2/leads/{id}/export` (mark exported; sets `x_leads.exported_at`) | ### Account management Tracked accounts no longer have a public "add" verb - adding happens via stream creation. Management verbs survive: | v1 path | v2 path | | ------------------------------------------------------------ | ---------------------------------------------------------------------------------- | | `GET /api/v1/tracked-accounts` | `GET /api/v2/accounts` (active + sleeping lists; `assignedSegmentIds` per account) | | `POST /api/v1/tracked-accounts` | **No public replacement.** Use `POST /api/v2/streams` with the handle in a pair. | | `DELETE /api/v1/tracked-accounts/{id}` (sleep) | `POST /api/v2/accounts/{id}/sleep` | | `PUT /api/v1/tracked-accounts/{id}` (wake) | `POST /api/v2/accounts/{id}/wake` | | `POST /api/v1/tracked-accounts/{id}/change-tier` | `POST /api/v2/accounts/{id}/change-tier` | | `PATCH /api/v1/tracked-accounts/{id}` (assign single search) | `PATCH /api/v2/streams/{id}` with the new pair in the array. | ### Segments Editing a segment's filters in place is intentionally NOT exposed (footgun risk for external integrators - mutating a segment affects classification across every stream that references it). Workflow: create a new segment via `POST`, then PATCH the affected streams to swap. | v1 path | v2 path | | ---------------------------------------------- | ------------------------------------------------------------------------ | | `GET /api/v1/saved-searches` | `GET /api/v2/segments` (with `linkedStreamCount` per row) | | `POST /api/v1/saved-searches` | `POST /api/v2/segments` | | `GET /api/v1/saved-searches/{id}` | Visible via `GET /api/v2/streams/{id}` (pairs include their segment) | | `PUT /api/v1/saved-searches/{id}` | **Not exposed.** Create a new segment + PATCH affected streams. | | `DELETE /api/v1/saved-searches/{id}` | **Not exposed.** Archive the streams that reference the segment instead. | | `POST /api/v1/saved-searches/{id}/assignments` | `PATCH /api/v2/streams/{id}` with the new pair in the array. | ### Sync settings | v1 path | v2 path | | ---------------------------- | ---------------------------------------------------------------------------------------- | | `GET /api/v1/sync-settings` | **Removed.** Per-tier schedules are gone; schedule is per-stream on `streams.syncTimes`. | | `PUT /api/v1/sync-settings` | **Removed.** `PATCH /api/v2/streams/{id}` with `syncTimes` + `timezone`. | | `GET /api/v1/explore/budget` | Folded into `GET /api/v2/me.accounts[].monthlyRetest`. | ### Lookups (Chrome extension surface) | v1 path | v2 path | | ---------------------------------------------- | ------------------------------------------------ | | `POST /api/v1/enrich` | `POST /api/v2/lookups/enrich` | | `GET /api/v1/enrich/history` | `GET /api/v2/lookups/enrich/history` | | `POST /api/v1/enrich-email` | `POST /api/v2/lookups/enrich-email` | | `GET /api/v1/enrich-email/history` | `GET /api/v2/lookups/enrich-email/history` | | `POST /api/v1/saved-searches/from-description` | `POST /api/v2/lookups/segments/from-description` | | `DELETE /api/v1/api-keys/{id}` | `DELETE /api/v2/lookups/api-keys/{id}` | ### Utility (unchanged path, sometimes shape) | Path | Notes | | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `GET, POST /api/v2/dm-templates` | No change. | | `GET, PUT, DELETE /api/v2/dm-templates/{id}` | No change. | | `GET, PUT /api/v2/notification-settings` | No change. | | `POST, DELETE /api/v2/webhooks/subscribe` | Filter shape updated: `segmentId` dropped, `streamId` added, `trackedAccountId` renamed to `accountId`. matchConfidence enum is `'high' \| 'medium'`. | ## Conceptual changes ### Stream is a campaign The keystone of the v2 redesign. One `stream` row owns N `(tracked_account, segment)` pairs and a shared config (schedule, enrich, delivery, csv-email). A segment can appear in multiple streams. A stream can contain multiple segments AND/OR multiple accounts in any combination. ### One-shot stream creation `POST /api/v2/streams` accepts handles (resolves via ScrapeBadger -> kaitoeasyapi, upserts tracked accounts, increments Stripe quantity) AND inline segment objects (creates new `segments` rows) in one call. Existing accounts/segments can be referenced by ID instead. ### Full-replace pair updates `PATCH /api/v2/streams/{id}` accepts a `pairs` array as full-replace semantics: server diffs against existing pairs, adds new (upserting accounts + creating inline segments), removes ones not in the new array. ### Lead lifecycle Leads carry a server-derived `exported_at` field. `?lifecycle=new` filters for `IS NULL` (default). `?lifecycle=exported` filters for `IS NOT NULL`. Exporting is done via `POST /api/v2/leads/{id}/export`. ### matchConfidence enum `'full' / 'maybe'` -> `'high' / 'medium'` (column rewrite in migration 0113). Reflected in `/api/v2/leads`, `/api/v2/streams/{id}/leads`, webhook filter config. ### Per-account billing visibility `GET /api/v2/me` exposes per-account daily-sync slots + monthly re-test budgets, plus credit balance + low-balance flag, plus a `totals` rollup ("how much headroom do I have anywhere right now"). No standalone `/api/v2/budget` - it's folded into `/me`. ### Tracked accounts are second-class in the API Adding accounts happens via stream creation. Removing pairs leaves orphan accounts (tracked but referenced by no active stream); orphans stay tracked + billed until explicitly slept via `POST /api/v2/accounts/{id}/sleep`. Account-level operations (sleep / wake / change-tier) survive as `/api/v2/accounts/{id}/`. ### Webhook filter Filter config supports `{streamId?, accountId?, sourceType?, feedbackStatus?, matchConfidence?}`. `segmentId` is removed. Existing subscriptions stored with `segmentId` filter silently match nothing post-deploy (the filter was a TODO that never actually applied anyway). Re-subscribe with `streamId` if you want filtering. ## Paywall error codes Every server gate that surfaces a paywall returns `{ error: '', message: '' }` with HTTP 403 (or 400 / 429 where appropriate). Branch on the code, not the message text. | Code | When | HTTP | | --------------------------- | -------------------------------------------------------- | ---- | | `free_retest_limit` | Free user tries to re-test | 403 | | `retest_limit` | Starter/Growth re-test budget exhausted | 403 | | `retest_limit_pro` | Pro re-test budget exhausted | 403 | | `daily_sync_limit` | Account hit its daily sync cap | 403 | | `free_account_limit` | Free user trying to add 2nd account | 403 | | `free_account_ip_duplicate` | Free user shares signup IP | 409 | | `subscription_required` | Paid user with lapsed sub | 403 | | `stream_cap_exceeded` | Tier cap of automated streams per account exceeded | 400 | | `stream_multi_segment` | A stream targets exactly one segment; pairs spanned more | 400 | | `credits_exhausted` | Enrichment endpoints out of credits | 429 | | `handle_not_found` | X handle didn't resolve | 404 | | `handle_protected` | X handle is a private account | 422 | | `handle_lookup_failed` | Upstream lookup service down | 503 | | `orphan_label` | Segment write contained labels with no canonical match | 400 | ## Auth (unchanged) API key auth via `Authorization: Bearer ` header. Existing PATs continue working post-deploy. In-app UI calls (cookie session) work transparently on the hybrid-auth routes (`/api/v2/streams*`, `/api/v2/leads*`, `/api/v2/segments`, `/api/v2/accounts/{id}/*`, `/api/v2/me`, `/api/v2/runs`). ## Rate limits (unchanged) 60 requests per minute per Bearer key. Cookie-session in-app calls are not rate-limited at this layer (session middleware gates them). # Re-test a stream Source: https://docs.catchthegoodones.com/api-reference/re-test-stream POST /api/v2/streams/{streamId}/re-test Re-classify cached followers against the campaign's segments Re-classifies cached followers against the campaign's segments. No new follower fetch. Consumes monthly re-test budget per account (`MONTHLY_RETEST_BUDGET_BY_TIER`). ## Body (all optional) | Field | Type | Description | | ----------- | ------ | ------------------------------------ | | `accountId` | number | Re-test all pairs on this account | | `segmentId` | number | Re-test all pairs using this segment | Combine for one specific pair, or send `{}` to re-test every pair in the stream. ## Response ```json theme={null} { "queued": [ { "pairId": 101, "trackedAccountId": 7, "segmentId": 12, "runId": 234 } ], "skipped": [ { "pairId": 102, "trackedAccountId": 8, "reason": "budget_exhausted" } ] } ``` `skipped.reason` is one of `free_tier`, `budget_exhausted`, `account_sleeping`. Poll each queued runId with [GET /streams//runs/](/api-reference/stream-run-status). ## Auth Bearer or session cookie. # Run a stream now Source: https://docs.catchthegoodones.com/api-reference/run-stream POST /api/v2/streams/{streamId}/run Trigger a sync across every pair in a campaign Triggers a sync (X-API follower fetch + classification) across every active pair in the stream. Consumes one daily sync slot per account. For re-test-only (cached profiles, no follower fetch) use [Re-test a stream](/api-reference/re-test-stream). ## Body None. ## Response ```json theme={null} { "queued": [ { "trackedAccountId": 7, "syncRunId": 123 } ], "skipped": [ { "trackedAccountId": 8, "reason": "budget_exhausted" } ] } ``` `skipped.reason` is one of `budget_exhausted` (daily sync cap hit) or `account_sleeping`. Poll each queued runId with [GET /streams//runs/](/api-reference/stream-run-status). ## Errors | Code | HTTP | When | | -------------------- | ---- | ------------------------ | | `Stream not found` | 404 | | | `Stream is archived` | 400 | Restore the stream first | ## Auth Bearer or session cookie. # Sleep a tracked account Source: https://docs.catchthegoodones.com/api-reference/sleep-tracked-account POST /api/v2/accounts/{id}/sleep Pause an account: decrements Stripe quantity but preserves history + stream pairs Puts a tracked account to sleep. Decrements the Stripe subscription quantity for the account's tier. Sleeping accounts stop syncing but their history and stream pairs are preserved. To resume, see [POST /api/v2/accounts//wake](/api-reference/wake-tracked-account). ## Example ```bash theme={null} curl -X POST https://www.catchthegoodones.com/api/v2/accounts/7/sleep \ -H "Authorization: Bearer ctgo_your_api_key_here" ``` ## Response ```json theme={null} { "success": true, "accountId": 7, "asleep": true } ``` ## Errors | Code | HTTP | When | | ------------------- | ---- | --------------------------------------------- | | `Account not found` | 404 | Account not owned by user, or already deleted | ## Auth Bearer or session cookie. # Get leads from a stream Source: https://docs.catchthegoodones.com/api-reference/stream-leads GET /api/v2/streams/{streamId}/leads Extract leads scoped to one campaign Returns leads from one campaign. Supports filtering by lifecycle, match confidence, account, segment, and date. For cross-campaign extraction use [GET /api/v2/leads](/api-reference/list-leads). ## Query parameters | Name | Type | Description | | ----------------- | -------- | -------------------------------------------------------------- | | `lifecycle` | enum | `"new"` (un-exported) or `"exported"` | | `matchConfidence` | enum | `"high"` or `"medium"` | | `accountId` | number | Scope to one source account | | `segmentId` | number | Scope to one segment | | `since` | ISO 8601 | Leads discovered after this timestamp | | `limit` | number | Default 100, max 500 | | `offset` | number | Pagination | | `summary` | boolean | Return only counts (replaces the legacy /lead-counts endpoint) | ## Response (default) ```json theme={null} { "leads": [ { "id": 1234, "streamId": 42, "trackedXAccountId": 7, "segmentId": 12, "matchConfidence": "high", "sourceType": "new_follower", "discoveredAt": "2026-06-02T09:01:00Z", "exportedAt": null, "profile": { "xUserId": "1234567890", "handle": "alice", "displayName": "Alice Smith", "profileImageUrl": "...", "followerCount": 8200, "bio": "..." } } ], "total": 47, "hasMore": false } ``` ## Response with `?summary=true` ```json theme={null} { "highFitCount": 33, "mediumFitCount": 14 } ``` ## Auth Bearer or session cookie. # Get a stream run Source: https://docs.catchthegoodones.com/api-reference/stream-run-status GET /api/v2/streams/{streamId}/runs/{runId} Check the status + progress log of a single sync run Returns a single sync run's status, counts, and progress log. Used for polling progress + final-status reads. The run must belong to the stream (its `(account, segment)` tuple appears in the stream's pairs). ## Response ```json theme={null} { "run": { "id": 123, "streamId": 42, "streamName": "AI founders watching Yongfook", "trackedXAccountId": 7, "xHandle": "yongfook", "segmentId": 12, "segmentName": "AI founders building developer tools", "source": "manual", "status": "running", "mode": "sync-then-search", "triggeredAt": "2026-06-02T09:00:00Z", "completedAt": null, "newFollowersCount": 0, "candidatesPassedCount": 0, "mediumFitCount": 0, "enrichedCount": 0, "errorMessage": null, "progressLog": [ { "step": "fetch_followers", "message": "Fetched 50 new followers", "timestamp": "..." } ] } } ``` ## Status values | Value | Meaning | | ----------- | --------------------------------------- | | `queued` | Waiting for Inngest worker | | `running` | Pipeline in progress | | `succeeded` | Completed cleanly | | `failed` | Aborted with error - see `errorMessage` | ## Auth Bearer or session cookie. # List runs for a stream Source: https://docs.catchthegoodones.com/api-reference/stream-runs GET /api/v2/streams/{streamId}/runs Sync run history scoped to one campaign Returns the sync run history for one stream. Each row is filtered to runs whose `(account, segment)` tuple appears in the stream's pairs. For cross-stream history use [GET /api/v2/runs](/api-reference/sync-history). ## Query parameters | Name | Type | Description | | ------- | ------ | ------------------- | | `limit` | number | Default 30, max 100 | ## Response ```json theme={null} { "runs": [ { "id": 123, "streamId": 42, "streamName": "AI founders watching Yongfook", "trackedXAccountId": 7, "xHandle": "yongfook", "segmentId": 12, "segmentName": "AI founders building developer tools", "source": "cron", "status": "succeeded", "mode": "sync-then-search", "triggeredAt": "2026-06-02T09:00:00Z", "completedAt": "2026-06-02T09:01:23Z", "newFollowersCount": 50, "candidatesPassedCount": 12, "mediumFitCount": 4, "enrichedCount": 8, "errorMessage": null, "progressLog": [] } ] } ``` ## Auth Bearer or session cookie. # Submit feedback on a lead Source: https://docs.catchthegoodones.com/api-reference/submit-feedback POST /api/v2/leads/{leadId}/feedback Mark a lead as good or bad. Feedback is per-(lead, segment) pair. Submits good / bad feedback for a lead. Feedback is per-(lead, segment) - the same lead can be evaluated against multiple segments via different streams, so the body specifies which segment's classification you're voting on. ## Body | Field | Type | Description | | ------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `segmentId` | number (required) | The segment id this feedback applies to | | `feedback` | enum (required) | `"good"` or `"bad"` | | `badReason` | enum | `"bot"`, `"wrong_gender"`, `"wrong_job"`, `"wrong_skill"`, `"wrong_personality"`, `"zapier"`, `"other"`. Default `"zapier"`. | | `badReasonFreeform` | string | Optional free-text reason, max 500 chars | ## Example ```bash theme={null} curl -X POST https://www.catchthegoodones.com/api/v2/leads/1234/feedback \ -H "Authorization: Bearer ctgo_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{ "segmentId": 12, "feedback": "good" }' ``` ## Response ```json theme={null} { "ok": true } ``` ## Errors | Code | HTTP | When | | ------------------------ | ---- | ------------------------- | | `Lead not found` | 404 | Lead not owned by user | | `Saved search not found` | 404 | Segment not owned by user | ## Auth Bearer API key. # List sync runs (flat) Source: https://docs.catchthegoodones.com/api-reference/sync-history GET /api/v2/runs Cross-stream flat list of recent sync runs Returns a flat list of the user's recent sync runs across every campaign. Each row carries the parent stream's id + name (when the run's `(account, segment)` pair belongs to a stream). For per-stream history use [GET /api/v2/streams//runs](/api-reference/stream-runs). ## Query parameters | Name | Type | Description | | ------- | ------ | ------------------- | | `limit` | number | Default 30, max 100 | ## Response ```json theme={null} { "runs": [ { "id": 123, "streamId": 42, "streamName": "AI founders watching Yongfook", "streamIsActive": true, "trackedXAccountId": 7, "xHandle": "yongfook", "segmentId": 12, "segmentName": "AI founders building developer tools", "source": "cron", "status": "succeeded", "triggeredAt": "2026-06-02T09:00:00Z", "completedAt": "2026-06-02T09:01:23Z", "newFollowersCount": 50, "candidatesPassedCount": 12, "mediumFitCount": 4, "enrichedCount": 8, "errorMessage": null, "progressLog": [] } ] } ``` `streamId` / `streamName` / `streamIsActive` are null for sync runs whose `(account, segment)` pair no longer belongs to any stream (orphaned manual runs). ## Auth Bearer or session cookie. # Update DM Template Source: https://docs.catchthegoodones.com/api-reference/update-dm-template PUT https://www.catchthegoodones.com/api/v2/dm-templates/{id} Update an existing DM template Updates an existing DM template. Both fields are required. ### Headers | Header | Required | Description | | --------------- | -------- | ------------------------------- | | `Authorization` | Yes | `Bearer ctgo_your_api_key_here` | | `Content-Type` | Yes | `application/json` | ### Path parameters | Parameter | Type | Description | | --------- | ------- | --------------- | | `id` | integer | The template ID | ### Body | Field | Type | Required | Description | | ------------- | ------ | -------- | ------------------------------------------------- | | `name` | string | Yes | Template name (1-100 characters). Must be unique. | | `messageBody` | string | Yes | Message content (1-2000 characters) | ### Example ```bash theme={null} curl -X PUT https://www.catchthegoodones.com/api/v2/dm-templates/1 \ -H "Authorization: Bearer ctgo_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{"name": "Updated intro", "messageBody": "Hey! Thanks for following. Let me know if you have any questions."}' ``` ### Response ```json 200 theme={null} { "template": { "id": 1, "name": "Updated intro", "messageBody": "Hey! Thanks for following. Let me know if you have any questions.", "createdAt": "2026-04-10T12:00:00.000Z", "updatedAt": "2026-04-17T12:00:00.000Z" } } ``` ```json 404 theme={null} { "error": "Template not found" } ``` ```json 409 theme={null} { "error": "You already have a template called 'Updated intro'" } ``` # Update Notification Settings Source: https://docs.catchthegoodones.com/api-reference/update-notification-settings PUT https://www.catchthegoodones.com/api/v2/notification-settings Update one or more notification preferences Updates one or more notification preferences. At least one field must be provided. ### Headers | Header | Required | Description | | --------------- | -------- | ------------------------------- | | `Authorization` | Yes | `Bearer ctgo_your_api_key_here` | | `Content-Type` | Yes | `application/json` | ### Body | Field | Type | Required | Description | | -------------------------- | ------- | -------- | ------------------------------------------------------- | | `summaryFrequency` | string | No | `"after_every_sync"`, `"daily"`, `"weekly"`, or `"off"` | | `emailSummariesEnabled` | boolean | No | Enable or disable email summaries | | `telegramSummariesEnabled` | boolean | No | Enable or disable Telegram summaries | At least one field must be provided. ### Example ```bash theme={null} curl -X PUT https://www.catchthegoodones.com/api/v2/notification-settings \ -H "Authorization: Bearer ctgo_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{"summaryFrequency": "after_every_sync", "emailSummariesEnabled": true}' ``` ### Response ```json 200 theme={null} { "summaryFrequency": "after_every_sync", "emailSummariesEnabled": true, "telegramSummariesEnabled": false } ``` ```json 400 theme={null} { "error": "At least one setting must be provided" } ``` # Update a stream Source: https://docs.catchthegoodones.com/api-reference/update-stream PATCH /api/v2/streams/{streamId} Update any setting on a campaign. Pairs are full-replace. Updates any subset of campaign settings. The `pairs` field, when present, is treated as a **full-replace array**: server diffs against existing pairs, adds new (upserting accounts + creating inline segments), removes ones not in the new list. ## Body (all fields optional) | Field | Type | Notes | | --------------------- | ------- | --------------------------------------------------------------------------------------------------- | | `name` | string | Rename the campaign | | `pairs` | array | Full-replace array. Each pair uses the same shape as [Create stream](/api-reference/create-stream). | | `syncTimes` | array | `["HH:MM"]` slots | | `timezone` | string | IANA timezone | | `autoEnrichLinkedin` | boolean | | | `autoEnrichEmail` | boolean | | | `emailMode` | enum | `"verified"` or `"any"` | | `emailDigestOverride` | enum | `"after_every_sync"`, `"daily"`, `"weekly"`, `"off"` or null | | `telegramOverride` | boolean | or null | | `deliveryDestination` | enum | `"leads_only"` or `"leads_plus_webhook"` | | `webhookUrl` | string | or null | | `csvEmailRecipients` | object | `{to, cc?, format}` or null | | `isActive` | boolean | Pause/resume | | `isArchived` | boolean | | ## Response ```json theme={null} { "id": 42, "pairsDiff": { "addedAccounts": [{ "handle": "newhandle", "accountId": 9, "accountCreated": true, "billingDelta": "stripe_quantity_incremented" }], "addedSegments": [{ "segmentId": 15, "segmentCreated": true }] } } ``` `pairsDiff` is omitted when `pairs` wasn't in the request body. ## Auth Bearer or session cookie. # Wake a tracked account Source: https://docs.catchthegoodones.com/api-reference/wake-tracked-account POST /api/v2/accounts/{id}/wake Resume a slept account: re-activates syncing and increments Stripe quantity Wakes a sleeping account. Increments the Stripe subscription quantity for the tier. Requires an active subscription. ## Example ```bash theme={null} curl -X POST https://www.catchthegoodones.com/api/v2/accounts/7/wake \ -H "Authorization: Bearer ctgo_your_api_key_here" ``` ## Response ```json theme={null} { "success": true, "accountId": 7, "awake": true } ``` ## Errors | Code | HTTP | When | | ----------------------- | ---- | ------------------------------------ | | `subscription_required` | 403 | User has no active paid subscription | | `Account not found` | 404 | | ## Auth Bearer or session cookie. # Webhooks Source: https://docs.catchthegoodones.com/api-reference/webhooks Register and manage webhook subscriptions for real-time notifications # Webhooks Webhook subscriptions let you receive real-time notifications when new leads are discovered. Primarily used by the Zapier integration but works with any system that accepts webhook POSTs. When a sync completes, Catch The Good Ones sends a POST request to each registered webhook URL with the newly discovered leads. **Filter changes in v2 amendment-12**: `segmentId` has been removed (segments are not first-class in the public filter surface); `streamId` is the new way to filter by campaign. `trackedAccountId` renamed to `accountId`. Existing subscriptions with the old `segmentId` filter silently match nothing post-deploy - re-subscribe with `streamId` if you want filtering. ## Subscribe Register a webhook URL to receive notifications. ### Headers | Header | Required | Description | | --------------- | -------- | ------------------------------- | | `Authorization` | Yes | `Bearer ctgo_your_api_key_here` | | `Content-Type` | Yes | `application/json` | ### Body | Field | Type | Required | Description | | ------------------------------ | ------- | -------- | ---------------------------------------------------------------- | | `targetUrl` | string | Yes | The URL to receive webhook POST requests | | `filterConfig` | object | No | Optional filters to narrow which discoveries trigger the webhook | | `filterConfig.streamId` | integer | No | Only send leads from this campaign | | `filterConfig.accountId` | integer | No | Only send leads from this tracked account | | `filterConfig.sourceType` | string | No | `"follower"` or `"liker"` | | `filterConfig.feedbackStatus` | string | No | `"good"` or `"bad"` | | `filterConfig.matchConfidence` | string | No | `"high"` (high fit lead) or `"medium"` (medium fit lead) | ### Example ```bash theme={null} curl -X POST https://www.catchthegoodones.com/api/v2/webhooks/subscribe \ -H "Authorization: Bearer ctgo_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{"targetUrl": "https://hooks.zapier.com/hooks/catch/123456/abcdef/", "filterConfig": {"sourceType": "follower"}}' ``` ### Response ```json theme={null} { "id": 1 } ``` Returns the subscription ID. Save this - you need it to unsubscribe. ### Notes * Subscribing the same URL twice updates the filter config (idempotent). * Different URLs can have different filter configs, so you can set up multiple Zaps with different filters. *** ## Unsubscribe Remove a webhook subscription. ### Headers | Header | Required | Description | | --------------- | -------- | ------------------------------- | | `Authorization` | Yes | `Bearer ctgo_your_api_key_here` | | `Content-Type` | Yes | `application/json` | ### Body | Field | Type | Required | Description | | ----- | ------- | -------- | -------------------------------------------------------- | | `id` | integer | Yes | The subscription ID returned from the subscribe endpoint | ### Example ```bash theme={null} curl -X DELETE https://www.catchthegoodones.com/api/v2/webhooks/subscribe \ -H "Authorization: Bearer ctgo_your_api_key_here" \ -H "Content-Type: application/json" \ -d '{"id": 1}' ``` ### Response ```json theme={null} { "success": true } ``` Unsubscribing a non-existent subscription returns 200 (idempotent). *** ## Webhook payload format When a sync completes, each registered webhook URL receives a POST request with an array of leads. The payload format is identical to the [List leads](/api-reference/list-leads) response. Payloads are chunked into batches of 50 leads per request. Each lead is a separate item, so if 120 leads are discovered, you receive 3 webhook calls (50 + 50 + 20). Delivery retries up to 3 times with exponential backoff if the target URL is unreachable. # Adding X Accounts Source: https://docs.catchthegoodones.com/help/adding-x-accounts How to add and manage tracked X accounts # Adding and Managing X Accounts Track X (formerly Twitter) accounts to discover who is engaging with them. You can track your own accounts or any public account. ## Adding an account 1. In the sidebar, click **X** under Accounts 2. Enter the X handle you want to track (without the @ symbol) 3. Select a tier for the account (Starter, Growth, or Pro) 4. Click **Add** 5. The app will verify the handle exists and add it to your list ## What happens after adding an account Once added, the account will be included in your next scheduled sync. During a sync: * New followers are fetched * Recent post likers are identified * Bot accounts are filtered out * Real people are classified using your defined filters Each tracked account card shows: * The account handle * When the last scan ran * How many candidates were found ## Removing an account To stop tracking an account: 1. Go to the **X** page in the sidebar 2. Find the account you want to remove 3. Click the remove button Removing an account stops future syncs for that handle. Previously discovered leads from that account remain visible on your Leads page. ## Common issues ### "Handle not found" The handle may not exist or the account may be private/suspended. Double-check the spelling and try again. ### "Already tracking this account" You cannot add the same handle twice. Check your tracked accounts list - it should already be there. ## Account tiers Each tracked account is billed individually at the tier you select (Starter, Growth, or Pro). You can mix tiers across accounts. The tier determines sync frequency and daily budget per account. Visit the [Pricing page](https://www.catchthegoodones.com/pricing) for details on each tier. Instead of permanently deleting accounts, you can "put them to sleep" (Moon icon) - this pauses all syncs and stops the subscription charge. Sleeping accounts can be woken up at any time, and historical data is preserved. # Using AI to Describe Your Audience Source: https://docs.catchthegoodones.com/help/ai-describe-filters Let AI set your filters from a plain English description # Using AI to Describe Your Ideal Audience Instead of manually selecting individual filter values, you can describe your ideal audience in plain English and let AI configure your filters automatically. ## How to use Describe mode 1. Go to the **Search** page in the sidebar 2. Type a description of who you are looking for in the search input, for example: * "Female tech founders with 10K+ followers" * "People with AI skills in the marketing industry" * "Creative professionals with large audiences" 3. Click **Apply Filters** The AI will analyse your description and set the appropriate filter values across all relevant dimensions (gender, follower tier, and any active filter categories). ## Two modes: Replace and Add When applying AI-generated filters, you can choose between two modes: ### New filter set (Replace) Replaces all your current filters with the AI-suggested ones. This is the default and is useful when starting fresh or completely changing your criteria. ### Add filters that don't conflict Keeps your existing filters and only adds new ones for dimensions you have not already configured. If the AI suggests filters for a dimension you have already set, those suggestions are skipped and shown in an amber notice. ## What happens when terms are missing If your description includes niche terms that are not yet in the vocabulary, the AI will show them in a blue box with "Suggest" buttons. Clicking Suggest submits the term for review. Once approved, it will be available for future filter applications. ## Guidance hints The search input shows hint chips to help you write effective descriptions. These hints are pulled dynamically from the available filter categories and update automatically when new categories are approved. ## Tips for good descriptions * Be specific: "Female SaaS founders" works better than "business people" * Mention follower size if relevant: "with 10K+ followers" * Combine dimensions: "Creative tech professionals with large audiences who are interested in AI" # API Key Management Source: https://docs.catchthegoodones.com/help/api-key Generate and manage your API key for programmatic access # Managing Your API Key Use an API key to access your people of interest data programmatically via the Catch The Good Ones API. ## Generating a key 1. In the sidebar, click **API Key** under General 2. Click **Generate API Key** 3. Your key will be displayed once in a copiable field 4. Copy and store it securely - you will not be able to see it again API keys have the prefix `ctgo_` and are 45 characters long. ## Using your key Include your API key in the `Authorization` header of API requests: ```bash theme={null} curl https://www.catchthegoodones.com/api/v2/leads \ -H "Authorization: Bearer ctgo_your_api_key_here" ``` For full API documentation, visit the [API Reference](https://docs.catchthegoodones.com). ## Regenerating a key If your key is compromised or you need a new one: 1. Go to the **API Key** page 2. Click **Regenerate** 3. Confirm in the dialog 4. Your old key immediately stops working 5. Copy and store the new key ## Revoking a key To disable API access entirely: 1. Go to the **API Key** page 2. Click **Revoke** 3. Confirm in the dialog 4. The key is permanently invalidated After revoking, the page returns to the "no key" state. You can generate a new key at any time. ## Rate limits API requests are limited to **60 requests per minute** per key. Rate limit status is returned in response headers (`X-RateLimit-Limit`, `X-RateLimit-Remaining`). If you exceed the limit, you will receive a `429` response with a `Retry-After` header indicating when you can retry. ## Zapier integration Catch The Good Ones has a native Zapier app. Search for "Catch The Good Ones" in Zapier to set up automated workflows triggered when new people of interest are discovered. # Getting Started Source: https://docs.catchthegoodones.com/help/getting-started Your first steps with Catch The Good Ones # Getting Started with Catch The Good Ones Welcome to Catch The Good Ones! This guide walks you through setting up your account and discovering your first people of interest. ## What is Catch The Good Ones? Catch The Good Ones helps you understand **who** is engaging with your social media content - not just how many. It surfaces people of interest who have recently followed, liked, or engaged with your tracked accounts. You can also explore another account's followers to find high-value people to connect with - useful for competitor analysis, finding collaborators, or building targeted audiences. ## Step 1: Create your account 1. Visit [catchthegoodones.com](https://www.catchthegoodones.com) and click **Sign Up** 2. Enter your email and create a password 3. You will be taken to the Streams page ## Step 2: Add an X account to track 1. In the sidebar, click **X** under Accounts 2. Enter the X handle you want to track (e.g. `elonmusk`) 3. Select a tier for the account (Starter, Growth, or Pro) 4. The handle will be verified and added to your tracked accounts list ## Step 3: Create a saved search Before your first sync runs, you need to tell the app what makes someone a "good one" for you. 1. Go to the **Search** page in the sidebar 2. Type a description of your ideal audience in plain English (e.g. "Female tech founders with 10K+ followers") 3. Or use the inline edit panel to manually select values across filter dimensions 4. Your first saved search becomes the default for all tracked accounts ## Step 4: Wait for your first sync Your first sync will run automatically based on your sync schedule. You can also trigger a manual sync from the Sync page. During a sync, Catch The Good Ones will: * Fetch new followers of your tracked accounts * Fetch people who liked recent posts * Remove bot accounts * Classify real people using AI-powered filters you have defined * Surface high fit and medium fit leads on the Leads page ## Step 5: Review your leads Once a sync completes, visit the **Leads** page to see the people who matched your filters. Each lead card shows: * Profile photo, name, and handle * Follower count and engagement signals (e.g. "Followed you", "Liked 2 posts") * Classification details based on your filter criteria ## What's next? * [Set up notifications](/help/notifications) to get alerted when new leads are found * [Generate an API key](/help/api-key) to integrate with Zapier or your own tools * [Configure your sync schedule](/help/sync-schedule) to control when syncs run # Notifications Source: https://docs.catchthegoodones.com/help/notifications Set up email and Telegram notifications for new discoveries # Setting Up Notifications Get notified when Catch The Good Ones discovers new people of interest after a sync. ## Notification settings Go to **Notifications** in the sidebar under Sync. Here you can configure: ### Frequency * **Every sync** - Get notified after every sync that finds new people * **Daily** - Get one summary per day, even if multiple syncs ran * **Weekly** - Get one summary per week ### Email summaries Toggle email summaries on or off. When enabled, you will receive an email after each qualifying sync with: * A summary of how many new high fit and medium fit leads were found * A link to your Leads page to review them ### Telegram summaries Connect your Telegram account to receive summaries via Telegram: 1. On the Notifications page, click the Telegram connection link 2. This opens a conversation with the Catch The Good Ones Telegram bot 3. Click **Start** in Telegram to complete the connection 4. You will see a confirmation message in Telegram Once connected, you will receive Telegram messages with a summary of new people of interest, showing up to 10 people per message. ## Important notes ### Both channels disabled If you disable both email and Telegram, you will see an amber warning on the Notifications page. No notifications will be sent, but your syncs will still run normally. ### Telegram connection expiry The Telegram connection link expires after 15 minutes for security. If it expires, generate a new one from the Notifications page. ### Telegram bot blocked If you block the Telegram bot, Telegram notifications will be automatically disabled. Re-enable them by unblocking the bot and reconnecting. ### No new people If a sync runs but finds no new people matching your filters, no notification is sent. # Setting Up Filters Source: https://docs.catchthegoodones.com/help/setting-up-filters How to configure your saved searches to find the right people # Setting Up Filters Filters determine which people Catch The Good Ones surfaces as your "people of interest". You configure them as saved searches on the **Search** page. ## Where to find filters Go to the **Search** page in the sidebar. Here you can create saved searches that define your filter criteria. Each saved search can be assigned to one or more tracked accounts. ## Creating a saved search You can create a saved search in two ways: 1. **AI-assisted** - Type a plain English description of who you are looking for (see [Using AI to Describe Your Audience](/help/ai-describe-filters)) 2. **Manual** - Use the inline edit panel on any search entry to select values across filter dimensions using type-and-search multi-select ## Filter dimensions Filter dimensions include gender, follower tier, and customisable AI-powered categories. Categories are dynamic - new ones can be suggested by users and added without code deployment. The available dimensions are pulled from the database and update automatically when new categories are approved. ### How it works * Select the values you want within each dimension * A person must match at least one selected value in each active dimension to appear as a person of interest * Classification uses a soft gate - profiles are excluded only on explicit contradictory evidence, not absence of signal * Candidates receive a match confidence of `"high"` (high fit lead - all dimensions confirmed) or `"medium"` (medium fit lead - partial evidence) ### Suggesting new terms If a filter dimension is missing a term you need, you can suggest it from the inline edit panel. You can also suggest entirely new filter categories (e.g. ethnicity, nationality). Suggestions are reviewed and can be approved without code deployment. ## Filters must be set before syncing If you have no saved searches, syncs will not run. Create at least one saved search before your first sync. ## Assigning searches to accounts Each tracked account can be assigned a different saved search. On the **X** page, each account shows its assigned search and a "Specify search" button that links to the Search page. The first saved search you create becomes the default for all accounts. ## Re-evaluating existing data When you create or edit a saved search, you can use the **Explore** feature (Telescope icon) to re-run your filters against already-synced followers and likers without using external API credits or waiting for the next sync. See [Explore](/help/ai-describe-filters) for details. # Sync Budget Source: https://docs.catchthegoodones.com/help/sync-budget Understanding your sync budget and the Pro boost feature # Understanding Your Sync Budget Each subscription tier has a daily budget that determines how many people can be processed per sync. ## How budgets work Each tier has a per-account daily budget that determines how many followers, posts, and likers are processed per sync: * **Starter**: 50 followers, 5 posts, 5 likers per account per day * **Growth**: 100 followers, 10 posts, 10 likers per account per day * **Pro**: 500 followers, 15 posts, 15 likers per account per day Budget is tracked per account per day and resets at midnight UTC. Usage is visible on the Sync page. ## Delta-based fetching Syncs only fetch **new** followers since the last scan - not your entire follower list every time. This means your budget is spent on discovering new people, not re-processing existing ones. ## Pro Boost (pull-forward) Pro tier users can "boost" a sync by pulling forward budget from future days in the billing period: 1. Go to the **Sync** page 2. Use the boost slider on a Pro account to select how many days to pull forward (up to 7) 3. Click **Sync Now** 4. The boosted sync runs immediately with the combined budget After a boosted sync, regular syncs are paused until the pulled-forward days have passed. You will see a message: "Syncs paused until \[date]". ### Who can use Boost? * Only **Pro** tier users * You must have remaining days in your billing period * You cannot boost while a previous boost is still in its skip period * The boost slider is disabled on your very first sync ## Budget exhausted If your daily budget runs out during a sync, the fetch stops at the budget limit. Remaining candidates will be picked up in the next sync. ## Budget reset Your budget resets automatically when your Stripe subscription renews at the start of each billing period. # Sync Schedule Source: https://docs.catchthegoodones.com/help/sync-schedule How to configure when your syncs run # Configuring Your Sync Schedule Syncs are how Catch The Good Ones discovers new people engaging with your tracked accounts. You can control when they run. ## Viewing your sync schedule Go to **Sync** in the sidebar under Sync Schedule. This page shows: * Your current sync times * Your timezone setting * Recent sync history with status badges, dates, and counts ## Setting sync times Choose what time(s) of day your syncs should run. The number of sync times available depends on your subscription tier: * **Starter**: 1 sync per day * **Growth**: 1 sync per day * **Pro**: Up to 4 syncs per day ## Timezone Your timezone is auto-detected from your browser on first visit. You can change it on the Sync page. Changing your timezone applies to all sync schedules. ## Sync tiers If you have accounts on different subscription tiers, you will see separate tabs for each tier on the Sync page. Each tier has its own sync schedule that you can configure independently. ## What happens during a sync 1. **Follower fetch** - New followers since the last sync are collected 2. **Like fetch** - People who liked recent posts are identified 3. **Bot filter** - Automated accounts are removed 4. **Classification** - Real people are classified using AI against your filter criteria 5. **Results** - High fit and medium fit leads appear on your Leads page ## Manual sync If you do not want to wait for a scheduled sync, you can trigger one manually using the **Sync Now** button on each account on the Sync page. Each on-demand sync uses one of your daily sync slots. See [Sync Budget](/help/sync-budget) for details on the Pro boost feature. ## Sync history The Sync page shows a history of past syncs with: * Status (succeeded, failed) * When it ran * How many candidates were found # Understanding Your Leads page Source: https://docs.catchthegoodones.com/help/understanding-dashboard How to read and use the Leads page # Understanding Your Leads page The Leads page is where you see the high fit and medium fit leads that Catch The Good Ones has discovered for you. ## Lead cards Each person who matches your filters appears as a card showing: * **Profile photo, name, and handle** - from their X profile * **Follower count** - grouped by tier (nano, micro, mid, macro, mega) * **Engagement signals** - how they interacted with your tracked account (e.g. "Followed you", "Liked 3 posts") * **Filter match tags** - mint-coloured tags showing which of your filter criteria they matched * **Source account** - which tracked account they engaged with ## Grouping by date People are grouped under date headers (e.g. "March 13, 2026") based on when they were discovered. The most recent discoveries appear at the top. ## Unified cards If the same person both followed your account and liked a post, they appear as a single card with all their engagement signals combined (e.g. "Followed you - Liked 2 posts"). They are not duplicated. ## High fit and Medium fit lead sections The Leads page splits results into **High fit leads** (all filter dimensions confirmed) and **Medium fit leads** (partial evidence) sections, based on the match confidence from classification. ## Ordering People are ordered by relevance within each date group. After 3+ Good One ratings on a search, cards are reordered by match likelihood based on patterns from your feedback. ## Empty states ### "Add an X account" You have not added any tracked accounts yet. Go to the X page in the sidebar to add one. ### "First scan hasn't run yet" You have tracked accounts but no sync has run yet. Check the [Sync page](/help/sync-schedule) to see when your next sync is scheduled, or trigger a manual sync. ### "No results match your filters" A sync has run but nobody matched your current filter criteria. Try adjusting your saved search on the Search page to broaden your criteria. # Waitlisted Platforms Source: https://docs.catchthegoodones.com/help/waitlisted-platforms Instagram, TikTok, and YouTube platform support # Waitlisted Platforms Catch The Good Ones currently supports **X** (formerly Twitter) for discovering people of interest. Support for additional platforms is in development. ## Upcoming platforms ### Instagram Track Instagram accounts to discover followers and engagers of interest. Join the waitlist to be notified when Instagram support launches. ### TikTok Track TikTok accounts to discover who is engaging with your content. Join the waitlist to be notified when TikTok support launches. ### YouTube Track YouTube channels to find engaged subscribers and commenters. Join the waitlist to be notified when YouTube support launches. ## Joining a waitlist 1. In the sidebar, click the platform name under Accounts (e.g. **TikTok (waitlist)**) 2. On the platform page, click **Join the Waitlist** 3. The button will change to a confirmed state 4. Your waitlist status persists - you will still see the confirmed state after refreshing ## What happens when a platform launches When a waitlisted platform becomes available, users on the waitlist will be notified. The platform will then appear in your sidebar as an active option, similar to how X works today. ## Feature parity When new platforms launch, they will support the same core features as X: * Tracked account management * Follower and engagement discovery * AI-powered classification using your filters * High fit and medium fit leads on your Leads page * API access and notifications