{"openapi":"3.1.0","info":{"title":"Cleanlist API (v2)","description":"Versioned public API surface (v2) for the Cleanlist MCP and direct API consumers: workspace, lead lists, enrichment, smart agents, sync, and export.","version":"2.0.0"},"paths":{"/api/v2/whoami":{"get":{"tags":["Public Workspace"],"summary":"Get current identity & entitlements","description":"Introspect the authenticated principal and its effective entitlements.\nReturns the caller's identity (user, org, auth type, scopes) plus the\nresolved plan `tier`, any `appsumo_tier`, and the merged `features` list\nthe server-side gates actually enforce — letting clients (and the MCP)\ntier-filter their advertised tools before hitting a gated route. This\nendpoint is free and deducts no credits; for API-key auth the returned\n`user_email` is masked so a leaked key can't reveal the creator's address,\nand display-name lookups are best-effort (null on a transient miss).\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/authentication","operationId":"whoami_api_v2_whoami_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhoamiResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v2/credits/balance":{"get":{"tags":["Public Workspace"],"summary":"Get credit balance","description":"Return the current spendable credit balance for the caller's organization.\nReads the authoritative `organizations.credits_count` and returns it with a\nshort `agent_instructions` nudge summarizing remaining credits. Requires the\n`credits:read` scope and deducts no credits itself. Use this before large\noperations, and pair it with `POST /credits/estimate` to confirm a specific\naction is affordable before spending.\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/credits","operationId":"credits_balance_api_v2_credits_balance_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreditsBalanceResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v2/credits/estimate":{"post":{"tags":["Public Workspace"],"summary":"Estimate cost & get a signed quote","description":"Pre-flight a paid operation and mint a signed, single-use `quote_id`.\nThe quote binds the canonical request (tool, list_id|filters|cohort, scope,\nrow_count, org) via HMAC and must be presented at execution time by every\npaid bulk tool (`enrich_list`, `run_smart_agent`, `sync_to_crm`,\n`sync_to_sequencer`, cohort enrichments). Row count is derived\nauthoritatively from the list or cohort — never trusted from the caller —\nso a small quote can't be replayed against a large job. Requires the\n`credits:read` scope; estimating itself is free, and the response reports\nwhether the org has `sufficient` credits to proceed.\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/credits","operationId":"credits_estimate_api_v2_credits_estimate_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EstimateCostRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EstimateCostResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v2/api-keys":{"get":{"tags":["Public Workspace"],"summary":"List API keys","description":"List the caller's API keys, showing only the prefix and last 4 characters\n— full secret values are never returned. Results are user-scoped and further\nfiltered to the calling token's organization (defense in depth), so a Clerk\nuser in multiple orgs only sees keys belonging to the active org. Requires\nthe `admin:api_keys` scope and deducts no credits. Scopes are granted\nper-user today, so each key reports the caller's own scopes.\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/authentication","operationId":"list_api_keys_endpoint_api_v2_api_keys_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeysResponse"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v2/usage":{"get":{"tags":["Public Workspace"],"summary":"Get usage report","description":"Aggregate this organization's public-API request log over the last `days`\n(1-365, default 7), grouped by `tool`, `key`, `day`, or `error`. Returns\nper-bucket call counts and error counts plus window totals, so admins can\naudit traffic and spot failing integrations. Requires the `admin:api_keys`\nscope and deducts no credits. Note: until the request log gains cost/\ntool-name columns, `group_by=\"tool\"` buckets by request path and\n`credits_spent` is always 0.0.\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/credits","operationId":"usage_report_api_v2_usage_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"days","in":"query","required":false,"schema":{"type":"integer","maximum":365,"minimum":1,"default":7,"title":"Days"}},{"name":"group_by","in":"query","required":false,"schema":{"enum":["tool","key","day","error"],"type":"string","default":"tool","title":"Group By"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UsageReportResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/lead-lists":{"post":{"tags":["Public Lead Lists"],"summary":"Create a lead list","description":"Create a private LeadList in the caller's workspace.\n\nIdempotent on name: MCP agents sometimes fire create_list twice for one\n\"make a list\" request (seconds apart), leaving an orphan empty list. If\nthe caller already OWNS an exact same-name list (case-insensitive),\nreuse it instead of inserting a duplicate — returns 200 + `reused: true`.\nStrictly caller-scoped: a user never reuses a teammate's org-visible\nlist, and match is exact name only, so \"Q3 Leads\" never reuses\n\"Q3 Leads List\".\n\nRequires the `lists:write` scope. Public-tier keys carry no contact-list\ncap, but the cap is still enforced defensively (403 `list_cap_reached`).\nPass an optional `folder_id` you own to file the new list on creation.\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/lead-lists · https://docs.cleanlist.ai/guides/managing-lists","operationId":"create_list_api_v2_lead_lists_post","security":[{"HTTPBearer":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateListRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"get":{"tags":["Public Lead Lists"],"summary":"List lead lists","description":"Paginate the lead lists the caller can access (owned, org-wide, shared).\n\nReturns a page of `ListSummary` rows with live lead counts and owner\nnames. Page forward with `limit` (1–100, default 50) and the opaque\n`cursor` from the previous response; omit `cursor` to start at the top.\nPass `folder_id` to scope the page to one folder. Requires the\n`lists:read` scope.\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/lead-lists · https://docs.cleanlist.ai/guides/managing-lists","operationId":"list_lists_api_v2_lead_lists_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"folder_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Folder Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":50,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListsPageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/lead-lists/{list_id}":{"get":{"tags":["Public Lead Lists"],"summary":"Get a lead list","description":"Return one LeadList accessible to the caller.\n\n`lead_count` is derived from a live membership COUNT rather than the\ndenormalized `prospect_count` column, so a just-completed add/remove is\nreflected immediately and any historical counter drift can't surface a\nwrong number here.\n\nResolves any list you can access — owned, org-visible, or shared with\nyou — and 404s otherwise so private lists never leak. Requires the\n`lists:read` scope.\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/lead-lists · https://docs.cleanlist.ai/guides/managing-lists","operationId":"get_list_api_v2_lead_lists__list_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["Public Lead Lists"],"summary":"Update a lead list","description":"Rename a list (and optionally re-describe it / move it between folders).\n\nPATCH semantics: only the fields present in the body change. A `null`\n`description` clears it; a `null` `folder_id` unfiles the list; an\nomitted field is left untouched. Renaming to the current name is an\nidempotent 200 no-op. Owner-only + is_protected guards mirror the\nportal's edit path.\n\nAt least one of `name`, `description`, or `folder_id` must be present;\nan empty body is a 422. Requires the `lists:write` scope.\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/lead-lists · https://docs.cleanlist.ai/guides/managing-lists","operationId":"update_list_api_v2_lead_lists__list_id__patch","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicLeadListUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListDetailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Public Lead Lists"],"summary":"Delete a lead list","description":"Delete a list and its lead memberships. Leads survive.\n\nHard delete via `delete_with_dependencies` (mirrors the portal):\nsmart/action columns, CRM-contact links, per-user state, shares,\nraw-person stubs, and junction rows are removed; the Lead rows\nthemselves are global entities and are NOT deleted — they stay\nfindable via search_people and in any other list they belong to.\n\nOwner-only + is_protected guards mirror the portal. There is no\nin-flight-workflow guard (the portal has none either; the MCP\nconfirm-gate already prevents accidental deletes off a fuzzy name\nmatch). Re-deleting an unknown/already-deleted list returns 404.\n\nOwner-only: a non-owner gets 404 (indistinguishable from missing) and a\nprotected list gets 403 `list_protected`. Requires the `lists:write`\nscope.\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/lead-lists · https://docs.cleanlist.ai/guides/managing-lists","operationId":"delete_list_api_v2_lead_lists__list_id__delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/lead-lists/{list_id}/leads":{"get":{"tags":["Public Lead Lists"],"summary":"List leads in a list","description":"Paginate leads in a list.\n\nFields like `first_name`, `last_name`, `title`, `company` come from\nthe joined Prospect + Company relations — the Lead row itself only\ncarries enrichment results (email, phone, linkedin URL).\n\nPage forward with `limit` (1–500, default 100) and the opaque `cursor`.\nThe response also carries a `task_id` cohort handle for the page so a\nfollow-up `add_person_enrichments` can operate on these leads without\nre-passing lead_ids. Requires the `lists:read` scope.\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/lead-lists · https://docs.cleanlist.ai/guides/managing-lists","operationId":"list_leads_in_list_api_v2_lead_lists__list_id__leads_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadsPageResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["Public Lead Lists"],"summary":"Add leads to a list","description":"Add leads to a list in one of two mutually-exclusive modes.\n\nMode A (`lead_ids`): add already-materialized leads by UUID.\nMode B (`task_id` [+ `entity_ids`]): materialize a prior search\ncohort into the list (search rows carry integer search-DB ids, not\nlead UUIDs, so they can't go through Mode A).\n\nThe body is an exclusive `oneOf` union (`AddByLeadIds | AddByCohort`):\nboth-set or neither-set bodies are rejected at the schema layer (422),\nso this handler only sees a well-formed single mode. Filter-based add\nis rejected — the MCP description tells the agent to translate filters\ninto a cohort task_id (or lead_ids) first.\n\nImport / \"Add to list\" is FREE — materializing leads costs no credits;\nonly enrichment is billed separately. Pass an optional `idempotency_key`\nso a retried add inserts exactly once. Requires the `lists:write` scope.\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/lead-lists · https://docs.cleanlist.ai/guides/managing-lists","operationId":"add_leads_to_list_api_v2_lead_lists__list_id__leads_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"title":"Body","oneOf":[{"$ref":"#/components/schemas/AddByLeadIds"},{"$ref":"#/components/schemas/AddByCohort"}]}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddLeadsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Public Lead Lists"],"summary":"Remove leads from a list","description":"Remove leads from a list (max 100 per call).\n\nRemoves the membership row only — the Lead itself stays in the\nworkspace (still findable via `search_people`, still in other lists\nit belonged to). Non-existent lead_ids are silently no-op so the\ncaller can dedupe loosely without 404s.\n\nRemoving leads is free — no credits are refunded or charged. Requires the\n`lists:write` scope.\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/lead-lists · https://docs.cleanlist.ai/guides/managing-lists","operationId":"remove_leads_from_list_api_v2_lead_lists__list_id__leads_delete","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveLeadsRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveLeadsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/lead-lists/{list_id}/csv-import":{"post":{"tags":["Public Lead Lists"],"summary":"Import leads from CSV","description":"Import a CSV into a lead list and optionally dispatch bulk enrichment.\n\nPipeline:\n    1. Authz: `lists:write` always; `enrich:write` additionally when\n       `dispatch_enrichment=True` (the workflow spends credits).\n    2. Feature flag gate — disabled-by-default kill switch returns 503.\n    3. Idempotency cache lookup (org-wide, 24h TTL).\n    4. Base64 decode + parse via `parse_csv_to_rawpeople`.\n    5. If dispatching: verify quote (if provided), open reservation,\n       redeem quote.\n    6. Bulk-insert RawPeople into the list. UUIDs are stamped\n       Python-side so we can hand them to the workflow.\n    7. If dispatching: kick off `start_bulk_enrichment` with the\n       parsed prospects + raw_person_ids. The workflow's per-Lead\n       `insert_lead_to_list` activity handles prospect_count\n       updates per the existing pattern; import-only path bumps\n       prospect_count here directly.\n    8. Cache the response under `idempotency_key` (if provided).\n\nCross-list dedup: this endpoint dedupes ONLY within the upload. A\nrow whose identity already exists as a Lead elsewhere in the\nworkspace still imports as a RawPerson here; the workflow's\n`persist_enrichment_result` then deduplicates against existing\nLeads by public_identifier / prospect_id (existing behavior).\nNet effect: no duplicate Lead, but the RawPerson stub persists\nuntil the workflow processes it.\n\nSend the file as base64 in `csv_content_base64` (≤150 KB / ~600 rows)\nwith a `column_mapping` from canonical field to CSV header. Requires the\n`lists:write` scope, plus `enrich:write` when `dispatch_enrichment=true`\n(the enrichment spend). Importing rows is free; when dispatching,\nenrichment is billed on top (partial 1cr, phone_only 10cr, full 11cr per\nlead). Pass an `idempotency_key` to make retries safe.\n\n**📖 Docs:** https://docs.cleanlist.ai/guides/bulk-enrichment","operationId":"csv_import_api_v2_lead_lists__list_id__csv_import_post","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"path","required":true,"schema":{"type":"string","title":"List Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CsvImportRequest"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CsvImportResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/enrichment/person":{"post":{"tags":["Public Enrichment"],"summary":"Enrich a single person","description":"Resolve email and/or phone for one contact through the provider\nwaterfall and write the result to a lead list.\n\nThis is an asynchronous workflow: it reserves credits up front, returns\na `workflow_id` immediately, then runs the provider cascade in the\nbackground — poll `poll_url` (GET /enrichment/status/{workflow_id}) until\n`status` is terminal. Cost is set by `enrichment_type`: 1 credit for\n`partial` (email only), 10 for `phone_only`, or 11 for `full` (email +\nphone); the legacy `include_phone` boolean is used when the field is\nunset. Pay-for-results means the final debit may be lower than\n`credits_reserved`. `quote_id` is optional here — single-call scale\ndoesn't require it, but a supplied quote is verified and redeemed so the\ncaller can pre-commit spend.\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/enrichment\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/enrichment-types\n**📖 Docs:** https://docs.cleanlist.ai/guides/bulk-enrichment","operationId":"enrich_person_api_v2_enrichment_person_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichPersonRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichPersonResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v2/enrichment/company":{"post":{"tags":["Public Enrichment"],"summary":"Enrich a company","description":"Look up firmographics for a single company and return the full record\nsynchronously.\n\nUnlike person enrichment, this path is synchronous — it performs a\nCompanyDB single-record lookup inline (no Temporal workflow, no\n`workflow_id`) and returns the enriched `CompanyRecord` in the response,\nbilling a flat 1 credit that settles before the response returns. Pass\nany of `domain` (most reliable), `company_id`, `company_name`, or\n`company_ticker`; a ticker is resolved to a domain via Finnhub (24h Redis\ncache) and falls back to a `company_name` fuzzy match when the symbol is\nunknown. `quote_id` is optional and, when present, verified and redeemed.\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/enrichment\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/enrichment-types\n**📖 Docs:** https://docs.cleanlist.ai/guides/bulk-enrichment","operationId":"enrich_company_api_v2_enrichment_company_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichCompanyRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichCompanyResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v2/enrichment/by-task":{"post":{"tags":["Public Enrichment"],"summary":"Enrich entities from a prior task","description":"Enrich a cohort of entities referenced by a `task_id` returned from a\nprior search, without re-listing every identifier.\n\nThe task's entity type decides the flow. Person (or lead) tasks dispatch\nan asynchronous workflow — the response carries a `workflow_id` with every\nentity `queued`, and results land in `lead_list_id` (a hidden per-user\nscratch list is auto-created when none is given); poll status for\nprogress. Company tasks run synchronously — each entity returns an\ninline `enriched`/`skipped`/`failed` result with no workflow. Both paths\nare billed per entity and REQUIRE a `quote_id` from POST /credits/estimate\n(person cohort cost follows the requested data points; company cohort is\n~1 credit per 10 companies), settling with pay-for-results semantics.\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/enrichment\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/enrichment-types\n**📖 Docs:** https://docs.cleanlist.ai/guides/bulk-enrichment","operationId":"enrich_by_task_api_v2_enrichment_by_task_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichByTaskRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichByTaskResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v2/enrichment/bulk":{"post":{"tags":["Public Enrichment"],"summary":"Enrich a whole lead list","description":"Run the provider waterfall over every lead in an existing list in one\ncall.\n\nThis is an asynchronous, fan-out workflow: it reserves the estimated cost,\nreturns a `workflow_id` immediately, then enriches all leads in parallel\nbackground child workflows — poll `poll_url` for aggregate progress and\nthe settled billing. `scope` sets the per-lead enrichment type\n(`partial` = 1 credit, `phone-only` = 10, `full` = 11). A `quote_id` from\nPOST /credits/estimate is REQUIRED and bound to this list + scope + lead\ncount; the actual charge is the recomputed cost capped at the quote's\n`max_credits`, and pay-for-results refunds the unused reservation for\nleads where nothing was found.\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/enrichment\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/enrichment-types\n**📖 Docs:** https://docs.cleanlist.ai/guides/bulk-enrichment","operationId":"enrich_list_api_v2_enrichment_bulk_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichListRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrichListResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v2/enrichment/status/{workflow_id}":{"get":{"tags":["Public Enrichment"],"summary":"Poll an enrichment workflow","description":"Poll the status, progress, and settled billing of an enrichment\nworkflow started by `enrich_person`, `enrich_list`, or `enrich_by_task`.\n\nThis read is free and idempotent — call it repeatedly until `status`\nreaches a terminal state (`completed` / `failed` / `cancelled`). It\naccepts EITHER a `workflow_id` (e.g. `enrich-...` / `bulk-enrich-...`) OR\nan MCPTask cohort `task_id` (`cl-task_...`), resolving the latter to the\nworkflow bound at dispatch so the agent can keep using the one handle it\nalready has. Terminal single-lead runs include the inline enriched `result`;\nbulk runs report aggregate counters instead. When a prepaid-via-MCP run\nends in failure, `refund_status` surfaces `pending_review` (credits are\nnot auto-refunded).\n\n**📖 Docs:** https://docs.cleanlist.ai/api-reference/enrichment","operationId":"enrichment_status_api_v2_enrichment_status__workflow_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"workflow_id","in":"path","required":true,"schema":{"type":"string","title":"Workflow Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkflowStatusResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/smart-agents/run":{"post":{"tags":["Public Smart Agents"],"summary":"Run a smart agent","description":"Launch an AI smart agent over the leads in a list and return a run id.\n\nPick an `agent_type` — `custom_ai` (your own `prompt`), `cold_intro_email`\n(personalized opener), or a preset research agent — and the run executes\nasynchronously as a smart column, one AI call per lead. Runs are metered\nper lead by agent type (e.g. `custom_ai` ≈ 1 credit/lead,\n`cold_intro_email` ≈ 3 credits/lead) and require a single-use `quote_id`\nfrom POST /credits/estimate; credits are debited as rows complete. Scope\nwith `lead_scope=\"subset\"` + `max_rows` for speed and cost control — a\n`lead_scope=\"all\"` run above the approval threshold (default 500 leads)\nis blocked with a 400 `approval_required` until you narrow it or route a\nhuman approval. The response returns immediately with `status=\"pending\"`;\npoll GET /smart-agents/{smart_agent_task_id} for progress and per-lead\nresults.\n\n**📖 Docs:** https://docs.cleanlist.ai/guides/using-ai-agents","operationId":"run_smart_agent_api_v2_smart_agents_run_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunSmartAgentRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunSmartAgentResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v2/smart-agents":{"get":{"tags":["Public Smart Agents"],"summary":"List smart agent runs","description":"List recent smart-agent runs with live progress counters.\n\nReturns each run's id, target list, agent type, status, and processed /\nfailed / total counts so you can track in-flight and completed runs. Pass\n`list_id` to scope to a single list (access-checked so a known id can't\nenumerate another workspace's history); omit it for a caller-scoped rollup\nacross every list you can see. This endpoint is free and does not consume\ncredits. To read the per-lead output of any run, call\nGET /smart-agents/{smart_agent_task_id}.\n\n**📖 Docs:** https://docs.cleanlist.ai/guides/using-ai-agents","operationId":"list_smart_agents_api_v2_smart_agents_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional lead-list UUID. When set, returns only runs for that list (access-checked). When omitted, returns a rollup of recent runs across every list the caller can see.","examples":["9c1f7e2a-4b6d-4f21-9a3e-2b7c8d1e5f04"],"title":"List Id"},"description":"Optional lead-list UUID. When set, returns only runs for that list (access-checked). When omitted, returns a rollup of recent runs across every list the caller can see."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"description":"Maximum number of runs to return, most recent first.","examples":[20],"default":20,"title":"Limit"},"description":"Maximum number of runs to return, most recent first."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSmartAgentsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/smart-agents/{smart_agent_task_id}":{"get":{"tags":["Public Smart Agents"],"summary":"Get smart agent results","description":"Poll a smart-agent run for status, progress, and per-lead output.\n\nReturns the run's lifecycle `status`, a `progress` percentage, and\nsucceeded / failed / total counters, along with a `results` array holding\neach lead's agent output (or its error). Poll this after\nPOST /smart-agents/run until `status` is `completed` (or `failed`) — the\n`results` array grows as leads finish, so a still-running run returns a\npartial set. Access is checked via the run's list, and unknown or\ninaccessible run ids return 404. This endpoint is free and does not\nconsume credits.\n\n**📖 Docs:** https://docs.cleanlist.ai/guides/using-ai-agents","operationId":"get_smart_agent_results_api_v2_smart_agents__smart_agent_task_id__get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"smart_agent_task_id","in":"path","required":true,"schema":{"type":"string","description":"Canonical id from /smart-agents/run's `smart_agent_task_id` field","examples":["run_7c2a91"],"title":"Smart Agent Task Id"},"description":"Canonical id from /smart-agents/run's `smart_agent_task_id` field"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SmartAgentResultsResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/v2/export/csv/signed-url":{"post":{"tags":["Public Export"],"summary":"Export a list to CSV (signed URL)","description":"Export a Lead List to CSV and return a time-limited signed download URL.\n\nStreams the list's leads through a temporary spooled file, uploads the\nresult to blob storage, and responds with a SAS-signed `download_url`\nthat expires 24 hours after generation (see `expires_at`). Pass `columns`\nto control which fields — and their order — are written, or omit it to use\nthe default column set; set `include_smart_agents=false` to skip any\nconfigured smart-agent result columns. This export is free and does not\nconsume credits.\n\n**📖 Docs:** https://docs.cleanlist.ai/guides/exporting-data","operationId":"export_csv_api_v2_export_csv_signed_url_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportCsvRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportCsvResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"HTTPBearer":[]}]}},"/api/v2/export/json":{"get":{"tags":["Public Export"],"summary":"Export a list as JSON","description":"Export a Lead List as JSON rows returned inline in the response.\n\nReturns a page of leads (default 100, max 500 per call) projected to the\nsame columns as the CSV export and wrapped in the standard response\nenvelope. Use the opaque `cursor` from each response to fetch the next\npage; a malformed cursor returns 400 rather than silently rewinding to\npage 0. This export is free and does not consume credits.\n\n**📖 Docs:** https://docs.cleanlist.ai/guides/exporting-data","operationId":"export_json_api_v2_export_json_get","security":[{"HTTPBearer":[]}],"parameters":[{"name":"list_id","in":"query","required":true,"schema":{"type":"string","title":"List Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"default":100,"title":"Limit"}},{"name":"cursor","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor"}},{"name":"columns","in":"query","required":false,"schema":{"anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}],"title":"Columns"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExportJsonResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AddByCohort":{"properties":{"task_id":{"type":"string","title":"Task Id","description":"A cohort handle from a prior search_people / list_leads_in_list response. Materializes that cohort's entities into this list.","examples":["task_9f2c1a"]},"entity_ids":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":2000},{"type":"null"}],"title":"Entity Ids","description":"Optional subset of the cohort to save (must be ⊆ the cohort). Omit to save the full cohort. search_people cohorts carry integer search-DB ids (as strings).","examples":[["4482910","4482911"]]},"idempotency_key":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Idempotency Key","description":"Optional single-use key. Re-sending the same save with the same key bills + inserts exactly once (safe to retry). Omit to mint a server-side key per request.","examples":["save-2026-06-28-d4e5f6"]}},"additionalProperties":false,"type":"object","required":["task_id"],"title":"AddByCohort","description":"Mode B: materialize a prior search cohort into the list.\n\n`search_*` rows carry integer search-DB ids (not lead UUIDs), so\nMode A's `UUID(lead_id)` rejects them — Mode B resolves the cohort by\n`task_id` and creates the lead rows from the task's cached\n`entity_details`.","example":{"entity_ids":["4482910","4482911"],"idempotency_key":"save-2026-06-28-d4e5f6","task_id":"task_9f2c1a"}},"AddByLeadIds":{"properties":{"lead_ids":{"items":{"type":"string"},"type":"array","maxItems":1000,"minItems":1,"title":"Lead Ids","description":"Lead UUIDs to add (from list_leads_in_list / an owned list).","examples":[["lead_5f3b8c","lead_9d2e71"]]},"idempotency_key":{"anyOf":[{"type":"string","maxLength":128},{"type":"null"}],"title":"Idempotency Key","description":"Optional single-use key. Re-sending the same add with the same key bills + inserts exactly once (safe to retry). Omit to mint a server-side key per request.","examples":["add-2026-06-28-a1b2c3"]}},"additionalProperties":false,"type":"object","required":["lead_ids"],"title":"AddByLeadIds","description":"Mode A: add already-materialized leads by UUID.\n\nThese come from `list_leads_in_list` (real lead UUIDs) or a list\nalready owned by the workspace.","example":{"idempotency_key":"add-2026-06-28-a1b2c3","lead_ids":["lead_5f3b8c","lead_9d2e71"]}},"AddLeadsResponse":{"properties":{"list_id":{"type":"string","title":"List Id","description":"Identifier of the list leads were added to.","examples":["list_7c2a91"]},"added":{"type":"integer","title":"Added","description":"Number of leads newly inserted into the list (billed rows).","examples":[25]},"skipped_duplicates":{"type":"integer","title":"Skipped Duplicates","description":"Leads already in the list that were skipped (not re-billed).","examples":[3]},"created":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Created","description":"New lead rows materialized by a Mode-B cohort save (vs reusing an existing lead for the same person). Null for Mode A — telemetry only.","examples":[20]},"credits_charged":{"type":"number","title":"Credits Charged","description":"Import credits charged. Import is free, so this is 0; only enrichment is billed (separately).","default":0,"examples":[0]}},"additionalProperties":false,"type":"object","required":["list_id","added","skipped_duplicates"],"title":"AddLeadsResponse","example":{"added":25,"created":20,"credits_charged":12.5,"list_id":"list_7c2a91","skipped_duplicates":3}},"ApiKeyInfo":{"properties":{"key_id":{"type":"string","title":"Key Id","description":"Opaque id of the API key row (safe to display and reference).","examples":["ak_5f8c2b91"]},"key_prefix":{"type":"string","title":"Key Prefix","description":"Non-secret leading segment of the key, used for at-a-glance identification. Cleanlist keys are `clapi_`.","examples":["clapi_"]},"key_last4":{"type":"string","title":"Key Last4","description":"Last 4 characters of the key for disambiguation. The full secret is never returned by this endpoint.","examples":["a4f9"]},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Optional human label set when the key was created.","examples":["Production server key"]},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"Permission scopes carried by this key. Scopes are granted per-USER today, so every key the caller owns shares the caller's scopes.","examples":[["credits:read","search:read","enrichment:write","admin:api_keys"]]},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At","description":"Timestamp the key was created (stringified), or null if unknown.","examples":["2026-05-12 09:31:44"]},"last_used_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Used At","description":"Timestamp the key was last used to authenticate (stringified), or null if it has never been used.","examples":["2026-06-30 18:02:10"]},"is_active":{"type":"boolean","title":"Is Active","description":"Whether the key is currently active. Revoked keys report false.","examples":[true]}},"additionalProperties":false,"type":"object","required":["key_id","key_prefix","key_last4","scopes","is_active"],"title":"ApiKeyInfo","example":{"created_at":"2026-05-12 09:31:44","is_active":true,"key_id":"ak_5f8c2b91","key_last4":"a4f9","key_prefix":"clapi_","last_used_at":"2026-06-30 18:02:10","name":"Production server key","scopes":["credits:read","search:read","enrichment:write","admin:api_keys"]}},"ApiKeysResponse":{"properties":{"keys":{"items":{"$ref":"#/components/schemas/ApiKeyInfo"},"type":"array","title":"Keys","description":"API keys owned by the caller and scoped to the calling token's org (prefix + last 4 only — never full secrets). Empty when none exist."}},"additionalProperties":false,"type":"object","required":["keys"],"title":"ApiKeysResponse","example":{"keys":[{"created_at":"2026-05-12 09:31:44","is_active":true,"key_id":"ak_5f8c2b91","key_last4":"a4f9","key_prefix":"clapi_","last_used_at":"2026-06-30 18:02:10","name":"Production server key","scopes":["credits:read","search:read","enrichment:write","admin:api_keys"]}]}},"CompanyRecord":{"properties":{"company_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Id","description":"Stable Cleanlist/CompanyDB identifier for the company.","examples":["company_3f9a"]},"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"Company display name.","examples":["Acme"]},"domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain","description":"Primary web domain.","examples":["acme.io"]},"industry":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Industry","description":"Primary industry classification.","examples":["Computer Software"]},"industries":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Industries","description":"All industry tags associated with the company.","examples":[["Computer Software","SaaS"]]},"employee_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Employee Count","description":"Estimated headcount.","examples":[240]},"employee_count_range":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Employee Count Range","description":"Headcount bucket when an exact count is unavailable.","examples":["201-500"]},"revenue_range":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Revenue Range","description":"Estimated annual revenue band.","examples":["$25M-$50M"]},"hq_location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hq Location","description":"Headquarters location (city, region, country).","examples":["San Francisco, CA, US"]},"funding_stage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Funding Stage","description":"Most recent funding round type.","examples":["Series B"]},"total_funding_usd":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total Funding Usd","description":"Total capital raised to date, in USD.","examples":[48000000]},"tech_stack":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Tech Stack","description":"Detected technologies in the company's stack.","examples":[["React","Python","AWS"]]},"linkedin_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linkedin Url","description":"Company LinkedIn page URL.","examples":["https://www.linkedin.com/company/acme"]}},"additionalProperties":true,"type":"object","title":"CompanyRecord","description":"Synchronous company enrichment result. Shape follows what\nCompanyDB returns, but with stable field names independent of\nCrustdata's response shape drift.","example":{"company_id":"company_3f9a","domain":"acme.io","employee_count":240,"employee_count_range":"201-500","funding_stage":"Series B","hq_location":"San Francisco, CA, US","industries":["Computer Software","SaaS"],"industry":"Computer Software","linkedin_url":"https://www.linkedin.com/company/acme","name":"Acme","revenue_range":"$25M-$50M","tech_stack":["React","Python","AWS"],"total_funding_usd":48000000}},"CreateListRequest":{"properties":{"name":{"type":"string","maxLength":200,"minLength":1,"title":"Name","description":"Display name for the list (1–200 chars). Create is idempotent on this name — if you already own an exact same-name list (case-insensitive) it is reused and returned with `reused: true` instead of a duplicate being inserted.","examples":["Q3 Enterprise SaaS Leads"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Optional free-text note describing the list's ICP or purpose.","examples":["VPs of Sales at Series B+ SaaS companies, North America."]},"folder_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Folder Id","description":"Optional folder to file the list under. Must be a folder_id you own (from GET /folders); an unknown or foreign folder returns 404.","examples":["folder_3d9b12"]}},"additionalProperties":false,"type":"object","required":["name"],"title":"CreateListRequest","example":{"description":"VPs of Sales at Series B+ SaaS companies, North America.","folder_id":"folder_3d9b12","name":"Q3 Enterprise SaaS Leads"}},"CreditsBalanceResponse":{"properties":{"task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Id"},"timestamp_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timestamp Ms"},"agent_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Instructions"},"usage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Usage"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"organization_id":{"type":"string","title":"Organization Id","description":"Organization whose credit wallet this balance belongs to.","examples":["org_2abcRevenueLabs"]},"credits":{"type":"integer","title":"Credits","description":"Current spendable credit balance for the organization (`organizations.credits_count`). Whole credits; never negative.","examples":[4820]}},"additionalProperties":false,"type":"object","required":["organization_id","credits"],"title":"CreditsBalanceResponse","description":"Minimal balance shape. The MCP `check_credits` description tells\nthe agent to direct the user to billing for plan/renewal info, so\nwe deliberately don't ship plan_slug / renew_at here even though\ncallers might want them — keep the contract tight.","example":{"agent_instructions":"You have 4,820 credits remaining. Enrichment costs 1-11 credits per lead depending on scope.","credits":4820,"organization_id":"org_2abcRevenueLabs","task_id":"task_9f2c1a","timestamp_ms":1751404800000}},"CsvImportRequest":{"properties":{"csv_content_base64":{"type":"string","title":"Csv Content Base64","description":"Full CSV file body, base64-encoded. UTF-8 (with or without BOM). v1 hard-caps the raw body at 150 KB / ~600 rows — past that the base64 payload starts hitting tool-call output limits in the agent before any server cap fires. Above the cap the endpoint returns 413; chunk the file upstream.","examples":["Zmlyc3RfbmFtZSxsYXN0X25hbWUsZW1haWwsY29tcGFueQpKYW5lLERvZSxqYW5lLmRvZUBhY21lLmlvLEFjbWUK"]},"column_mapping":{"additionalProperties":{"type":"string"},"propertyNames":{"enum":["first_name","last_name","full_name","email","phone","mobile_phone","work_phone","linkedin_url","company_name","company_domain","job_title","city","state","country"]},"type":"object","minProperties":1,"title":"Column Mapping","description":"{ canonical_field: csv_header_name }. The MCP agent reads the CSV header line and supplies the mapping; the server never guesses. CSV columns not mapped are dropped. Mapping a field that isn't in the CSV header returns 400 missing_column with the bad header name in `details`.","examples":[{"company_name":"Company","email":"Email","first_name":"First Name","last_name":"Last Name"}]},"dispatch_enrichment":{"type":"boolean","title":"Dispatch Enrichment","description":"When True (default), the import dispatches a BulkEnrichmentWorkflow immediately after the RawPeople rows land. When False, rows land as RawPerson(status=pending) and the caller dispatches enrichment separately via enrich_list.","default":true,"examples":[true]},"enrichment_type":{"anyOf":[{"type":"string","enum":["partial","phone_only","full"]},{"type":"null"}],"title":"Enrichment Type","description":"Required when dispatch_enrichment=True. Same enum and pricing as enrich_person / enrich_list: partial (email, 1cr), phone_only (phone, 10cr), full (email + phone, 11cr).","examples":["full"]},"quote_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quote Id","description":"Optional signed quote from /credits/estimate. The MCP tool should quote first for large dispatches; small ones can skip and let the per-call deduct run. Verified against the tool=`bulk_import_csv`, row_count=rows_imported, filter={list_id}, extra={enrichment_type} canonical.","examples":["quote_2f8b10"]},"idempotency_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Idempotency Key","description":"Opaque caller-supplied key. If a previous request with the same key+org succeeded within 24h, the cached response is replayed with idempotency_replayed=true and the CSV is NOT re-imported (credits are NOT re-charged). Scope is org-wide: the same key targeting different lists collides — first wins. Use a fresh UUID per logical import.","examples":["csv-import-2026-06-28-7a3c"]}},"additionalProperties":false,"type":"object","required":["csv_content_base64","column_mapping"],"title":"CsvImportRequest","example":{"column_mapping":{"company_name":"Company","email":"Email","first_name":"First Name","last_name":"Last Name"},"csv_content_base64":"Zmlyc3RfbmFtZSxsYXN0X25hbWUsZW1haWwsY29tcGFueQpKYW5lLERvZSxqYW5lLmRvZUBhY21lLmlvLEFjbWUK","dispatch_enrichment":true,"enrichment_type":"full","idempotency_key":"csv-import-2026-06-28-7a3c","quote_id":"quote_2f8b10"}},"CsvImportResponse":{"properties":{"task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Id"},"timestamp_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timestamp Ms"},"agent_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Instructions"},"usage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Usage"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"list_id":{"type":"string","title":"List Id","description":"Identifier of the list the CSV was imported into.","examples":["list_7c2a91"]},"rows_parsed":{"type":"integer","title":"Rows Parsed","description":"Total data rows read from the CSV (excludes the header).","examples":[200]},"rows_imported":{"type":"integer","title":"Rows Imported","description":"Rows that landed as importable contacts.","examples":[188]},"rows_skipped":{"type":"integer","title":"Rows Skipped","description":"Rows dropped during parsing (see skip_reasons).","examples":[12]},"skip_reasons":{"additionalProperties":{"type":"integer"},"type":"object","title":"Skip Reasons","description":"Skip counts by stable reason key (missing_required, duplicate_email, duplicate_linkedin, row_cap_exceeded).","examples":[{"duplicate_email":9,"missing_required":3}]},"sample_skip_rows":{"items":{"type":"integer"},"type":"array","title":"Sample Skip Rows","description":"Up to 10 1-indexed row numbers (header is row 1) that were skipped, for spot-checking the file.","examples":[[14,27,33,58,61]]},"workflow_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Id","description":"Bulk-enrichment workflow id when dispatch_enrichment=true; null for import-only.","examples":["bulk-enrich-a1b2c3d4"]},"enrichment_status_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enrichment Status Url","description":"Poll URL for the dispatched enrichment workflow; null for import-only.","examples":["/api/v2/enrichment/status/bulk-enrich-a1b2c3d4"]},"idempotency_replayed":{"type":"boolean","title":"Idempotency Replayed","description":"True when served from the idempotency cache — the CSV was NOT re-imported and NO credits were re-charged.","default":false,"examples":[false]}},"additionalProperties":false,"type":"object","required":["list_id","rows_parsed","rows_imported","rows_skipped"],"title":"CsvImportResponse","example":{"enrichment_status_url":"/api/v2/enrichment/status/bulk-enrich-a1b2c3d4","idempotency_replayed":false,"list_id":"list_7c2a91","rows_imported":188,"rows_parsed":200,"rows_skipped":12,"sample_skip_rows":[14,27,33,58,61],"skip_reasons":{"duplicate_email":9,"missing_required":3},"task_id":"task_9f2c1a","timestamp_ms":1751404800000,"workflow_id":"bulk-enrich-a1b2c3d4"}},"DeleteListResponse":{"properties":{"task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Id"},"timestamp_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timestamp Ms"},"agent_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Instructions"},"usage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Usage"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"list_id":{"type":"string","title":"List Id","description":"Identifier of the deleted list.","examples":["list_7c2a91"]},"deleted":{"type":"boolean","title":"Deleted","description":"Always true on a successful delete.","examples":[true]}},"additionalProperties":false,"type":"object","required":["list_id","deleted"],"title":"DeleteListResponse","example":{"deleted":true,"list_id":"list_7c2a91","message":"List deleted.","task_id":"task_9f2c1a","timestamp_ms":1751404800000}},"EnrichByTaskEntityResult":{"properties":{"entity_id":{"type":"string","title":"Entity Id","description":"The cohort entity id this result belongs to.","examples":["company_3f9a"]},"status":{"type":"string","enum":["queued","enriched","skipped","failed"],"title":"Status","description":"Per-entity outcome. Person tasks return 'queued' (results land asynchronously). Company tasks return 'enriched' (record attached), 'skipped' (no cached identifier), or 'failed' (lookup error / not found).","examples":["enriched"]},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Reason for a skipped/failed entity; null on success.","examples":[null]},"company":{"anyOf":[{"$ref":"#/components/schemas/CompanyRecord"},{"type":"null"}],"description":"Enriched company record for company-task entities with status 'enriched'. Null for person tasks and non-enriched entities."}},"additionalProperties":false,"type":"object","required":["entity_id","status"],"title":"EnrichByTaskEntityResult","example":{"company":{"company_id":"company_3f9a","domain":"acme.io","employee_count":240,"funding_stage":"Series B","industry":"Computer Software","linkedin_url":"https://www.linkedin.com/company/acme","name":"Acme"},"entity_id":"company_3f9a","status":"enriched"}},"EnrichByTaskRequest":{"properties":{"task_id":{"type":"string","title":"Task Id","description":"Task handle from a prior search_people / search_companies / list_leads_in_list call. Org-scoped and time-limited; re-run the search to refresh an expired handle.","examples":["cl-task_8b21f4"]},"data_points":{"items":{"type":"string"},"type":"array","minItems":1,"title":"Data Points","description":"Fields to enrich. Person tasks accept Email, Phone, LinkedIn; company tasks accept Industry, Employees, Revenue, Funding, TechStack, Domain. Must match the task's entity type. For person cohorts, Email+Phone → 'full', Phone → 'phone_only', else 'partial'.","examples":[["Email","Phone"]]},"entity_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Entity Ids","description":"Optional strict subset of the task's cohort to enrich. Every id must belong to the original cohort. Omit to enrich the whole cohort.","examples":[["prospect_5c8e","prospect_9d21"]]},"lead_list_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lead List Id","description":"Destination list for person-task results. When omitted a hidden per-user 'MCP Results' scratch list is auto-created and reused. Ignored for company tasks (results return inline).","examples":["ll_7f3a2b9c"]},"quote_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quote Id","description":"Required in practice — cohort enrichment bills per entity, so a quote_id from POST /credits/estimate (same task_id + data_points) must be approved. Omitting it returns a typed quote_required error.","examples":["quote_c0h0rt"]}},"additionalProperties":false,"type":"object","required":["task_id","data_points"],"title":"EnrichByTaskRequest","example":{"data_points":["Email","Phone"],"entity_ids":["prospect_5c8e","prospect_9d21"],"lead_list_id":"ll_7f3a2b9c","quote_id":"quote_c0h0rt","task_id":"cl-task_8b21f4"}},"EnrichByTaskResponse":{"properties":{"task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Id","description":"The originating cohort task_id, echoed back for chaining.","examples":["cl-task_8b21f4"]},"timestamp_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timestamp Ms"},"agent_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Instructions"},"usage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Usage"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"entity_kind":{"type":"string","enum":["person","company"],"title":"Entity Kind","description":"Resolved entity type of the task. 'person' → async workflow; 'company' → synchronous inline results.","examples":["person"]},"workflow_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Workflow Id","description":"Workflow handle for person tasks (poll it for progress). Null for company tasks, which complete inline.","examples":["wf_enrich_7c2a"]},"status":{"type":"string","enum":["pending","completed"],"title":"Status","description":"'pending' for person tasks (workflow running); 'completed' for company tasks (results already attached).","examples":["pending"]},"total_entities":{"type":"integer","title":"Total Entities","description":"Number of cohort entities dispatched for enrichment.","examples":[2]},"credits_reserved":{"type":"integer","title":"Credits Reserved","description":"Person tasks: upfront reservation cap; the workflow settles with actual usage and refunds the delta. Company tasks: the final billed amount (settled inline).","examples":[22]},"results":{"items":{"$ref":"#/components/schemas/EnrichByTaskEntityResult"},"type":"array","title":"Results","description":"Per-entity outcomes. Person tasks: all 'queued'. Company tasks: 'enriched'/'skipped'/'failed' with records attached."}},"additionalProperties":false,"type":"object","required":["entity_kind","status","total_entities","credits_reserved","results"],"title":"EnrichByTaskResponse","example":{"agent_instructions":"Person cohort queued. Poll status with the workflow_id (or the task_id) until completed; results land in the destination list.","credits_reserved":22,"entity_kind":"person","results":[{"entity_id":"prospect_5c8e","status":"queued"},{"entity_id":"prospect_9d21","status":"queued"}],"status":"pending","task_id":"cl-task_8b21f4","timestamp_ms":1751404800000,"total_entities":2,"workflow_id":"wf_enrich_7c2a"}},"EnrichCompanyRequest":{"properties":{"domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain","description":"Company web domain. The most reliable identifier — preferred over name/ticker for an exact CompanyDB match.","examples":["acme.io"]},"company_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Name","description":"Company name. Used for a fuzzy match when no domain is given, and as the fallback when an unknown ticker can't be resolved.","examples":["Acme"]},"company_ticker":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Ticker","description":"Public stock ticker. Resolved to a domain via Finnhub (24h cache); unresolved symbols fall back to a company_name fuzzy match.","examples":["ACME"]},"company_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Id","description":"Cleanlist/CompanyDB company identifier from a prior search.","examples":["company_3f9a"]},"quote_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quote Id","description":"Optional pre-approved quote from POST /credits/estimate. Verified and redeemed when present; the upfront reservation caps cost regardless.","examples":["quote_c0mp4ny"]}},"additionalProperties":false,"type":"object","title":"EnrichCompanyRequest","example":{"company_name":"Acme","domain":"acme.io","quote_id":"quote_c0mp4ny"}},"EnrichCompanyResponse":{"properties":{"task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Id"},"timestamp_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timestamp Ms"},"agent_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Instructions"},"usage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Usage"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"company":{"$ref":"#/components/schemas/CompanyRecord","description":"The enriched company record returned synchronously."},"credits_charged":{"type":"integer","title":"Credits Charged","description":"Final credits billed for this lookup (1). Settled inline before the response returns — no reserved-vs-charged ambiguity.","examples":[1]}},"additionalProperties":false,"type":"object","required":["company","credits_charged"],"title":"EnrichCompanyResponse","example":{"company":{"company_id":"company_3f9a","domain":"acme.io","employee_count":240,"employee_count_range":"201-500","funding_stage":"Series B","hq_location":"San Francisco, CA, US","industries":["Computer Software","SaaS"],"industry":"Computer Software","linkedin_url":"https://www.linkedin.com/company/acme","name":"Acme","revenue_range":"$25M-$50M","tech_stack":["React","Python","AWS"],"total_funding_usd":48000000},"credits_charged":1,"task_id":"task_9f2c1a","timestamp_ms":1751404800000}},"EnrichListRequest":{"properties":{"list_id":{"type":"string","title":"List Id","description":"The lead list to enrich in bulk. Every lead in the list runs through the provider waterfall. Must be non-empty and accessible to your API key.","examples":["ll_7f3a2b9c"]},"scope":{"type":"string","enum":["partial","full","phone-only"],"title":"Scope","description":"Enrichment scope per lead. 'partial' = email + LinkedIn + title + company (1 credit); 'phone-only' = phone only (10 credits); 'full' = email AND phone (11 credits).","default":"partial","examples":["full"]},"quote_id":{"type":"string","title":"Quote Id","description":"Required. Obtain via POST /credits/estimate first (bound to this list_id + scope + lead count). Bulk spend must be pre-approved; cost is capped at the quote's max_credits.","examples":["quote_bulk99"]}},"additionalProperties":false,"type":"object","required":["list_id","quote_id"],"title":"EnrichListRequest","example":{"list_id":"ll_7f3a2b9c","quote_id":"quote_bulk99","scope":"full"}},"EnrichListResponse":{"properties":{"task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Id"},"timestamp_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timestamp Ms"},"agent_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Instructions"},"usage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Usage"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"workflow_id":{"type":"string","title":"Workflow Id","description":"Handle for the bulk enrichment workflow. Poll it via poll_url.","examples":["wf_enrich_7c2a"]},"status":{"type":"string","const":"pending","title":"Status","description":"Always 'pending' on dispatch — the bulk workflow runs async.","examples":["pending"]},"total_leads":{"type":"integer","title":"Total Leads","description":"Number of leads in the list submitted for enrichment.","examples":[120]},"estimated_cost":{"type":"integer","title":"Estimated Cost","description":"Recomputed cost for the run, capped at the quote's max_credits.","examples":[1320]},"credits_reserved":{"type":"integer","title":"Credits Reserved","description":"Upfront reservation cap. Pay-for-results: leads with no email/phone found aren't charged, so settle refunds the unused portion. Poll status for the final debit.","examples":[1320]},"poll_url":{"type":"string","title":"Poll Url","description":"Fully-qualified URL to poll for aggregate progress and billing.","examples":["https://api.cleanlist.ai/api/v1/public/enrichment/status/wf_enrich_7c2a"]}},"additionalProperties":false,"type":"object","required":["workflow_id","status","total_leads","estimated_cost","credits_reserved","poll_url"],"title":"EnrichListResponse","example":{"agent_instructions":"Bulk enrichment started for 120 leads. Poll poll_url until status is 'completed'; final charge may be lower than credits_reserved (pay-for-results).","credits_reserved":1320,"estimated_cost":1320,"poll_url":"https://api.cleanlist.ai/api/v1/public/enrichment/status/wf_enrich_7c2a","status":"pending","task_id":"task_9f2c1a","timestamp_ms":1751404800000,"total_leads":120,"workflow_id":"wf_enrich_7c2a"}},"EnrichPersonRequest":{"properties":{"lead_list_id":{"type":"string","title":"Lead List Id","description":"ID of the lead list the enriched contact is written to when the workflow completes. Must be a list your API key can access.","examples":["ll_7f3a2b9c"]},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Known work email. A strong identifier — providing it (or linkedin_url / person_id) satisfies the identifier requirement on its own.","examples":["jane@acme.io"]},"linkedin_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linkedin Url","description":"LinkedIn profile URL. Many forms are accepted (regional subdomains, /pub/ paths, a bare handle) and normalized to the canonical https://linkedin.com/in/<slug> shape before dispatch.","examples":["https://www.linkedin.com/in/jane-doe-9a1b"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Known phone number in E.164 format. Optional hint; the waterfall still runs to discover/verify contact data per enrichment_type.","examples":["+14155550142"]},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name","description":"Given name. Combined with last_name and a company_name or domain it forms a weak-identifier match when no email/linkedin/person_id is available.","examples":["Jane"]},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name","description":"Family name. Pairs with first_name for name+company matching.","examples":["Doe"]},"company_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company Name","description":"Employer name. Feeds the provider waterfall's name+company email resolution and completes the weak-identifier path.","examples":["Acme"]},"domain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Domain","description":"Employer web domain. More precise than company_name for the name+company match; also completes the weak-identifier path.","examples":["acme.io"]},"person_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Person Id","description":"Cleanlist prospect/person identifier from a prior search. A strong identifier that satisfies the requirement on its own.","examples":["prospect_5c8e"]},"enrichment_type":{"anyOf":[{"type":"string","enum":["partial","phone_only","full"]},{"type":"null"}],"title":"Enrichment Type","description":"Enrichment scope and cost. 'partial' = email + LinkedIn + title + company (1 credit); 'phone_only' = phone only (10 credits); 'full' = email AND phone (11 credits). Preferred over the legacy include_phone boolean; when null the include_phone mapping applies.","examples":["full"]},"include_phone":{"type":"boolean","title":"Include Phone","description":"Legacy scope toggle, kept for back-compat. false → 'partial', true → 'phone_only'. Ignored when enrichment_type is set.","default":false,"examples":[false]},"quote_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Quote Id","description":"Optional pre-approved quote from POST /credits/estimate. When present it is verified and redeemed so spend is confirmed up front; when absent the upfront credit reservation still caps cost.","examples":["quote_a1b2c3"]}},"additionalProperties":false,"type":"object","required":["lead_list_id"],"title":"EnrichPersonRequest","example":{"company_name":"Acme","domain":"acme.io","email":"jane@acme.io","enrichment_type":"full","first_name":"Jane","include_phone":true,"last_name":"Doe","lead_list_id":"ll_7f3a2b9c","linkedin_url":"https://www.linkedin.com/in/jane-doe-9a1b","quote_id":"quote_a1b2c3"}},"EnrichPersonResponse":{"properties":{"task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Id"},"timestamp_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timestamp Ms"},"agent_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Instructions"},"usage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Usage"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"workflow_id":{"type":"string","title":"Workflow Id","description":"Handle for the async enrichment workflow. Poll it via poll_url.","examples":["wf_enrich_7c2a"]},"status":{"type":"string","const":"pending","title":"Status","description":"Always 'pending' on dispatch — the workflow runs asynchronously.","examples":["pending"]},"credits_reserved":{"type":"integer","title":"Credits Reserved","description":"Upfront credit reservation cap (1/10/11 for partial/phone_only/full). Pay-for-results: the workflow refunds the unused portion at settle, so the final debit may be lower. Poll status for the actual charge.","examples":[11]},"lead_list_id":{"type":"string","title":"Lead List Id","description":"List the enriched contact will be written to on completion.","examples":["ll_7f3a2b9c"]},"poll_url":{"type":"string","title":"Poll Url","description":"Fully-qualified URL to poll for workflow status and results.","examples":["https://api.cleanlist.ai/api/v1/public/enrichment/status/wf_enrich_7c2a"]}},"additionalProperties":false,"type":"object","required":["workflow_id","status","credits_reserved","lead_list_id","poll_url"],"title":"EnrichPersonResponse","example":{"agent_instructions":"Enrichment started. Poll poll_url until status is 'completed'; the enriched contact lands in lead_list_id.","credits_reserved":11,"lead_list_id":"ll_7f3a2b9c","poll_url":"https://api.cleanlist.ai/api/v1/public/enrichment/status/wf_enrich_7c2a","status":"pending","task_id":"task_9f2c1a","timestamp_ms":1751404800000,"workflow_id":"wf_enrich_7c2a"}},"EstimateCostRequest":{"properties":{"tool":{"type":"string","title":"Tool","description":"Tool to price. Must be a key in the server's cost table (e.g. `enrich_list`, `enrich_person`, `run_smart_agent`, `sync_to_crm`, `add_person_enrichments`). Unknown tools return a 400.","examples":["enrich_list"]},"list_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"List Id","description":"Target lead list. When present, `row_count` is derived AUTHORITATIVELY from the list and any caller-supplied `row_count` is ignored — this prevents quoting a small count then enriching a large list.","examples":["list_7a1b2c3d"]},"filters":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Filters","description":"Free-form filter object bound into the signed `filter_hash` for filter-backed tools. Ignored when `list_id` (or a cohort `task_id`) is supplied, since those define the canonical filter shape.","examples":[{"headcount":"51-200","title":"VP of Sales"}]},"row_count":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Row Count","description":"Explicit row count for tools that aren't list- or cohort-backed. Trusted ONLY when neither `list_id` nor a cohort `task_id` is present; otherwise the count is derived server-side.","examples":[250]},"scope":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Scope","description":"Enrichment scope for `enrich_list`: `partial` (email, 1cr), `full` (email + phone, 11cr), or `phone-only` (10cr). Binds into the quote's canonical extras.","examples":["full"]},"agent_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Type","description":"Smart-agent type for `run_smart_agent` pricing (e.g. `custom_ai`, `cold_intro_email`, `company_intel`). Binds into the quote.","examples":["cold_intro_email"]},"include_phone":{"type":"boolean","title":"Include Phone","description":"Legacy boolean that requests phone enrichment when `enrichment_type`/`scope` are not set. Kept for back-compat; prefer `scope`/`enrichment_type`.","default":false,"examples":[true]},"provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider","description":"Destination provider for sync tools (`sync_to_crm` / `sync_to_sequencer`), e.g. `hubspot`, `salesforce`, `smartlead`. Binds into the quote's canonical extras.","examples":["hubspot"]},"sub_action":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Sub Action","description":"Composite sub-action for `sync_to_crm` (e.g. `contacts+companies`). Required so the quote envelope binds `sub_action` correctly — without it a composite HubSpot sync HMAC-mismatches at execution.","examples":["contacts+companies"]},"enrichment_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enrichment Type","description":"Explicit per-lead price tier for tools that quote against an `enrichment_type` enum rather than `enrich_list`'s `scope` (e.g. `bulk_import_csv`: `partial` / `full` / `phone_only`). Leave null for tools that don't use it.","examples":["full"]},"task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Id","description":"Prior search cohort handle for cohort-enrichment tools (`add_person_enrichments` / `add_company_enrichments`). The priced row count is derived from this task server-side — never trusted from the caller — and the task_id binds into the quote's `filter_hash`.","examples":["task_c0h0rt42"]},"data_points":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Data Points","description":"Cohort enrichment data points that set the price tier (e.g. `Email`, `Phone`; `LinkedIn` is free). Sorted and bound into the quote so HMAC matches regardless of caller ordering.","examples":[["Email","Phone"]]},"entity_ids":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Entity Ids","description":"Optional subset of the cohort's entities to price. Must be a strict subset of the task's cohort; when present, only these are priced, otherwise the full cohort is.","examples":[["ent_a1","ent_b2","ent_c3"]]},"extra":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Extra","description":"Escape hatch for additional canonical-extra keys the tool binds. Keys outside the tool's declared canonical set are rejected at quote-issue time (400) rather than silently dropped.","examples":[{"priority":"high"}]}},"additionalProperties":false,"type":"object","required":["tool"],"title":"EstimateCostRequest","example":{"include_phone":true,"list_id":"list_7a1b2c3d","scope":"full","tool":"enrich_list"}},"EstimateCostResponse":{"properties":{"quote_id":{"type":"string","title":"Quote Id","description":"HMAC-signed quote handle (`qte_<kid>_<issued_at>_<sig>`) that the paired paid tool must present at execution time. Single-use and bound to the canonical request (tool, list/filters, scope, row_count, org).","examples":["qte_v1_1751404800_9a3f2c1b8e7d6a5f4c3b2a1908f7e6d5c4b3a2910f8e7d6c5b4a3928170"]},"expires_at":{"type":"integer","title":"Expires At","description":"Unix epoch SECONDS at which the quote expires (issue time + `CLEANLIST_QUOTE_TTL_SECONDS`, 5 min default). Re-estimate after this to obtain a fresh quote.","examples":[1751405100]},"estimated_cost":{"type":"integer","title":"Estimated Cost","description":"Credits this operation will cost — the authoritative signed spend cap. Execution rejects (`spend_cap_exceeded`) if the recomputed cost would exceed it.","examples":[275]},"tool":{"type":"string","title":"Tool","description":"Echo of the priced tool.","examples":["enrich_list"]},"row_count":{"type":"integer","title":"Row Count","description":"Number of rows/entities the estimate priced — derived from the list or cohort when applicable, not the caller's raw input.","examples":[25]},"filter_hash":{"type":"string","title":"Filter Hash","description":"Stable digest of the canonical filter/list/cohort shape bound into the quote signature; `none` when there is no filter object.","examples":["3f8a1c9d2e4b6a70"]},"available_credits":{"type":"integer","title":"Available Credits","description":"The organization's current credit balance at estimate time.","examples":[4820]},"sufficient":{"type":"boolean","title":"Sufficient","description":"True when `available_credits >= estimated_cost`. False signals the org must top up at billing before the operation can run.","examples":[true]}},"additionalProperties":false,"type":"object","required":["quote_id","expires_at","estimated_cost","tool","row_count","filter_hash","available_credits","sufficient"],"title":"EstimateCostResponse","example":{"available_credits":4820,"estimated_cost":275,"expires_at":1751405100,"filter_hash":"3f8a1c9d2e4b6a70","quote_id":"qte_v1_1751404800_9a3f2c1b8e7d6a5f4c3b2a1908f7e6d5c4b3a2910f8e7d6c5b4a3928170","row_count":25,"sufficient":true,"tool":"enrich_list"}},"ExportCsvRequest":{"properties":{"list_id":{"type":"string","title":"List Id","description":"ID of the Lead List to export. Must be a list your API key is authorized to read.","examples":["list_7c2a91"]},"columns":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Columns","description":"Explicit, ordered subset of columns to write. When omitted, the default set is used: lead_id, first_name, last_name, email, title, company, domain, linkedin_url, phone, location.","examples":[["first_name","last_name","email","company_domain"]]},"include_smart_agents":{"type":"boolean","title":"Include Smart Agents","description":"Append the list's configured smart-agent result columns after the selected columns. Set false to export only the base columns.","default":true,"examples":[true]}},"additionalProperties":false,"type":"object","required":["list_id"],"title":"ExportCsvRequest","example":{"columns":["first_name","last_name","email","company_domain"],"include_smart_agents":true,"list_id":"list_7c2a91"}},"ExportCsvResponse":{"properties":{"list_id":{"type":"string","title":"List Id","description":"ID of the Lead List that was exported.","examples":["list_7c2a91"]},"download_url":{"type":"string","title":"Download Url","description":"Time-limited, SAS-signed URL to download the generated CSV file. The signature (`sig`) and expiry (`exp`) query params gate access; the link stops working after `expires_at`.","examples":["https://api.cleanlist.ai/exports/list_7c2a91.csv?sig=Rk9PQkFSc2lnbmF0dXJlLWV4YW1wbGU&exp=1751404800"]},"expires_at":{"type":"string","title":"Expires At","description":"ISO-8601 UTC timestamp at which `download_url` expires. Generated URLs are valid for 24 hours.","examples":["2025-07-01T21:20:00+00:00"]},"row_count":{"type":"integer","title":"Row Count","description":"Number of data rows written to the CSV (excludes the header row).","examples":[1284]},"file_size_bytes":{"type":"integer","title":"File Size Bytes","description":"Size of the generated CSV file in bytes.","examples":[402118]}},"additionalProperties":false,"type":"object","required":["list_id","download_url","expires_at","row_count","file_size_bytes"],"title":"ExportCsvResponse","example":{"download_url":"https://api.cleanlist.ai/exports/list_7c2a91.csv?sig=Rk9PQkFSc2lnbmF0dXJlLWV4YW1wbGU&exp=1751404800","expires_at":"2025-07-01T21:20:00+00:00","file_size_bytes":402118,"list_id":"list_7c2a91","row_count":1284}},"ExportJsonResponse":{"properties":{"task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Id"},"timestamp_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timestamp Ms"},"agent_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Instructions"},"usage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Usage"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"list_id":{"type":"string","title":"List Id","description":"ID of the Lead List whose leads were returned.","examples":["list_7c2a91"]},"leads":{"items":{"additionalProperties":true,"type":"object"},"type":"array","title":"Leads","description":"Page of lead rows returned inline, each projected to the selected (or default) columns. At most `limit` rows are returned per call.","examples":[[{"company_domain":"analytical.io","email":"ada@analytical.io","first_name":"Ada","last_name":"Lovelace","lead_id":"lead_5f9d2b"}]]},"total":{"type":"integer","title":"Total","description":"Total number of leads in the list (across all pages).","examples":[1284]},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor","description":"Opaque pagination handle for the next page. Pass it back as the `cursor` query param to continue; null when the last page has been reached.","examples":["100"]}},"additionalProperties":false,"type":"object","required":["list_id","leads","total"],"title":"ExportJsonResponse","example":{"cursor":"100","leads":[{"company_domain":"analytical.io","email":"ada@analytical.io","first_name":"Ada","last_name":"Lovelace","lead_id":"lead_5f9d2b"},{"company_domain":"bletchley.co","email":"alan@bletchley.co","first_name":"Alan","last_name":"Turing","lead_id":"lead_6a1e4c"}],"list_id":"list_7c2a91","task_id":"task_9f2c1a","timestamp_ms":1751404800000,"total":1284}},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"LeadRow":{"properties":{"lead_id":{"type":"string","title":"Lead Id","description":"Stable lead identifier. Reusable as an add/remove lead_id.","examples":["lead_5f3b8c"]},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name","description":"Given name, or null if unknown.","examples":["Jane"]},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name","description":"Family name, or null if unknown.","examples":["Doe"]},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name","description":"Pre-composed display name (Prospect.full_name, else first + last).","examples":["Jane Doe"]},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Primary email, or null until the lead is email-enriched.","examples":["jane.doe@acme.io"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Primary personal phone (E.164), or null until phone-enriched.","examples":["+14155550142"]},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Job title, or null if unknown.","examples":["VP of Sales"]},"company":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company","description":"Current company name, or null if unknown.","examples":["Acme"]},"location":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Location","description":"Composed \"City, State, Country\" string, or null.","examples":["San Francisco, California, United States"]},"linkedin_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linkedin Url","description":"Resolved LinkedIn profile URL, or null if unknown. Use it to confirm the enrichment matched the intended person.","examples":["https://www.linkedin.com/in/jane-doe-9a1b"]},"provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider","description":"Enrichment provider that resolved this lead's contact data, or null if not enriched.","examples":["leadmagic"]}},"additionalProperties":false,"type":"object","required":["lead_id"],"title":"LeadRow","example":{"company":"Acme","email":"jane.doe@acme.io","first_name":"Jane","full_name":"Jane Doe","last_name":"Doe","lead_id":"lead_5f3b8c","location":"San Francisco, California, United States","phone":"+14155550142","title":"VP of Sales"}},"LeadsPageResponse":{"properties":{"task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Id"},"timestamp_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timestamp Ms"},"agent_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Instructions"},"usage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Usage"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"list_id":{"type":"string","title":"List Id","description":"Identifier of the list these leads belong to.","examples":["list_7c2a91"]},"leads":{"items":{"$ref":"#/components/schemas/LeadRow"},"type":"array","title":"Leads","description":"The page of leads in the list."},"total":{"type":"integer","title":"Total","description":"Total number of leads in the list across all pages.","examples":[128]},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor","description":"Opaque handle for the next page; pass back as `cursor`. Null on the last page.","examples":["100"]}},"additionalProperties":false,"type":"object","required":["list_id","leads","total"],"title":"LeadsPageResponse","example":{"cursor":"100","leads":[{"company":"Acme","email":"jane.doe@acme.io","first_name":"Jane","full_name":"Jane Doe","last_name":"Doe","lead_id":"lead_5f3b8c","location":"San Francisco, California, United States","phone":"+14155550142","title":"VP of Sales"}],"list_id":"list_7c2a91","task_id":"task_9f2c1a","timestamp_ms":1751404800000,"total":128}},"ListDetailResponse":{"properties":{"task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Id"},"timestamp_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timestamp Ms"},"agent_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Instructions"},"usage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Usage"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"list_id":{"type":"string","title":"List Id","description":"Stable identifier of the list.","examples":["list_7c2a91"]},"name":{"type":"string","title":"Name","description":"Display name of the list.","examples":["Q3 Enterprise SaaS Leads"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Free-text note describing the list, or null.","examples":["VPs of Sales at Series B+ SaaS companies, North America."]},"lead_count":{"type":"integer","title":"Lead Count","description":"Live count of leads in the list (derived from a fresh COUNT).","examples":[128]},"enriched_count":{"type":"integer","title":"Enriched Count","description":"How many of the leads have been enriched (email/phone found).","examples":[96]},"folder_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Folder Id","description":"Folder the list is filed under, or null if unfiled.","examples":["folder_3d9b12"]},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At","description":"ISO-8601 timestamp of when the list was created.","examples":["2026-06-28T14:32:00Z"]},"reused":{"type":"boolean","title":"Reused","description":"True only when POST /lead-lists returned a pre-existing same-name list (idempotent reuse). Always false on reads and genuine creates.","default":false,"examples":[false]}},"additionalProperties":false,"type":"object","required":["list_id","name","lead_count","enriched_count"],"title":"ListDetailResponse","example":{"created_at":"2026-06-28T14:32:00Z","description":"VPs of Sales at Series B+ SaaS companies, North America.","enriched_count":96,"folder_id":"folder_3d9b12","lead_count":128,"list_id":"list_7c2a91","name":"Q3 Enterprise SaaS Leads","reused":false,"task_id":"task_9f2c1a","timestamp_ms":1751404800000}},"ListSmartAgentsResponse":{"properties":{"task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Id"},"timestamp_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timestamp Ms"},"agent_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Instructions"},"usage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Usage"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"runs":{"items":{"$ref":"#/components/schemas/SmartAgentRun"},"type":"array","title":"Runs","description":"Smart-agent runs, most recent first, scoped to what the caller can see (a single list when `list_id` is passed, else a rollup across accessible lists)."},"total":{"type":"integer","title":"Total","description":"Number of runs returned in `runs` (bounded by `limit`).","examples":[2]}},"additionalProperties":false,"type":"object","required":["runs","total"],"title":"ListSmartAgentsResponse","example":{"runs":[{"agent_type":"custom_ai","column_name":"Fit Research","created_at":"2026-07-01T20:40:00Z","failed":2,"list_id":"9c1f7e2a-4b6d-4f21-9a3e-2b7c8d1e5f04","processed":100,"status":"completed","task_id":"run_7c2a91","total":100},{"agent_type":"cold_intro_email","column_name":"Cold Intro","created_at":"2026-07-01T21:05:00Z","failed":0,"list_id":"9c1f7e2a-4b6d-4f21-9a3e-2b7c8d1e5f04","processed":18,"status":"processing","task_id":"run_4b1e77","total":50}],"task_id":"task_9f2c1a","timestamp_ms":1751404800000,"total":2}},"ListSummary":{"properties":{"list_id":{"type":"string","title":"List Id","description":"Stable identifier of the list.","examples":["list_7c2a91"]},"name":{"type":"string","title":"Name","description":"Display name of the list.","examples":["Q3 Enterprise SaaS Leads"]},"lead_count":{"type":"integer","title":"Lead Count","description":"Live count of leads currently in the list.","examples":[128]},"folder_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Folder Id","description":"Folder the list is filed under, or null if unfiled.","examples":["folder_3d9b12"]},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At","description":"ISO-8601 timestamp of when the list was created.","examples":["2026-06-28T14:32:00Z"]},"owner_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner Name","description":"Display name of the list's creator (Clerk first + last name). Null when the owner was deleted, has no profile name, or the lookup failed.","examples":["Jane Doe"]},"visibility":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Visibility","description":"Sharing scope: 'private' (owner only), 'organization' (visible org-wide), or 'shared' (shared with specific members). Null on legacy rows.","examples":["organization"]}},"additionalProperties":false,"type":"object","required":["list_id","name","lead_count"],"title":"ListSummary","example":{"created_at":"2026-06-28T14:32:00Z","folder_id":"folder_3d9b12","lead_count":128,"list_id":"list_7c2a91","name":"Q3 Enterprise SaaS Leads","owner_name":"Jane Doe","visibility":"organization"}},"ListsPageResponse":{"properties":{"task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Id"},"timestamp_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timestamp Ms"},"agent_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Instructions"},"usage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Usage"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"lists":{"items":{"$ref":"#/components/schemas/ListSummary"},"type":"array","title":"Lists","description":"The page of lists accessible to the caller."},"total":{"type":"integer","title":"Total","description":"Total number of accessible lists across all pages.","examples":[2]},"cursor":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cursor","description":"Opaque handle for the next page; pass back as `cursor`. Null when this is the last page.","examples":["50"]}},"additionalProperties":false,"type":"object","required":["lists","total"],"title":"ListsPageResponse","example":{"lists":[{"created_at":"2026-06-28T14:32:00Z","folder_id":"folder_3d9b12","lead_count":128,"list_id":"list_7c2a91","name":"Q3 Enterprise SaaS Leads","owner_name":"Jane Doe","visibility":"organization"},{"created_at":"2026-06-12T09:05:00Z","lead_count":41,"list_id":"list_4b8e02","name":"Inbound Trials — June","owner_name":"Jane Doe","visibility":"private"}],"task_id":"task_9f2c1a","timestamp_ms":1751404800000,"total":2}},"PublicLeadListUpdate":{"properties":{"name":{"anyOf":[{"type":"string","maxLength":200,"minLength":1},{"type":"null"}],"title":"Name","description":"New display name (1–200 chars). Omit to leave unchanged; sending null is rejected — the name column is NOT NULL.","examples":["Q3 Enterprise SaaS Leads (EMEA)"]},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"New description. Omit to leave unchanged; send null to clear it.","examples":["Refocused on Series B+ SaaS in EMEA."]},"folder_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Folder Id","description":"Move the list to this folder (must be one you own). Omit to leave unchanged; send null to unfile the list.","examples":["folder_3d9b12"]}},"additionalProperties":false,"type":"object","title":"PublicLeadListUpdate","description":"PATCH body for renaming / re-describing / re-filing a list.\n\nAll fields optional but at least one must be supplied. PATCH\nsemantics: only the fields PRESENT in the request change. A field\nset to ``null`` clears it (``description``) or unfiles the list\n(``folder_id``); a field omitted entirely is left untouched. The\nomitted-vs-explicit-null distinction is read from\n``model_fields_set`` in the handler — which is exactly why\n``extra=\"forbid\"`` matters here: an unknown key would otherwise land\nin that set and be mistaken for a real field change.","example":{"description":"Refocused on Series B+ SaaS in EMEA.","folder_id":"folder_3d9b12","name":"Q3 Enterprise SaaS Leads (EMEA)"}},"RemoveLeadsRequest":{"properties":{"lead_ids":{"items":{"type":"string"},"type":"array","maxItems":100,"minItems":1,"title":"Lead Ids","description":"Lead UUIDs to remove from the list (max 100 per call). Unknown ids are silently no-op — no 404.","examples":[["lead_5f3b8c","lead_9d2e71"]]}},"additionalProperties":false,"type":"object","required":["lead_ids"],"title":"RemoveLeadsRequest","example":{"lead_ids":["lead_5f3b8c","lead_9d2e71"]}},"RemoveLeadsResponse":{"properties":{"list_id":{"type":"string","title":"List Id","description":"Identifier of the list leads were removed from.","examples":["list_7c2a91"]},"removed":{"type":"integer","title":"Removed","description":"Number of membership rows actually removed.","examples":[2]}},"additionalProperties":false,"type":"object","required":["list_id","removed"],"title":"RemoveLeadsResponse","example":{"list_id":"list_7c2a91","removed":2}},"RunSmartAgentRequest":{"properties":{"list_id":{"type":"string","title":"List Id","description":"UUID of the lead list the agent runs against. Must be a list this API key can access (owned, org-visible, or shared) — a known id for a private list in the same org is rejected with 404.","examples":["9c1f7e2a-4b6d-4f21-9a3e-2b7c8d1e5f04"]},"agent_type":{"type":"string","enum":["custom_ai","cold_intro_email","title_normalizer","company_intel","pain_point_hypothesis"],"title":"Agent Type","description":"Which agent to run. `custom_ai` runs your own `prompt`; `cold_intro_email` drafts a personalized opener from an email template. The preset research agents (`title_normalizer`, `company_intel`, `pain_point_hypothesis`) run a baked-in instruction and ignore `prompt`. All types execute asynchronously as an AI smart column.","default":"custom_ai","examples":["custom_ai"]},"prompt":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Prompt","description":"Free-text instruction applied per lead. REQUIRED when `agent_type='custom_ai'` (a 400 `validation_error` is returned otherwise). Ignored for `cold_intro_email` and the preset research agents, which carry their own instruction.","examples":["For each lead, research their company's recent funding and product launches, then summarize in 2 sentences why they are a strong outbound fit."]},"column_name":{"type":"string","maxLength":100,"minLength":1,"title":"Column Name","description":"Display name for the new column that stores each lead's agent output. Re-using an existing column name appends to it unless `overwrite=true` is set.","examples":["Fit Research"]},"lead_scope":{"type":"string","enum":["subset","all"],"title":"Lead Scope","description":"`subset` runs on up to `max_rows` leads (the fast, cheap path). `all` runs on the entire list, but any `all` run above the configured threshold (default 500 leads) is blocked with a 400 `approval_required` until you narrow scope or route a human approval.","default":"subset","examples":["subset"]},"max_rows":{"type":"integer","maximum":10000.0,"minimum":1.0,"title":"Max Rows","description":"Upper bound on how many leads to process when `lead_scope='subset'`. Clamped to the list size. Ignored when `lead_scope='all'`.","default":100,"examples":[100]},"quote_id":{"type":"string","title":"Quote Id","description":"Single-use spend quote from POST /credits/estimate, signed for this exact request shape (list_id + agent_type + column_name + row count). It is verified and atomically redeemed before the run starts; a stale, mismatched, or already-used quote returns a 400 or 409. Re-quote if you change any field.","examples":["qte_v1_1751404800_a3f9c8e2b7d14f6a9c0e1b2d3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a"]},"overwrite":{"type":"boolean","title":"Overwrite","description":"When true, existing values in a column of the same name are overwritten instead of preserved. Passed through to the workflow via column metadata.","default":false,"examples":[false]}},"additionalProperties":false,"type":"object","required":["list_id","column_name","quote_id"],"title":"RunSmartAgentRequest","example":{"agent_type":"custom_ai","column_name":"Fit Research","lead_scope":"subset","list_id":"9c1f7e2a-4b6d-4f21-9a3e-2b7c8d1e5f04","max_rows":100,"overwrite":false,"prompt":"For each lead, research their company's recent funding rounds and product launches, then write a 2-sentence summary of why they are a strong fit for our sales-automation platform. Only cite facts supported by the available lead and company fields.","quote_id":"qte_v1_1751404800_a3f9c8e2b7d14f6a9c0e1b2d3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a"}},"RunSmartAgentResponse":{"properties":{"task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Id"},"timestamp_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timestamp Ms"},"agent_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Instructions"},"usage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Usage"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"smart_agent_task_id":{"type":"string","title":"Smart Agent Task Id","description":"Canonical run id. Pass this to GET /smart-agents/{id} to poll progress and per-lead results.","examples":["run_7c2a91"]},"smart_agent_task_ids":{"additionalProperties":{"type":"string"},"type":"object","title":"Smart Agent Task Ids","description":"Underlying task ids keyed by column type. Usually a single entry; populated when the run fans out to more than one smart-column task.","examples":[{"custom_ai":"run_7c2a91"}]},"status":{"type":"string","title":"Status","description":"Initial run status; always 'pending' at launch.","examples":["pending"]},"list_id":{"type":"string","title":"List Id","description":"UUID of the lead list the run was launched against.","examples":["9c1f7e2a-4b6d-4f21-9a3e-2b7c8d1e5f04"]},"column_name":{"type":"string","title":"Column Name","description":"Name of the column that will hold each lead's output.","examples":["Fit Research"]},"estimated_cost":{"type":"integer","title":"Estimated Cost","description":"Credits expected for this run (per-lead rate × row count). Actual debit happens as rows complete and may be lower if leads fail.","examples":[100]}},"additionalProperties":false,"type":"object","required":["smart_agent_task_id","status","list_id","column_name","estimated_cost"],"title":"RunSmartAgentResponse","description":"Three identifiers can live on this response (T1-6 ID-space rename):\n\n- `smart_agent_task_id` — canonical id for polling\n  `get_smart_agent_results`.\n- `smart_agent_task_ids` — dict keyed by column type when\n  `process_smart_columns` returns multiple underlying task ids.\n- `task_id` (from EnvelopeFields) — kept populated for back-compat\n  with v1.0 callers that read it from the envelope.\n\nThe three are distinct identifiers. A smart-column run's id is NOT\nan MCP cohort handle, but the envelope still carries `task_id` for\ntransitional compat. New callers should read `smart_agent_task_id`.","example":{"agent_instructions":"Run started over 100 leads. Poll GET /smart-agents/run_7c2a91 every few seconds until status is 'completed'. ~100 credits are deducted as rows complete.","column_name":"Fit Research","estimated_cost":100,"list_id":"9c1f7e2a-4b6d-4f21-9a3e-2b7c8d1e5f04","smart_agent_task_id":"run_7c2a91","smart_agent_task_ids":{"custom_ai":"run_7c2a91"},"status":"pending","task_id":"task_9f2c1a","timestamp_ms":1751404800000}},"SingleEnrichmentResultRead":{"properties":{"lead_id":{"type":"string","title":"Lead Id","description":"ID of the persisted Lead. Empty when the provider cascade was exhausted with no usable result.","examples":["lead_4a7c"]},"status":{"type":"string","title":"Status","description":"EnrichmentResult-level outcome: 'completed' (lead persisted) or 'failed'. Distinct from the outer workflow status.","examples":["completed"]},"full_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Full Name","description":"Contact's full name.","examples":["Jane Doe"]},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name","description":"Contact's given name.","examples":["Jane"]},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name","description":"Contact's family name.","examples":["Doe"]},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Discovered/verified work email; null if none found.","examples":["jane@acme.io"]},"email_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email Status","description":"Verification status of the email (e.g. valid, risky, unknown).","examples":["valid"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Discovered phone in E.164; null for partial scope or if none found.","examples":["+14155550142"]},"linkedin_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Linkedin Url","description":"Canonical LinkedIn profile URL.","examples":["https://www.linkedin.com/in/jane-doe-9a1b"]},"title":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Title","description":"Contact's job title.","examples":["VP of Sales"]},"company":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Company","description":"Contact's company name.","examples":["Acme"]},"enrichment_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enrichment Type","description":"Scope that produced this result (partial/phone_only/full).","examples":["full"]},"provider":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Provider","description":"Waterfall provider that resolved the data (e.g. wiza, leadmagic).","examples":["wiza"]}},"additionalProperties":false,"type":"object","required":["lead_id","status"],"title":"SingleEnrichmentResultRead","description":"Inline enriched-lead payload for single-workflow polls.\n\nPopulated by `enrich_status` only when the workflow is a single-\nlead `enrich_person` run AND has reached a terminal state. Read\nfrom the workflow's returned `EnrichmentResult` via Temporal's\nhistory — no extra DB round-trip, no race with backend visibility.\nBulk-list runs leave `result=None` on the parent response (the\naggregate counters carry the cohort's progress instead).\n\n`status` here is the EnrichmentResult-level outcome\n(\"completed\" / \"failed\"), NOT the outer workflow status. Use it\nto distinguish a real enrichment (Lead persisted, lead_id set)\nfrom a workflow that returned cleanly with no usable Lead (e.g.\nprovider cascade exhausted — `lead_id` will be empty).","example":{"company":"Acme","email":"jane@acme.io","email_status":"valid","enrichment_type":"full","first_name":"Jane","full_name":"Jane Doe","last_name":"Doe","lead_id":"lead_4a7c","linkedin_url":"https://www.linkedin.com/in/jane-doe-9a1b","phone":"+14155550142","provider":"wiza","status":"completed","title":"VP of Sales"}},"SmartAgentResultRow":{"properties":{"lead_id":{"type":"string","title":"Lead Id","description":"UUID of the lead this result row belongs to.","examples":["b2d4f6a8-1c3e-4d5f-8a9b-0c1d2e3f4a5b"]},"value":{"anyOf":[{},{"type":"null"}],"title":"Value","description":"The agent's output for this lead (usually a string; shape depends on the agent type). Null when the row failed or hasn't been processed yet.","examples":["Acme raised a $40M Series B in Mar 2026 and shipped an AI analytics suite; the VP of Sales owns quota for a 60-rep team — a strong fit for outbound automation."]},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"Error message when this lead failed processing; null on success.","examples":[null]}},"additionalProperties":false,"type":"object","required":["lead_id"],"title":"SmartAgentResultRow","example":{"lead_id":"b2d4f6a8-1c3e-4d5f-8a9b-0c1d2e3f4a5b","value":"Acme raised a $40M Series B in Mar 2026 and shipped an AI analytics suite; the VP of Sales owns quota for a 60-rep team — a strong fit for outbound automation."}},"SmartAgentResultsResponse":{"properties":{"task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Id"},"timestamp_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timestamp Ms"},"agent_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Instructions"},"usage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Usage"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"smart_agent_task_id":{"type":"string","title":"Smart Agent Task Id","description":"The run id being polled (echoes the path parameter).","examples":["run_7c2a91"]},"status":{"type":"string","title":"Status","description":"Run lifecycle state: `pending`, `processing`, `completed`, or `failed`.","examples":["completed"]},"progress":{"type":"integer","title":"Progress","description":"Percent complete (0–100), derived from processed / total.","examples":[100]},"total":{"type":"integer","title":"Total","description":"Total leads scheduled for this run.","examples":[100]},"succeeded":{"type":"integer","title":"Succeeded","description":"Leads that produced a value without error.","examples":[98]},"failed":{"type":"integer","title":"Failed","description":"Leads that errored during processing.","examples":[2]},"results":{"items":{"$ref":"#/components/schemas/SmartAgentResultRow"},"type":"array","title":"Results","description":"Per-lead output rows attributed to this run. May be empty while the run is still starting up."}},"additionalProperties":false,"type":"object","required":["smart_agent_task_id","status","progress","total","succeeded","failed","results"],"title":"SmartAgentResultsResponse","example":{"failed":2,"progress":100,"results":[{"lead_id":"b2d4f6a8-1c3e-4d5f-8a9b-0c1d2e3f4a5b","value":"Acme raised a $40M Series B in Mar 2026 and shipped an AI analytics suite; the VP of Sales owns quota for a 60-rep team — a strong fit for outbound automation."},{"error":"No company domain on lead; skipped research.","lead_id":"d4f6a8b2-3e1c-4d5f-9a8b-1c2d3e4f5a6b"}],"smart_agent_task_id":"run_7c2a91","status":"completed","succeeded":98,"task_id":"task_9f2c1a","timestamp_ms":1751404800000,"total":100}},"SmartAgentRun":{"properties":{"task_id":{"type":"string","title":"Task Id","description":"Run id for this smart-agent run — poll it via GET /smart-agents/{id}.","examples":["run_7c2a91"]},"list_id":{"type":"string","title":"List Id","description":"UUID of the lead list this run targeted.","examples":["9c1f7e2a-4b6d-4f21-9a3e-2b7c8d1e5f04"]},"column_name":{"type":"string","title":"Column Name","description":"Name of the column the run wrote its output to.","examples":["Fit Research"]},"agent_type":{"type":"string","title":"Agent Type","description":"Agent / smart-column type the run executed as (e.g. `custom_ai`, `cold_intro_email`).","examples":["custom_ai"]},"status":{"type":"string","title":"Status","description":"Lifecycle state: `pending`, `processing`, `completed`, or `failed`.","examples":["completed"]},"total":{"type":"integer","title":"Total","description":"Total leads scheduled for this run.","default":0,"examples":[100]},"processed":{"type":"integer","title":"Processed","description":"Leads processed so far (succeeded + failed).","default":0,"examples":[100]},"failed":{"type":"integer","title":"Failed","description":"Leads that errored during processing.","default":0,"examples":[2]},"created_at":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Created At","description":"ISO-8601 timestamp of when the run was created.","examples":["2026-07-01T20:40:00Z"]}},"additionalProperties":false,"type":"object","required":["task_id","list_id","column_name","agent_type","status"],"title":"SmartAgentRun","example":{"agent_type":"custom_ai","column_name":"Fit Research","created_at":"2026-07-01T20:40:00Z","failed":2,"list_id":"9c1f7e2a-4b6d-4f21-9a3e-2b7c8d1e5f04","processed":100,"status":"completed","task_id":"run_7c2a91","total":100}},"UsageGroupRow":{"properties":{"group":{"type":"string","title":"Group","description":"Bucket label for this row, per the requested `group_by`: the tool/path, key id, day, or error class.","examples":["/api/v1/public/enrichment/by-list"]},"calls":{"type":"integer","title":"Calls","description":"Number of API calls counted in this bucket over the window.","examples":[142]},"credits_spent":{"type":"number","title":"Credits Spent","description":"Credits spent in this bucket. Float because per-lead agents can cost fractional credits (e.g. 0.5). Currently 0.0 until the request log gains a per-row cost column.","examples":[0.0]},"errors":{"type":"integer","title":"Errors","description":"Number of error (non-2xx) responses counted in this bucket.","examples":[3]}},"additionalProperties":false,"type":"object","required":["group","calls","credits_spent","errors"],"title":"UsageGroupRow","example":{"calls":142,"credits_spent":0.0,"errors":3,"group":"/api/v1/public/enrichment/by-list"}},"UsageReportResponse":{"properties":{"days":{"type":"integer","title":"Days","description":"Length of the reporting window in days (as requested).","examples":[7]},"group_by":{"type":"string","title":"Group By","description":"Dimension the rows are grouped by: `tool`, `key`, `day`, or `error`.","examples":["tool"]},"total_calls":{"type":"integer","title":"Total Calls","description":"Total API calls across all buckets in the window.","examples":[318]},"total_credits_spent":{"type":"number","title":"Total Credits Spent","description":"Total credits spent across all buckets. Currently 0.0 until the request log gains a per-row cost column.","examples":[0.0]},"total_errors":{"type":"integer","title":"Total Errors","description":"Total error (non-2xx) responses across all buckets.","examples":[5]},"rows":{"items":{"$ref":"#/components/schemas/UsageGroupRow"},"type":"array","title":"Rows","description":"Per-bucket usage rows for the requested `group_by` dimension."}},"additionalProperties":false,"type":"object","required":["days","group_by","total_calls","total_credits_spent","total_errors","rows"],"title":"UsageReportResponse","example":{"days":7,"group_by":"tool","rows":[{"calls":142,"credits_spent":0.0,"errors":3,"group":"/api/v1/public/enrichment/by-list"},{"calls":176,"credits_spent":0.0,"errors":2,"group":"/api/v1/public/search/people"}],"total_calls":318,"total_credits_spent":0.0,"total_errors":5}},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WhoamiResponse":{"properties":{"task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Id"},"timestamp_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timestamp Ms"},"agent_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Instructions"},"usage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Usage"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"user_email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Email","description":"Email of the authenticating principal. For `api_key` auth this is MASKED (e.g. `j***@acme.io`) so a leaked key can't reveal the creator's full address; for `clerk_jwt`/`oauth` it is the full verified email. Null when no email is associated with the token.","examples":["jordan@acme.io","j***@acme.io"]},"user_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Id","description":"Clerk user id (`sub`) of the authenticated principal, or null when the token carries no user subject.","examples":["user_2abcJ0rdanRivera"]},"user_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"User Name","description":"Human-readable display name composed from Clerk's `first_name` + `last_name`. Nullable — users who never set a profile name get null and the client falls back to `user_email`.","examples":["Jordan Rivera"]},"organization_id":{"type":"string","title":"Organization Id","description":"Cleanlist organization id that owns this session's credits and lists.","examples":["org_2abcRevenueLabs"]},"organization_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Organization Name","description":"Display name for the organization. Nullable for orgs that never set one (early signup, trials that never completed setup); the client falls back to `organization_id`.","examples":["Acme Revenue Labs"]},"auth_type":{"type":"string","enum":["clerk_jwt","api_key","oauth"],"title":"Auth Type","description":"How the caller authenticated: `clerk_jwt` (portal session token), `api_key` (a `clapi_` bearer key), or `oauth` (third-party grant).","examples":["api_key"]},"scopes":{"items":{"type":"string"},"type":"array","title":"Scopes","description":"Granted permission scopes for this credential (per-user Clerk `publicMetadata.cleanlist_scopes`). Endpoints gate on these — e.g. `credits:read`, `search:read`, `lists:write`, `enrichment:write`, `admin:api_keys`.","examples":[["credits:read","search:read","lists:write","enrichment:write"]]},"tier":{"type":"string","enum":["free","starter","pro","enterprise"],"title":"Tier","description":"Resolved plan tier from the org's Stripe `credits_product_id` (V2 'Scale' buyers map to `enterprise`). The MCP reads this once per session to tier-filter its advertised tool list. Unknown / unmapped products fall through to `free` (the safe default).","examples":["pro"]},"appsumo_tier":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Appsumo Tier","description":"The org's active AppSumo tier (1-7), or null for non-AppSumo orgs. Surfaced alongside `tier` so a client can label an AppSumo buyer's plan correctly ('AppSumo Tier 5') even though their Stripe `tier` reads `free`. Effective entitlements are already merged into `features`.","examples":[null,5]},"features":{"items":{"type":"string"},"type":"array","title":"Features","description":"EFFECTIVE feature grants the gates actually enforce: base Stripe tier ∪ AppSumo carve-outs − AppSumo blocks (via `has_feature_effective`). Sourced from the same function the server-side gate uses so the advertised list and the gate can't drift. Sorted alphabetically for stable diffs.","examples":[["data_source_csv_upload","enrich_email","enrich_phone","search_people","sync_to_crm"]]}},"additionalProperties":false,"type":"object","required":["user_email","organization_id","auth_type","scopes","tier","features"],"title":"WhoamiResponse","example":{"agent_instructions":"You have 4,820 credits remaining.","auth_type":"api_key","features":["data_source_csv_upload","enrich_email","enrich_phone","search_people","sync_to_crm"],"organization_id":"org_2abcRevenueLabs","organization_name":"Acme Revenue Labs","scopes":["credits:read","search:read","lists:write","enrichment:write"],"task_id":"task_9f2c1a","tier":"pro","timestamp_ms":1751404800000,"user_email":"jordan@acme.io","user_id":"user_2abcJ0rdanRivera","user_name":"Jordan Rivera"}},"WorkflowStatusResponse":{"properties":{"task_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Task Id"},"timestamp_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Timestamp Ms"},"agent_instructions":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Agent Instructions"},"usage":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Usage"},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"workflow_id":{"type":"string","title":"Workflow Id","description":"The workflow (or cohort task) handle being polled.","examples":["wf_enrich_7c2a"]},"status":{"type":"string","title":"Status","description":"Outer workflow status: e.g. 'pending', 'running', 'completed', 'failed', 'cancelled'.","examples":["completed"]},"progress":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Progress","description":"Percent complete (0-100); null when not yet available.","examples":[100]},"total":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Total","description":"Total prospects/leads in the run (1 for single enrichment).","examples":[1]},"processed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Processed","description":"Count processed so far (completed + failed).","examples":[1]},"completed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Completed","description":"Count successfully enriched.","examples":[1]},"failed":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Failed","description":"Count that failed enrichment.","examples":[0]},"enrichment_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Enrichment Type","description":"Scope of the run (partial/phone_only/full).","examples":["full"]},"lead_list_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Lead List Id","description":"List enriched results are written to.","examples":["ll_7f3a2b9c"]},"summary":{"anyOf":[{"additionalProperties":true,"type":"object"},{"type":"null"}],"title":"Summary","description":"Aggregate counters for the run (e.g. emails_found, phones_found).","examples":[{"emails_found":1,"phones_found":1}]},"refund_status":{"anyOf":[{"type":"string","enum":["not_applicable","pending_review"]},{"type":"null"}],"title":"Refund Status","description":"Set to 'pending_review' when a prepaid-via-MCP run ends in a failed/cancelled state (credits are NOT auto-refunded — contact support). 'not_applicable' for successful/non-MCP terminal runs; null while running.","examples":["not_applicable"]},"refund_message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Refund Message","description":"Human-readable refund guidance to surface verbatim; null unless refund_status is 'pending_review'.","examples":[null]},"result":{"anyOf":[{"$ref":"#/components/schemas/SingleEnrichmentResultRead"},{"type":"null"}],"description":"Inline enriched-lead payload for terminal single-lead workflows. Always null for bulk runs (use the aggregate counters instead)."},"credits_charged":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credits Charged","description":"Actual credits billed after settle. Null while running or for non-prepaid legacy runs; 0 for a sweep-refunded run.","examples":[11]},"credits_refunded":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Credits Refunded","description":"Unused portion of the upfront reservation returned at settle. Null while running or when the full reserve was used.","examples":[0]}},"additionalProperties":false,"type":"object","required":["workflow_id","status"],"title":"WorkflowStatusResponse","example":{"completed":1,"credits_charged":11,"credits_refunded":0,"enrichment_type":"full","failed":0,"lead_list_id":"ll_7f3a2b9c","processed":1,"progress":100,"refund_status":"not_applicable","result":{"company":"Acme","email":"jane@acme.io","email_status":"valid","enrichment_type":"full","first_name":"Jane","full_name":"Jane Doe","last_name":"Doe","lead_id":"lead_4a7c","linkedin_url":"https://www.linkedin.com/in/jane-doe-9a1b","phone":"+14155550142","provider":"wiza","status":"completed","title":"VP of Sales"},"status":"completed","summary":{"emails_found":1,"phones_found":1},"task_id":"task_9f2c1a","timestamp_ms":1751404800000,"total":1,"workflow_id":"wf_enrich_7c2a"}}},"securitySchemes":{"HTTPBearer":{"type":"http","scheme":"bearer"}}},"tags":[{"name":"Public Workspace","description":"Workspace, credits, usage"},{"name":"Public Lead Lists","description":"Lead list management"},{"name":"Public Enrichment","description":"Enrichment operations"},{"name":"Public Smart Agents","description":"Smart agent runs"},{"name":"Public Export","description":"Data export"}],"servers":[{"url":"https://api.cleanlist.ai","description":"Production"},{"url":"http://localhost:8000","description":"Local development"}]}