# API Quick Start Source: https://docs.sixtyfour.ai/api-quick-start Authentication, base URL, rate limits, and API overview for the Sixtyfour API. ## API Playground Explore every endpoint interactively before writing a line of code. The [API Playground](https://app.sixtyfour.ai/playground/) lets you build requests, inspect live responses, and copy generated code snippets directly into your project. Run live API requests against Company Intelligence, People Intelligence, Find Email, Find Phone, and more — no setup required. ## Authentication All API requests require an API key passed in the `x-api-key` header. You also need to set the `Content-Type` header to `application/json`. ```http theme={null} x-api-key: YOUR_API_KEY Content-Type: application/json ``` ## Get an API Key Follow the steps in [Get an API Key](/get-api-key) to create your account and generate a key. ## Base URL All API endpoints are available at: ``` https://api.sixtyfour.ai ``` ## Endpoints | Endpoint | Description | | - | - | | [Company Intelligence](/api-reference/endpoint/company-intelligence) | Research and enrich company data with additional information and find associated people | | [People Intelligence](/api-reference/endpoint/people-intelligence) | Research and enrich people profiles with contact details, social profiles, and company data | | [Find Email](/api-reference/endpoint/find-email) | Find professional or personal email addresses for leads | | [Find Phone](/api-reference/endpoint/find-phone) | Find phone numbers for leads | | [QA Agent](/api-reference/endpoint/qa-agent) | Evaluate and qualify data against predefined criteria | | [Webhooks](/api-reference/webhooks) | Receive HTTP notifications when async jobs complete | | [Workflows](/api-reference/workflows/workflows-overview) | Create, manage, and execute batch enrichment pipelines | | [Deep Search](/api-reference/search/search-overview) | Run agentic deep searches with natural language queries; poll status and download results | | [Filter Search](/api-reference/search/filter-search) | Search people or companies with structured filters; supports cursor pagination | ## Error Handling See [Errors](/api-reference/errors) for status codes, error shapes, and retry guidance. ## Rate Limits See [Rate Limits](/api-reference/errors#rate-limits) for limits and retry guidance. ## Deprecation Notice The `findings` field in API responses is deprecated. It currently returns an empty list and will be removed in a future update. Use `structured_data` and `notes` instead. ## Quick Start Examples Use this example to find an email address with a single cURL request: ### Request ```bash theme={null} curl -X POST "https://api.sixtyfour.ai/find-email" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "lead": { "name": "Saarth Shah", "company": "Sixtyfour AI" } }' ``` ### Response ```json theme={null} { "company": "Sixtyfour AI", "email": [ ["saarth@sixtyfour.ai", "OK", "COMPANY"] ], "name": "Saarth Shah", "cost_cents": 5 } ``` Use this example to find a phone number with a single cURL request: ### Request ```bash theme={null} curl -X POST "https://api.sixtyfour.ai/find-phone" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "lead": { "name": "Saarth Shah", "company": "Sixtyfour AI" } }' ``` ### Response ```json theme={null} { "company": "Sixtyfour AI", "name": "Saarth Shah", "phone": "+1 415-990-2048", "cost_cents": 30 } ``` Use this example to enrich a company with a single cURL request: ```bash theme={null} curl -X POST "https://api.sixtyfour.ai/company-intelligence" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "target_company": { "company_name": "Sixtyfour AI", "website": "https://sixtyfour.ai" }, "struct": { "industry": "Primary business sector", "num_employees": "Approximate employee count" } }' ``` ### Response ```json theme={null} { "confidence_score": 9.0, "findings": [], // Deprecated - will be removed in a future update. Use structured_data and notes instead. "notes": "Sixtyfour AI Inc., verified by domain sixtyfour.ai, is a San Francisco-based AI company focused on developing an orchestration layer for company and people intelligence using AI research agents. The company builds AI research agents that enrich people and company data on demand, enabling data enrichment, email finding, and custom research APIs for sales intelligence and prospecting workflows. The official website is https://sixtyfour.ai and the SaaS product interface is available at https://app.sixtyfour.ai. Developer documentation is provided at https://docs.sixtyfour.ai/introduction. The company is SOC 2 Type 2 compliant (https://compliance.sixtyfour.ai/overview), underlining a commitment to security and compliance. The legal name is Sixtyfour AI Inc., and the company is privately held, founded in 2025 with headquarters in San Francisco, California, United States. The employee count is approximately 6-7, with key executives including co-founders Saarth Shah and Christopher Price, and founding engineers Hashim Khawaja and Erik Scarlatescu as per LinkedIn data. The company received a pre-seed funding round led by Y Combinator, raising $500,000. Industry classification is Research Services, focusing on artificial intelligence, market research, and sales. The LinkedIn company profile URL is https://www.linkedin.com/company/sixtyfourai, verified to belong to the target company. Twitter presence is at https://www.twitter.com/sixtyfourai. The company leverages a mature tech stack including Python, OpenAI, Elasticsearch, Redis, React, and more. Active job postings include roles in AI engineering and operations.", "org_chart": null, "references": { "https://app.sixtyfour.ai": "Sixtyfour AI SaaS application login and product platform.", "https://app.sixtyfour.ai/login": "Sixtyfour AI SaaS platform login page", "https://compliance.sixtyfour.ai/overview": "Compliance and SOC 2 Type 2 certification overview of Sixtyfour AI.", "https://docs.sixtyfour.ai/introduction": "Official Sixtyfour AI developer documentation site.", "https://sixtyfour.ai": "Official Sixtyfour AI website, source of company description and product information.", "https://www.linkedin.com/company/sixtyfourai": "Sixtyfour AI LinkedIn company profile provides company overview, employees, and executive information.", "https://www.sixtyfour.ai": "Official website of Sixtyfour AI - company overview and platform details" }, "structured_data": { "company_name": "Sixtyfour AI", "industry": "Intelligence infrastructure (AI-powered people & company intelligence)", "num_employees": "Approximately 6-7", "website": "https://sixtyfour.ai" } } ``` # Check Balance Source: https://docs.sixtyfour.ai/api-reference/account/check-balance https://api.sixtyfour.ai/openapi.json get /check-balance Returns the current credit balance for the organization associated with the API key. # Age Verification Source: https://docs.sixtyfour.ai/api-reference/endpoint/age-verification Establish a person's date of birth or age from open-source research, with per-field confidence and sourced evidence. ## Use case Confirm a person's age for trust-and-safety, KYC, and due-diligence workflows where self-reported age isn't enough. Age Verification researches public sources to establish a date of birth (or a grounded age estimate when an exact date isn't available), and returns the evidence and sources behind the determination. See the full request/response schema and parameters in the API Reference. ## Pricing Age Verification runs at the same rate as the `high` tier of [People Intelligence](/api-reference/endpoint/people-intelligence). See [Credits & Pricing Guide](/guides/credits-and-pricing) for credit costs. Unlike calling People Intelligence directly with `tier: "high"`, Age Verification does not require high-tier access approval — it's available to any paying org. ## Errors For error responses (400, 403, 404, etc.), see [Handling Errors](/api-reference/errors). *** ## Start an age verification Submit the person to verify. Returns a `task_id` for polling. ```http theme={null} POST https://api.sixtyfour.ai/verify-age ``` ### Example request ```json theme={null} { "subject": { "name": "Jane Doe", "company": "Acme Corp", "linkedin": "https://www.linkedin.com/in/janedoe" } } ``` Save the `task_id` returned in the response — you'll need it to poll for the result. `subject` also accepts `lead_info` or `input` as aliases, so callers already integrated with People Intelligence can reuse the same request shape unchanged. *** ## Get verification status Poll for progress and results of a running or completed age verification. ```http theme={null} GET https://api.sixtyfour.ai/verify-age/{task_id} ``` `task_id` is the value returned by Start an age verification. ### Status values | Status | Description | | - | - | | running | Verification is actively researching. | | completed | Research finished. Response includes `date_of_birth`, `age`, `evidence`, and `sources`. | | failed | Verification encountered an error. | | cancelled | Verification was cancelled before completing. | | terminated | Verification was terminated before completing. | | timed\_out | Verification exceeded its time budget before completing. | ### Understanding the response Once `completed`, the response contains: | Field | Description | | - | - | | `date_of_birth.day` / `.month` / `.year` | Individually gated on evidence — each is `null` unless established from a concrete source. | | `date_of_birth.confidence` | 0–10 confidence in the weakest established date component. | | `age.value` | Current age in whole years — exact when the date of birth is known, otherwise a grounded estimate. `null` if it could not be determined. | | `age.as_of` | ISO date the age value was calculated as of. | | `age.confidence` | 0–10 confidence in the age value. | | `evidence` | Summary of what evidence established the date of birth or age. | | `sources` | Source URLs backing the determination. | A `null` date-of-birth component means it was not established from concrete evidence — Age Verification never guesses a day, month, or year. `age.value` may still be populated as an estimate in that case. *** ## Example usage ```bash cURL theme={null} # 1. Start an age verification curl -X POST "https://api.sixtyfour.ai/verify-age" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "subject": { "name": "Jane Doe", "company": "Acme Corp", "linkedin": "https://www.linkedin.com/in/janedoe" } }' # 2. Poll for status (use task_id from step 1) curl -X GET "https://api.sixtyfour.ai/verify-age/TASK_ID" \ -H "x-api-key: YOUR_API_KEY" ``` ```python Python theme={null} import requests import time API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.sixtyfour.ai" headers = { "x-api-key": API_KEY, "Content-Type": "application/json" } start_response = requests.post( f"{BASE_URL}/verify-age", headers=headers, json={ "subject": { "name": "Jane Doe", "company": "Acme Corp", "linkedin": "https://www.linkedin.com/in/janedoe" } } ) start_response.raise_for_status() task_id = start_response.json()["task_id"] while True: status_response = requests.get(f"{BASE_URL}/verify-age/{task_id}", headers=headers) status_response.raise_for_status() status_data = status_response.json() if status_data["status"] == "completed": print(f"Date of birth: {status_data['date_of_birth']}") print(f"Age: {status_data['age']}") break if status_data["status"] in ("failed", "cancelled", "terminated", "timed_out"): raise RuntimeError(f"Verification {status_data['status']}") time.sleep(10) ``` ```javascript JavaScript theme={null} const API_KEY = "YOUR_API_KEY"; const BASE_URL = "https://api.sixtyfour.ai"; const headers = { "x-api-key": API_KEY, "Content-Type": "application/json" }; const startResponse = await fetch(`${BASE_URL}/verify-age`, { method: "POST", headers, body: JSON.stringify({ subject: { name: "Jane Doe", company: "Acme Corp", linkedin: "https://www.linkedin.com/in/janedoe" } }) }); const { task_id } = await startResponse.json(); while (true) { const statusResponse = await fetch(`${BASE_URL}/verify-age/${task_id}`, { headers }); const statusData = await statusResponse.json(); if (statusData.status === "completed") { console.log("Date of birth:", statusData.date_of_birth); console.log("Age:", statusData.age); break; } if (["failed", "cancelled", "terminated", "timed_out"].includes(statusData.status)) { throw new Error(`Verification ${statusData.status}`); } await new Promise((r) => setTimeout(r, 10000)); } ``` # Bulk Intelligence Source: https://docs.sixtyfour.ai/api-reference/endpoint/bulk-intelligence Upload a file of leads or companies and enrich every row in a single job. ## Use case Enrich thousands of records at once without managing per-row API calls. Upload a CSV or JSON file, define what you want back with a `struct`, and download the enriched results when the job completes. Use it for list building, CRM backfills, and large-scale data refreshes. ## Endpoint Use the people endpoint for lead/person rows: ```http theme={null} POST https://api.sixtyfour.ai/bulk-intelligence/people ``` Use the company endpoint for company rows: ```http theme={null} POST https://api.sixtyfour.ai/bulk-intelligence/company ``` Full request/response schema for `POST /bulk-intelligence/people`. Full request/response schema for `POST /bulk-intelligence/company`. ## Pricing See [Credits & Pricing Guide](/guides/credits-and-pricing) for credit costs by tier. Before the job starts, Sixtyfour checks that your balance covers the estimated cost. If it doesn't, the request returns 402 and nothing is charged. The submit response includes `estimated_cost_cents`. The completed `/job-status` response includes `charge_amount` in cents. ## Errors For error responses (400, 402, 403, 422, etc.), see [Handling Errors](/api-reference/errors). ## Tiers Allowed tiers depend on the endpoint: | Endpoint | Tiers | | - | - | | `/bulk-intelligence/people` | `micro`, `low` (default), `medium`, `high`, `xhigh` | | `/bulk-intelligence/company` | `micro`, `low` (default), `medium`, `high` | ## Request format Both endpoints take `multipart/form-data` with two parts: | Part | Description | | - | - | | `file` | The records to enrich. Accepts `.csv`, `.json`, `.jsonl`, or `.ndjson`. | | `config` | A JSON-encoded string describing the enrichment — what to return for each row. | ### Limits | Limit | Value | | - | - | | File size | 100 MB | | Rows per file | 100,000 | | Config size | 300,000 characters | | Submissions | 10 per minute per org | ## The `config` object ```json theme={null} { "struct": { "email": "The individual's email address", "linkedin": "LinkedIn URL for the person" }, "tier": "low", "field_confidence": true, "result_formats": ["csv", "ndjson"], "webhook_url": "https://your-server.com/webhooks/sixtyfour" } ``` For struct value formats, supported types, and casting, see [Struct & Type Casting](/guides/struct-and-type-casting). `result_formats` controls output files. CSV is always produced regardless of this field; add `"ndjson"` to also save typed values — numbers, booleans, and nested objects survive instead of being flattened to strings. When you request `ndjson`, the job-status `results` array contains one entry per format at the same `block_number` — match the `filename` extension (`.csv` or `.ndjson`) to pick the file you need. `micro`, `high`, and `xhigh` are gated — access is granted case-by-case by our team. `xhigh` is available on people bulk only. Requests for a tier your org doesn't have access to return 403 — see [Handling Errors](/api-reference/errors#403-forbidden). ## Submitting a job ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/bulk-intelligence/people" \ -H "x-api-key: YOUR_API_KEY" \ -F "file=@leads.csv" \ -F 'config={ "struct": { "email": "The individual'\''s email address", "linkedin": "LinkedIn URL for the person", "title": "The individual'\''s job title" } }' ``` ```python Python theme={null} import json import requests config = { "struct": { "email": "The individual's email address", "linkedin": "LinkedIn URL for the person", "title": "The individual's job title" } } with open("leads.csv", "rb") as f: response = requests.post( "https://api.sixtyfour.ai/bulk-intelligence/people", headers={"x-api-key": "YOUR_API_KEY"}, files={"file": ("leads.csv", f, "text/csv")}, data={"config": json.dumps(config)} ) response.raise_for_status() task_id = response.json()["task_id"] ``` ```javascript JavaScript theme={null} import { readFile } from "node:fs/promises"; const form = new FormData(); form.append("file", new Blob([await readFile("leads.csv")], { type: "text/csv" }), "leads.csv"); form.append("config", JSON.stringify({ struct: { email: "The individual's email address", linkedin: "LinkedIn URL for the person", title: "The individual's job title" } })); const response = await fetch("https://api.sixtyfour.ai/bulk-intelligence/people", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY" }, body: form }); if (!response.ok) throw new Error(`Submission failed: ${response.status}`); const { task_id } = await response.json(); ``` The response includes the job handle and the cost estimate: ```json theme={null} { "task_id": "9b1f9d2e-...", "status": "RUNNING", "row_count": 2500, "estimated_cost_cents": 25000 } ``` ## Polling for results Poll `GET /job-status/{task_id}` until `status` is `completed`, `failed`, `cancelled`, `terminated`, or `timed_out`. When the job completes, the response includes a `results` array of signed download links for the output files plus `charge_amount` in cents — bulk jobs deliver files, not an inline `result`. The `results` array contains one entry per pipeline stage; the final enriched output is the entry with the highest `block_number`. Small files also carry an inline `results` row preview alongside the link — use one or the other, not both. ### Tracking progress while running While `status` is `running`, a bulk response includes a `progress` object with the number of rows enriched so far out of the total submitted: ```json theme={null} { "status": "running", "id": "job_123", "run_id": "temporal-run-123", "start_time": "2026-06-10T00:00:00Z", "progress": { "completed": 120, "total": 500 } } ``` | Field | Type | Description | | - | - | - | | `completed` | integer | Rows enriched so far. | | `total` | integer | Total rows submitted in the job. | `progress` is present only on bulk runs while `status` is `running`. It is absent once the job reaches `completed`, `failed`, `cancelled`, `terminated`, or `timed_out`. Poll `/job-status` on an interval to refresh the count. Download links are signed URLs that expire after 15 minutes — poll again for fresh links — and require your `x-api-key` header when fetching. ```bash cURL theme={null} # Check status curl "https://api.sixtyfour.ai/job-status/TASK_ID" \ -H "x-api-key: YOUR_API_KEY" # When status is "completed", download the result (no redirects — pass your key directly) curl -o enriched.csv \ -H "x-api-key: YOUR_API_KEY" \ "SIGNED_DOWNLOAD_URL" ``` ```python Python theme={null} import requests import time deadline = time.monotonic() + 2 * 60 * 60 # give up after 2 hours while True: poll_resp = requests.get( f"https://api.sixtyfour.ai/job-status/{task_id}", headers={"x-api-key": "YOUR_API_KEY"} ) poll_resp.raise_for_status() status = poll_resp.json() if status["status"] == "completed": # The final stage has the highest block_number. If you requested NDJSON, # CSV and NDJSON share that block_number — pick by filename extension. final_block = max(f["block_number"] or 0 for f in status["results"]) suffix = ".csv" # use ".ndjson" if you requested NDJSON output final = next( (f for f in status["results"] if (f["block_number"] or 0) == final_block and f["filename"].endswith(suffix)), None, ) if final is None: raise RuntimeError(f"No {suffix} file in results") download = requests.get( final["download_url"], headers={"x-api-key": "YOUR_API_KEY"}, # Downloads are served directly by api.sixtyfour.ai; never # forward your API key across a redirect. allow_redirects=False ) if download.status_code != 200: raise RuntimeError(f"Download failed: {download.status_code}") with open(final["filename"], "wb") as out: out.write(download.content) break if status["status"] in ("failed", "cancelled", "terminated", "timed_out"): raise RuntimeError(f"Job {status['status']}: {status.get('error', 'Unknown error')}") if time.monotonic() > deadline: raise TimeoutError("Job didn't complete within 2 hours") time.sleep(30) ``` ```javascript JavaScript theme={null} const deadline = Date.now() + 2 * 60 * 60 * 1000; // give up after 2 hours while (true) { const pollResponse = await fetch( `https://api.sixtyfour.ai/job-status/${task_id}`, { headers: { "x-api-key": "YOUR_API_KEY" } } ); if (!pollResponse.ok) throw new Error(`Status check failed: ${pollResponse.status}`); const status = await pollResponse.json(); if (status.status === "completed") { // The final stage has the highest block_number. If you requested NDJSON, // CSV and NDJSON share that block_number — pick by filename extension. const finalBlock = Math.max(...status.results.map(f => f.block_number ?? 0)); const suffix = ".csv"; // use ".ndjson" if you requested NDJSON output const final = status.results.find( f => (f.block_number ?? 0) === finalBlock && f.filename.endsWith(suffix) ); if (!final) throw new Error(`No ${suffix} file in results`); // Downloads are served directly by api.sixtyfour.ai; redirect: "error" // ensures your API key is never forwarded across a redirect. const data = await fetch(final.download_url, { headers: { "x-api-key": "YOUR_API_KEY" }, redirect: "error" }); if (!data.ok) throw new Error(`Download failed: ${data.status}`); // write data to disk or stream it onward break; } if (["failed", "cancelled", "terminated", "timed_out"].includes(status.status)) { throw new Error(`Job ${status.status}: ${status.error || "Unknown error"}`); } if (Date.now() > deadline) { throw new Error("Job didn't complete within 2 hours"); } await new Promise(r => setTimeout(r, 30000)); } ``` For per-block progress while the job runs, use [Workflow Run Live Status](/api-reference/workflow/get-workflow-run-live-status). ## Webhook callback Pass a `webhook_url` in the config to receive results via HTTP POST when the job completes, instead of polling. The payload includes signed download URLs for the result files. The signed payload, retry behavior, and verification steps are documented in [Outgoing Webhooks](/api-reference/webhooks/outgoing). Webhook URLs must use HTTPS and resolve to a public address. The URL is validated when you submit and again at delivery time. ## Idempotency Send an `Idempotency-Key` header (up to 255 characters) to make submissions retry-safe. Resubmitting with the same key returns the original job instead of starting a new one: ```json theme={null} { "task_id": "9b1f9d2e-...", "status": "RUNNING", "already_started": true } ``` Idempotent replays don't count against the submission rate limit, and `row_count` and `estimated_cost_cents` are omitted, since the original upload is authoritative. ## Failure behavior Rows that fail enrichment pass through to the output un-enriched — a single hard row doesn't fail the job. If the uploaded file itself can't be read or parsed, the request fails with 400 before anything runs. # Company Intelligence Source: https://docs.sixtyfour.ai/api-reference/endpoint/company-intelligence Research and enrich company data with additional information and find associated people. ## Use case Research and enrich company data with additional firmographic information and find associated people. Use it for sales outreach, CRM enrichment, lead qualification, or investigative diligence. ## Endpoint ```http theme={null} POST https://api.sixtyfour.ai/company-intelligence ``` See the full request/response schema and parameters in the API Reference. ## Pricing See [Credits & Pricing Guide](/guides/credits-and-pricing) for credit costs by tier. ## Errors For error responses (400, 403, 422, etc.), see [Handling Errors](/api-reference/errors). ## Tiers The `tier` parameter controls research depth and cost. To override the default, pass `tier` in the request body. See [Sync usage](#sync-usage) for an example. | Tier | Description | Access | | - | - | - | | `micro` | Lightweight enrichment — returns up to 10 fields. Best for very high-volume lookups where only minimal firmographic data is needed. | Access is granted case-by-case by our team. | | `low` (default) | Baseline tier — fast and cheap. Good for high-volume firmographic enrichment. | All orgs | | `medium` | Deeper research — more fields filled, more sources. | All orgs | | `high` | **OSINT-grade investigation** — deeply recursive crawling across the open web, dark web, directories, proprietary sources. Designed for high-stakes accounts, sensitive diligence, and investigative workflows. | **Exclusive — access is granted case-by-case by our team. Contact sales to request.** | If `tier` is omitted, `low` is used. Requests with `tier: "micro"` or `tier: "high"` on an org without access return **403** — see [Handling Errors](/api-reference/errors#403-forbidden). ## Using the `struct` field The `struct` field defines exactly what data you want back. Each key becomes a field in `structured_data`, and its value tells the agent what to find. You can pass either a plain-English description or an object with `description` and `type`: ```json theme={null} { "struct": { "instagram_url": "Instagram url for the photography company", "num_employees": {"description": "Approximate employee count", "type": "int"} } } ``` The agent uses these descriptions to guide its research. Be specific — `"company's primary Instagram handle"` returns better results than `"social"`. For supported types, type resolution priority, and casting examples, see [Struct & Type Casting](/guides/struct-and-type-casting). ## Choosing related parameters ### `find_people` vs `full_org_chart` * **`find_people`** — Finds specific people associated with the company. Pair with `people_focus_prompt` to filter by role or seniority. * **`full_org_chart`** — Returns a compact view of employees grouped by department (capped per department). It's broad coverage of who works where, not a literal reporting tree or an exhaustive employee dump. When `true`, the response includes a top-level `org_chart` field. Use `find_people` for targeted lead discovery and `full_org_chart` for department-level visibility. ### `research_plan` vs `people_focus_prompt` * **`research_plan`** — Methodology. Tells the agent **where to look** (e.g., `"Check the 'About Us' page and LinkedIn Company People tab"`). * **`people_focus_prompt`** — Criteria. Tells the agent **who to find** (e.g., `"Find the VP of Marketing and the CTO"`). ## Timeouts and parallelization This endpoint performs deep research and is a long-running operation. Typical P95 runtime is about 5 minutes and can reach 10 minutes for complex companies. We are actively working on performance improvements. * **Set client timeouts appropriately**: If you call the sync endpoint, configure your HTTP client with a timeout of at least **15 minutes**. * **Prefer async in production**: Use `POST /company-intelligence-async` and poll `GET /job-status/{task_id}`. * **Parallelize for throughput**: Submit multiple async jobs in parallel (bounded concurrency) rather than waiting for each to complete sequentially. ## Understanding scores | Field | Meaning | Range | | - | - | - | | `confidence_score` | **Global quality score** — overall quality, consistency, and correctness of the returned data. | 0–10 | | `score` | **Relevance score** — likelihood that a returned person matches the prompt or is helpful. Returned per lead. | 0–10 | The `findings` field on the response is deprecated. It currently returns an empty list and will be removed in a future update. ## Sync usage Make a direct request and wait for the response. Enrichment can take several minutes depending on depth of research. ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/company-intelligence" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "target_company": { "company_name": "Pacific View Studios", "website": "https://pacificview.studio" }, "struct": { "instagram_url": "Instagram url for the photography company", "num_employees": "How many employees work there" }, "tier": "medium", "find_people": true }' ``` ```python Python theme={null} import requests response = requests.post( "https://api.sixtyfour.ai/company-intelligence", headers={ "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, json={ "target_company": { "company_name": "Pacific View Studios", "website": "https://pacificview.studio" }, "struct": { "instagram_url": "Instagram url for the photography company", "num_employees": "How many employees work there" }, "tier": "medium", "find_people": True }, timeout=900 ) response.raise_for_status() results = response.json() ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.sixtyfour.ai/company-intelligence", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ target_company: { company_name: "Pacific View Studios", website: "https://pacificview.studio" }, struct: { instagram_url: "Instagram url for the photography company", num_employees: "How many employees work there" }, tier: "medium", find_people: true }) }); const results = await response.json(); console.log(results); ``` ## Async pattern For production workflows, use `/company-intelligence-async` to submit a job and poll for results. This avoids long-lived HTTP connections during deep research runs. The flow is: 1. **Submit** — `POST /company-intelligence-async` with the same body as the sync endpoint. Response includes a `task_id`. 2. **Poll** — `GET /job-status/{task_id}` until `status` is `completed`, `failed`, `cancelled`, `terminated`, or `timed_out`. 3. **Read result** — When `completed`, the full enrichment is in the `result` field. The async start endpoint returns uppercase `RUNNING`. Subsequent `/job-status/{task_id}` calls return lowercase statuses. `charge_amount` is returned in cents, not credits. ### Polling example ```python Python theme={null} import requests import time response = requests.post( "https://api.sixtyfour.ai/company-intelligence-async", headers={"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"}, json={ "target_company": { "company_name": "Pacific View Studios", "website": "https://pacificview.studio" }, "struct": { "instagram_url": "Instagram url for the photography company", "num_employees": "How many employees work there" }, "tier": "medium", "find_people": True } ) response.raise_for_status() task_id = response.json()["task_id"] while True: status = requests.get( f"https://api.sixtyfour.ai/job-status/{task_id}", headers={"x-api-key": "YOUR_API_KEY"} ).json() if status["status"] == "completed": results = status["result"] break if status["status"] in ("failed", "cancelled", "terminated", "timed_out"): raise RuntimeError(f"Job {status['status']}: {status.get('error', 'Unknown error')}") time.sleep(10) ``` ```javascript JavaScript theme={null} async function runAsync() { const start = await fetch("https://api.sixtyfour.ai/company-intelligence-async", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ target_company: { company_name: "Pacific View Studios", website: "https://pacificview.studio" }, struct: { instagram_url: "Instagram url for the photography company", num_employees: "How many employees work there" }, tier: "medium", find_people: true }) }); const { task_id } = await start.json(); while (true) { const status = await (await fetch( `https://api.sixtyfour.ai/job-status/${task_id}`, { headers: { "x-api-key": "YOUR_API_KEY" } } )).json(); if (status.status === "completed") return status.result; if (["failed", "cancelled", "terminated", "timed_out"].includes(status.status)) { throw new Error(`Job ${status.status}: ${status.error || "Unknown error"}`); } await new Promise(r => setTimeout(r, 10000)); } } ``` # Confidence Scoring Source: https://docs.sixtyfour.ai/api-reference/endpoint/field-confidence Per-field confidence percentages for enrichment results. Understand exactly how reliable each piece of data is. Field-level confidence provides a 0–100% confidence score for each enrichment field individually, replacing the single lead-level score. The response indicates which fields are reliable and which require verification. Each score reflects how much assumption is required to connect the field value back to the target person. A high score means the evidence chain is strong; a low score means there's meaningful uncertainty about attribution. This feature is in early access. Contact [Sixtyfour Support](mailto:support@sixtyfour.ai) to gain access. ## How to enable Pass `field_confidence: true` in your `/people-intelligence` request body: ```json theme={null} { "lead_info": {"full_name": "Jane Smith", "company": "Acme Corp"}, "struct": { "email": "Email address", "title": "Current job title", "linkedin": "LinkedIn profile URL" }, "tier": "high", "field_confidence": true } ``` ## Response format When enabled, the response includes a `field_confidence` object that mirrors the shape of your `struct`. Each leaf field gets a confidence score (0–100) or `null` if no evidence path exists for that field. ```json theme={null} { "notes": "Research complete. Found Jane Smith on LinkedIn and Acme Corp website.", "structured_data": { "email": "jsmith@acmecorp.com", "title": "VP of Engineering", "linkedin": "https://www.linkedin.com/in/janesmith-acme/" }, "findings": [], "references": { "https://linkedin.com/in/janesmith-acme/": "LinkedIn profile" }, "field_confidence": { "email": 88, "title": 96, "linkedin": 98 } } ``` ### Response fields | Field | Type | Description | | - | - | - | | `field_confidence` | object | Confidence scores mirroring the struct shape. Only present when `field_confidence: true` is passed and the org has access. | | `field_confidence.{scalar_field}` | integer (0–100) or null | Confidence for a scalar leaf field. `null` means no evidence path exists. | | `field_confidence.{object_field}` | object | Same keys as the struct object; each subfield scored individually. | | `field_confidence.{list_field}` | array | One object per item in `structured_data`; each subfield scored individually. | When `field_confidence` is present in the response, `confidence_score` is not included. The two fields are mutually exclusive and use different scales: `field_confidence` scores are 0–100 per field, while `confidence_score` is 0–10 at the lead level. ### When field\_confidence is off The response includes a single `confidence_score` (0–10) at the lead level: ```json theme={null} { "structured_data": {...}, "confidence_score": 8 } ``` ## Nested fields (objects and object\_lists) For nested structs, `field_confidence` mirrors the exact shape of `structured_data`: ```json theme={null} { "struct": { "email": "Email address", "company": { "name": "Company name", "size": "Company size" }, "jobs": [ {"title": "Job title", "company": "Company name", "start_date": "Start date"} ] } } ``` Response: ```json theme={null} { "structured_data": { "email": "jsmith@acmecorp.com", "company": {"name": "Acme Corp", "size": "500"}, "jobs": [ {"title": "VP of Engineering", "company": "Acme Corp", "start_date": "2020"}, {"title": "Senior Engineer", "company": "Google", "start_date": "2015"} ] }, "field_confidence": { "email": 88, "company": { "name": 96, "size": 45 }, "jobs": [ {"title": 95, "company": 95, "start_date": 60}, {"title": 72, "company": 72, "start_date": null} ] } } ``` The `field_confidence` structure always matches `structured_data` recursively. See [Response fields](#response-fields) for the per-type contract. ## Workflow mode In workflows, field confidence is returned as a single `enrichment_field_confidence` column containing a JSON string. The column parses as standard JSON while keeping the CSV flat: | name | email | enrichment\_field\_confidence | | - | - | - | | Jane Smith | [jsmith@acme.com](mailto:jsmith@acme.com) | `{"email": 88, "title": 96, "linkedin": 98}` | For nested structs, the JSON column contains the full nested structure: ```json theme={null} {"email": 88, "company": {"name": 96, "size": 45}, "jobs": [{"title": 95, "company": 95, "start_date": 60}, {"title": 72, "company": 72, "start_date": null}]} ``` To use confidence in downstream workflow blocks, parse the JSON column and filter on the fields you care about. ## Confidence tiers The 0–100 score maps to named confidence levels: | Range | Level | What it means | | - | - | - | | 85–100% | Confirmed | Near-zero assumption. Unique identifiers, direct assertions from authoritative sources. | | 65–84% | Established | Minimal assumption. A careful person would rely on this. One small residual uncertainty. | | 40–64% | Supported | Moderate assumption. The identity or value link is reasonable but not tight. | | 15–39% | Speculative | Significant assumption. Weak identifiers, multiple possible matches. Treat as hypothesis. | | 0–14% | Unreliable | Major assumption or conflicting evidence. Do not rely on. | ## Usage tips * Parse in code: the `enrichment_field_confidence` column (workflow) and `field_confidence` field (API) are both standard JSON. Use `JSON.parse()` or `json.loads()` to extract individual scores. * Threshold by use case: cold email outreach typically accepts 65%+. Legal and compliance work should require 85%+. * The weakest link dominates: if the target has a very common name, even well-sourced fields score lower because the identity link is uncertain. * Null means no evidence: a `null` value means no evidence chain exists in the attribution graph — either the value wasn't found, or it was provided without traceable sourcing. The key is always present in `field_confidence` (the shape mirrors `structured_data` exactly). # Find Email Source: https://docs.sixtyfour.ai/api-reference/endpoint/find-email Find email addresses for people. ## Use case Discover professional or personal email addresses for leads for sales outreach, CRM enrichment, or lead qualification. ## Endpoint ```http theme={null} POST https://api.sixtyfour.ai/find-email ``` See the full request/response schema and parameters in the API Reference. ## Pricing See [Credits & Pricing Guide](/guides/credits-and-pricing) for credit costs. ## Errors For error responses (400, 403, 422, etc.), see [Handling Errors](/api-reference/errors). ## Email modes The `mode` parameter controls which type of email the API returns. Defaults to `PROFESSIONAL` if omitted. | Mode | Description | Returned in | | - | - | - | | `PROFESSIONAL` (default) | Discovers company emails tied to the lead's employer domain. | `email` and `other_emails` fields | | `PERSONAL` | Discovers personal emails (e.g., Gmail, Yahoo). The `email` field still returns the company email when found. | `personal_email` and `personal_other_emails` fields | If the input `lead` already includes an `email` field, the API returns it unchanged with appropriate status and type. In `PERSONAL` mode, if the existing email is a personal email, the API returns immediately without additional processing or cost. ## Response format The `email` and `personal_email` fields contain a list of tuples. Each tuple consists of: * **Email address** (string) * **Validation status** (string, always uppercase): * `OK` — The email address has been validated and is likely deliverable. * `RISKY` — The address may work but could bounce, typically because the domain accepts every address (catch-all). * `UNKNOWN` — Validation was inconclusive. * `NOT_FOUND` — No email could be discovered for this lead. * **Email type** (string, always uppercase): * `COMPANY` — Tied to the company domain. * `PERSONAL` — A personal email address (e.g., Gmail, Yahoo). ### Other emails A lead can have more than one address. With `verify_emails` on (the default), we check every address a provider returns and put the first deliverable one in `email` (or `personal_email`). If none is deliverable, you get the first one that did not fail validation, so always read its status. The rest are listed in `other_emails` (or `personal_other_emails`) with their own status, so you can fall back to them: ```json theme={null} { "personal_email": [["sarah.chen@gmail.com", "OK", "PERSONAL"]], "personal_other_emails": [ { "email": "sarahchen@yahoo.com", "status": "RISKY" } ] } ``` With `verify_emails: false` nothing is checked: you get the provider's first address, and the other-emails list is empty. * Addresses that fail validation are left out. * In `PERSONAL` mode, addresses at the lead's employer are never listed. * The field is an empty list when there are no other addresses. ## Sync usage ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/find-email" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "lead": { "name": "Sarah Chen", "company": "Pacific View Studios", "title": "Creative Director & Lead Photographer", "linkedin": "https://www.linkedin.com/in/sarah-chen-photography" }, "mode": "PERSONAL" }' ``` ```python Python theme={null} import requests response = requests.post( "https://api.sixtyfour.ai/find-email", headers={ "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, json={ "lead": { "name": "Sarah Chen", "company": "Pacific View Studios", "title": "Creative Director & Lead Photographer", "phone": "+16195551234", "linkedin": "https://www.linkedin.com/in/sarah-chen-photography" }, "mode": "PERSONAL" } ) response.raise_for_status() results = response.json() ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.sixtyfour.ai/find-email", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ lead: { name: "Sarah Chen", company: "Pacific View Studios", title: "Creative Director & Lead Photographer", phone: "+16195551234", linkedin: "https://www.linkedin.com/in/sarah-chen-photography" }, mode: "PERSONAL" }) }); const results = await response.json(); console.log(results); ``` ## Async pattern For production workflows, use `/find-email-async` to submit a job and poll for results. This avoids long-lived HTTP connections and lets you parallelize many lookups without blocking your client. The flow is: 1. **Submit** — `POST /find-email-async` with the same body as the sync endpoint. Response includes a `task_id`. 2. **Poll** — `GET /job-status/{task_id}` until `status` is `completed`, `failed`, `cancelled`, `terminated`, or `timed_out`. 3. **Read result** — When `completed`, the discovered emails are in the `result` field, in the same shape as the sync response. The async start endpoint returns uppercase `RUNNING`. Subsequent `/job-status/{task_id}` calls return lowercase statuses. `charge_amount` is returned in cents, not credits. ### Polling example ```python Python theme={null} import requests import time response = requests.post( "https://api.sixtyfour.ai/find-email-async", headers={"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"}, json={ "lead": { "name": "Sarah Chen", "company": "Pacific View Studios", "linkedin": "https://www.linkedin.com/in/sarah-chen-photography" }, "mode": "PROFESSIONAL" } ) response.raise_for_status() task_id = response.json()["task_id"] while True: status = requests.get( f"https://api.sixtyfour.ai/job-status/{task_id}", headers={"x-api-key": "YOUR_API_KEY"} ).json() if status["status"] == "completed": results = status["result"] break if status["status"] in ("failed", "cancelled", "terminated", "timed_out"): raise RuntimeError(f"Job {status['status']}: {status.get('error', 'Unknown error')}") time.sleep(5) ``` ```javascript JavaScript theme={null} async function runAsync() { const start = await fetch("https://api.sixtyfour.ai/find-email-async", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ lead: { name: "Sarah Chen", company: "Pacific View Studios", linkedin: "https://www.linkedin.com/in/sarah-chen-photography" }, mode: "PROFESSIONAL" }) }); if (!start.ok) throw new Error(`Submit failed: ${start.status}`); const { task_id } = await start.json(); while (true) { const pollRes = await fetch( `https://api.sixtyfour.ai/job-status/${task_id}`, { headers: { "x-api-key": "YOUR_API_KEY" } } ); if (!pollRes.ok) throw new Error(`Poll failed: ${pollRes.status}`); const status = await pollRes.json(); if (status.status === "completed") return status.result; if (["failed", "cancelled", "terminated", "timed_out"].includes(status.status)) { throw new Error(`Job ${status.status}: ${status.error || "Unknown error"}`); } await new Promise(r => setTimeout(r, 5000)); } } ``` ### Webhook callback Pass a `webhook_url` to receive the result via HTTP POST instead of polling. The signed payload, retry behavior, and verification steps are documented in [Outgoing Webhooks](/api-reference/webhooks/outgoing). ```json theme={null} POST /find-email-async { "lead": { "name": "Sarah Chen", "company": "Pacific View Studios", "linkedin": "https://www.linkedin.com/in/sarah-chen-photography" }, "webhook_url": "https://your-server.com/webhooks/sixtyfour" } ``` ## Bulk processing Use `/find-email-bulk` (sync) or `/find-email-bulk-async` (async) to process up to 100 leads in a single call. Both accept a `leads` array and the same `mode`, `verify_emails`, and `webhook_url` fields as the single-lead endpoints. ### Bulk sync Returns 200 OK with results once every lead is processed. Best for small batches where you want a single round-trip. ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/find-email-bulk" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "leads": [ { "name": "Sarah Chen", "company": "Pacific View Studios", "linkedin": "https://www.linkedin.com/in/sarah-chen-photography" }, { "name": "John Doe", "company": "Example Corp", "linkedin": "https://linkedin.com/in/johndoe" } ], "mode": "PROFESSIONAL" }' ``` ```python Python theme={null} import requests response = requests.post( "https://api.sixtyfour.ai/find-email-bulk", headers={"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"}, json={ "leads": [ { "name": "Sarah Chen", "company": "Pacific View Studios", "linkedin": "https://www.linkedin.com/in/sarah-chen-photography" }, { "name": "John Doe", "company": "Example Corp", "linkedin": "https://linkedin.com/in/johndoe" } ], "mode": "PROFESSIONAL" } ) response.raise_for_status() results = response.json() ``` ### Bulk async Submit a batch, get back a `task_id`, then poll `/job-status/{task_id}` (or set `webhook_url` to receive a callback). Recommended for batches of more than a handful of leads, or when you don't want to hold a long-lived HTTP connection. ```python Python theme={null} import requests import time response = requests.post( "https://api.sixtyfour.ai/find-email-bulk-async", headers={"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"}, json={ "leads": [ { "name": "Sarah Chen", "company": "Pacific View Studios", "linkedin": "https://www.linkedin.com/in/sarah-chen-photography" }, { "name": "John Doe", "company": "Example Corp", "linkedin": "https://linkedin.com/in/johndoe" } ], "mode": "PROFESSIONAL" } ) response.raise_for_status() task_id = response.json()["task_id"] while True: status = requests.get( f"https://api.sixtyfour.ai/job-status/{task_id}", headers={"x-api-key": "YOUR_API_KEY"} ).json() if status["status"] == "completed": results = status["result"] break if status["status"] in ("failed", "cancelled", "terminated", "timed_out"): raise RuntimeError(f"Job {status['status']}: {status.get('error', 'Unknown error')}") time.sleep(10) ``` # Find Phone Source: https://docs.sixtyfour.ai/api-reference/endpoint/find-phone Find phone numbers for people. ## Use case Discover phone numbers for leads for sales outreach, CRM enrichment, or lead qualification. ## Endpoint ```http theme={null} POST https://api.sixtyfour.ai/find-phone ``` See the full request/response schema and parameters in the API Reference. ## Pricing See [Credits & Pricing Guide](/guides/credits-and-pricing) for credit costs. ## Errors For error responses (400, 403, 422, etc.), see [Handling Errors](/api-reference/errors). ## Sync usage ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/find-phone" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "lead": { "name": "Sarah Johnson", "company": "TechCorp Inc", "linkedin_url": "https://linkedin.com/in/sarahjohnson" } }' ``` ```python Python theme={null} import requests response = requests.post( "https://api.sixtyfour.ai/find-phone", headers={ "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, json={ "lead": { "name": "Sarah Johnson", "company": "TechCorp Inc", "linkedin_url": "https://linkedin.com/in/sarahjohnson" } } ) response.raise_for_status() result = response.json() ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.sixtyfour.ai/find-phone", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ lead: { name: "Sarah Johnson", company: "TechCorp Inc", linkedin_url: "https://linkedin.com/in/sarahjohnson" } }) }); const result = await response.json(); ``` ## Async pattern For production workflows, use `/find-phone-async` to submit a job and poll for results. This avoids long-lived HTTP connections and lets you parallelize many lookups without blocking your client. The flow is: 1. **Submit** — `POST /find-phone-async` with the same body as the sync endpoint. Response includes a `task_id`. 2. **Poll** — `GET /job-status/{task_id}` until `status` is `completed`, `failed`, `cancelled`, `terminated`, or `timed_out`. 3. **Read result** — When `completed`, the discovered phone numbers are in the `result` field, in the same shape as the sync response. The async start endpoint returns uppercase `RUNNING`. Subsequent `/job-status/{task_id}` calls return lowercase statuses. `charge_amount` is returned in cents, not credits. ### Polling example ```python Python theme={null} import requests import time response = requests.post( "https://api.sixtyfour.ai/find-phone-async", headers={"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"}, json={ "lead": { "name": "Sarah Johnson", "company": "TechCorp Inc", "linkedin_url": "https://linkedin.com/in/sarahjohnson" } } ) response.raise_for_status() task_id = response.json()["task_id"] while True: status = requests.get( f"https://api.sixtyfour.ai/job-status/{task_id}", headers={"x-api-key": "YOUR_API_KEY"} ).json() if status["status"] == "completed": results = status["result"] break if status["status"] in ("failed", "cancelled", "terminated", "timed_out"): raise RuntimeError(f"Job {status['status']}: {status.get('error', 'Unknown error')}") time.sleep(5) ``` ```javascript JavaScript theme={null} async function runAsync() { const start = await fetch("https://api.sixtyfour.ai/find-phone-async", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ lead: { name: "Sarah Johnson", company: "TechCorp Inc", linkedin_url: "https://linkedin.com/in/sarahjohnson" } }) }); if (!start.ok) throw new Error(`Submit failed: ${start.status}`); const { task_id } = await start.json(); while (true) { const pollRes = await fetch( `https://api.sixtyfour.ai/job-status/${task_id}`, { headers: { "x-api-key": "YOUR_API_KEY" } } ); if (!pollRes.ok) throw new Error(`Poll failed: ${pollRes.status}`); const status = await pollRes.json(); if (status.status === "completed") return status.result; if (["failed", "cancelled", "terminated", "timed_out"].includes(status.status)) { throw new Error(`Job ${status.status}: ${status.error || "Unknown error"}`); } await new Promise(r => setTimeout(r, 5000)); } } ``` ### Webhook callback Pass a `webhook_url` to receive the result via HTTP POST instead of polling. The signed payload, retry behavior, and verification steps are documented in [Outgoing Webhooks](/api-reference/webhooks/outgoing). ```json theme={null} POST /find-phone-async { "lead": { "name": "Sarah Johnson", "company": "TechCorp Inc", "linkedin_url": "https://linkedin.com/in/sarahjohnson" }, "webhook_url": "https://your-server.com/webhooks/sixtyfour" } ``` ## Bulk processing Use `/find-phone-bulk` (sync) or `/find-phone-bulk-async` (async) to process up to 100 leads in a single call. Both accept a `leads` array and the same `webhook_url` field as the single-lead endpoints. ### Bulk sync Returns 200 OK with results once every lead is processed. Best for small batches where you want a single round-trip. ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/find-phone-bulk" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "leads": [ { "name": "Sarah Johnson", "company": "TechCorp Inc", "linkedin_url": "https://linkedin.com/in/sarahjohnson" }, { "name": "John Doe", "company": "Example Corp", "linkedin_url": "https://linkedin.com/in/johndoe" } ] }' ``` ```python Python theme={null} import requests response = requests.post( "https://api.sixtyfour.ai/find-phone-bulk", headers={"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"}, json={ "leads": [ { "name": "Sarah Johnson", "company": "TechCorp Inc", "linkedin_url": "https://linkedin.com/in/sarahjohnson" }, { "name": "John Doe", "company": "Example Corp", "linkedin_url": "https://linkedin.com/in/johndoe" } ] } ) response.raise_for_status() results = response.json() ``` ### Bulk async Submit a batch, get back a `task_id`, then poll `/job-status/{task_id}` (or set `webhook_url` to receive a callback). Recommended for batches of more than a handful of leads, or when you don't want to hold a long-lived HTTP connection. ```python Python theme={null} import requests import time response = requests.post( "https://api.sixtyfour.ai/find-phone-bulk-async", headers={"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"}, json={ "leads": [ { "name": "Sarah Johnson", "company": "TechCorp Inc", "linkedin_url": "https://linkedin.com/in/sarahjohnson" }, { "name": "John Doe", "company": "Example Corp", "linkedin_url": "https://linkedin.com/in/johndoe" } ] } ) response.raise_for_status() task_id = response.json()["task_id"] while True: status = requests.get( f"https://api.sixtyfour.ai/job-status/{task_id}", headers={"x-api-key": "YOUR_API_KEY"} ).json() if status["status"] == "completed": results = status["result"] break if status["status"] in ("failed", "cancelled", "terminated", "timed_out"): raise RuntimeError(f"Job {status['status']}: {status.get('error', 'Unknown error')}") time.sleep(10) ``` # People Intelligence Source: https://docs.sixtyfour.ai/api-reference/endpoint/people-intelligence Research and enrich people profiles with contact information, social profiles, and company details. ## Use case Turn partial lead records into full contact profiles for sales outreach, CRM enrichment, or lead qualification. This endpoint is also available at `/enrich-lead` and `/enrich-lead-async`. Both paths are fully supported and functionally identical. ## Endpoint ```http theme={null} POST https://api.sixtyfour.ai/people-intelligence ``` See the full request/response schema and parameters in the API Reference. ## Pricing See [Credits & Pricing Guide](/guides/credits-and-pricing) for credit costs by tier. ## Errors For error responses (400, 403, 422, etc.), see [Handling Errors](/api-reference/errors). ## Tiers The `tier` parameter controls research depth and cost. To override the default, pass `tier` in the request body. See [Sync usage](#sync-usage) for an example. | Tier | Description | Access | | - | - | - | | `micro` | Lightweight enrichment — returns up to 10 fields. Best for very high-volume lookups where only minimal data is needed. | Access is granted case-by-case by our team. | | `low` (default) | Baseline tier — fast and cheap. Good for high-volume enrichment where you mostly need a name → email/LinkedIn mapping. | All orgs | | `medium` | Deeper research with more sources and iterations. Better for hard-to-find contacts. | All orgs | | `high` | **OSINT-grade investigation** — deeply recursive crawling across the open web, dark web, directories, proprietary sources. Designed for the hardest prospects, sensitive diligence, and investigative workflows. | **Exclusive — access is granted case-by-case by our team. Contact sales to request.** | | `xhigh` | **Maximum-depth OSINT investigation** — extends `high` with additional research cycles for the hardest identity and diligence cases. | **Exclusive — access is granted case-by-case by our team. Contact sales to request.** | If `tier` is omitted, `low` is used. Requests with `tier: "micro"`, `tier: "high"`, or `tier: "xhigh"` on an org without access return **403** — see [Handling Errors](/api-reference/errors#403-forbidden). ## Using the `struct` field The `struct` field defines exactly what data you want back. Each key becomes a field in `structured_data`, and its value tells the agent what to find. You can pass either a plain-English description or an object with `description` and `type`: ```json theme={null} { "struct": { "email": "The individual's email address", "github_url": {"description": "URL for their GitHub profile", "type": "str"} } } ``` The agent uses these descriptions to guide its research. Be specific — `"The individual's primary work email"` returns better results than `"email"`. For supported types, type resolution priority, and casting examples, see [Struct & Type Casting](/guides/struct-and-type-casting). ### Image fields On the `high` and `xhigh` tiers, a field that asks for an image (a headshot, a logo, a screenshot of a page) comes back as a permanent image URL hosted by Sixtyfour: ```json theme={null} {"struct": {"headshot": "A recent photo of the person"}} ``` ```json theme={null} {"structured_data": {"headshot": "https://api.sixtyfour.ai/v1/media/v1.eyJt..."}} ``` The URL is unguessable, needs no credentials, and stays valid while your data is retained — embed it directly in an ``, CRM record, or spreadsheet cell. The source page is listed in `references`. Exception: organizations with customer-managed encryption must fetch media URLs with their API key (`x-api-key` header) from a server, so they cannot be embedded directly. Page screenshots show the full source URL and the capture time (UTC) in a header above the page. For a printable version, ask for a PDF in the field description, for example `"Printable PDF capture of the source page"`. It comes back as the same kind of media URL, served as a US Letter PDF with the URL, capture time and page number on every page. People search returns LinkedIn profile photos as a different kind of permanent URL, `https://api.sixtyfour.ai/v1/media/profiles/{key}`. See [Photo and logo URLs](/api-reference/search/filter-search#photo-and-logo-urls). ## Timeouts and parallelization This endpoint performs deep research and is a long-running operation. Typical P95 runtime is about 5 minutes and can reach 10 minutes for complex leads. We are actively working on performance improvements. * **Set client timeouts appropriately**: If you call the sync endpoint, configure your HTTP client with a timeout of at least **15 minutes**. * **Prefer async in production**: Use `POST /people-intelligence-async` and poll `GET /job-status/{task_id}`. * **Parallelize for throughput**: Submit multiple async jobs in parallel (bounded concurrency) rather than waiting for each to complete sequentially. ## Understanding scores | Field | Meaning | Range | | - | - | - | | `confidence_score` | **Global quality score** — overall quality, consistency, and correctness of the returned data. | 0–10 | The `findings` field on the response is deprecated. It currently returns an empty list and will be removed in a future update. ## Sync usage Make a direct request and wait for the response. Enrichment can take several minutes depending on depth of research. ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/people-intelligence" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "lead_info": { "name": "Saarth Shah", "title": "CEO & Co-Founder @ Sixtyfour AI", "company": "Sixtyfour AI", "location": "San Francisco", "linkedin": "https://www.linkedin.com/in/saarthshah" }, "struct": { "name": "The individual'\''s full name", "email": "The individual'\''s email address", "phone": "The individual'\''s phone number", "company": "The company the individual is associated with", "title": "The individual'\''s job title", "linkedin": "LinkedIn URL for the person", "website": "Company website URL", "location": "The individual'\''s location and/or company location", "industry": "Industry the person operates in", "github_url": "url for their github profile", "github_notes": "Take detailed notes on their github profile." }, "tier": "medium" }' ``` ```python Python theme={null} import requests response = requests.post( "https://api.sixtyfour.ai/people-intelligence", headers={ "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, json={ "lead_info": { "name": "Saarth Shah", "title": "CEO & Co-Founder @ Sixtyfour AI", "company": "Sixtyfour AI", "location": "San Francisco", "linkedin": "https://www.linkedin.com/in/saarthshah" }, "struct": { "name": "The individual's full name", "email": "The individual's email address", "phone": "The individual's phone number", "company": "The company the individual is associated with", "title": "The individual's job title", "linkedin": "LinkedIn URL for the person", "website": "Company website URL", "location": "The individual's location and/or company location", "industry": "Industry the person operates in", "github_url": "url for their github profile", "github_notes": "Take detailed notes on their github profile." }, "tier": "medium" }, timeout=900 ) response.raise_for_status() results = response.json() ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.sixtyfour.ai/people-intelligence", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ lead_info: { name: "Saarth Shah", title: "CEO & Co-Founder @ Sixtyfour AI", company: "Sixtyfour AI", location: "San Francisco", linkedin: "https://www.linkedin.com/in/saarthshah" }, struct: { name: "The individual's full name", email: "The individual's email address", phone: "The individual's phone number", company: "The company the individual is associated with", title: "The individual's job title", linkedin: "LinkedIn URL for the person", website: "Company website URL", location: "The individual's location and/or company location", industry: "Industry the person operates in", github_url: "url for their github profile", github_notes: "Take detailed notes on their github profile." }, tier: "medium" }) }); const results = await response.json(); console.log(results); ``` ## Async pattern For production workflows, use `/people-intelligence-async` to submit a job and poll for results. This avoids long-lived HTTP connections during deep research runs. The flow is: 1. **Submit** — `POST /people-intelligence-async` with the same body as the sync endpoint. Response includes a `task_id`. 2. **Poll** — `GET /job-status/{task_id}` until `status` is `completed`, `failed`, `cancelled`, `terminated`, or `timed_out`. 3. **Read result** — When `completed`, the full enrichment is in the `result` field. The async start endpoint returns uppercase `RUNNING`. Subsequent `/job-status/{task_id}` calls return lowercase statuses. `charge_amount` is returned in cents, not credits. ### Polling example ```python Python theme={null} import requests import time response = requests.post( "https://api.sixtyfour.ai/people-intelligence-async", headers={"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"}, json={ "lead_info": { "name": "Saarth Shah", "title": "CEO & Co-Founder @ Sixtyfour AI", "company": "Sixtyfour AI", "location": "San Francisco", "linkedin": "https://www.linkedin.com/in/saarthshah" }, "struct": { "name": "The individual's full name", "email": "The individual's email address", "phone": "The individual's phone number", "company": "The company the individual is associated with", "title": "The individual's job title", "linkedin": "LinkedIn URL for the person", "website": "Company website URL", "location": "The individual's location and/or company location", "industry": "Industry the person operates in" }, "tier": "medium" } ) response.raise_for_status() task_id = response.json()["task_id"] while True: status = requests.get( f"https://api.sixtyfour.ai/job-status/{task_id}", headers={"x-api-key": "YOUR_API_KEY"} ).json() if status["status"] == "completed": results = status["result"] break if status["status"] in ("failed", "cancelled", "terminated", "timed_out"): raise RuntimeError(f"Job {status['status']}: {status.get('error', 'Unknown error')}") time.sleep(10) ``` ```javascript JavaScript theme={null} async function runAsync() { const start = await fetch("https://api.sixtyfour.ai/people-intelligence-async", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ lead_info: { name: "Saarth Shah", title: "CEO & Co-Founder @ Sixtyfour AI", company: "Sixtyfour AI", location: "San Francisco", linkedin: "https://www.linkedin.com/in/saarthshah" }, struct: { name: "The individual's full name", email: "The individual's email address", phone: "The individual's phone number", company: "The company the individual is associated with", title: "The individual's job title", linkedin: "LinkedIn URL for the person", website: "Company website URL", location: "The individual's location and/or company location", industry: "Industry the person operates in" }, tier: "medium" }) }); const { task_id } = await start.json(); while (true) { const status = await (await fetch( `https://api.sixtyfour.ai/job-status/${task_id}`, { headers: { "x-api-key": "YOUR_API_KEY" } } )).json(); if (status.status === "completed") return status.result; if (["failed", "cancelled", "terminated", "timed_out"].includes(status.status)) { throw new Error(`Job ${status.status}: ${status.error || "Unknown error"}`); } await new Promise(r => setTimeout(r, 10000)); } } ``` # QA Agent Source: https://docs.sixtyfour.ai/api-reference/endpoint/qa-agent Evaluate and qualify data against predefined criteria using autonomous research and structured analysis. This endpoint is currently in beta. The API response format and behavior may change in future releases. ## Use case Evaluate and qualify data against predefined criteria using autonomous research and structured analysis for sales outreach, CRM enrichment, or lead qualification. ## Endpoint ```http theme={null} POST https://api.sixtyfour.ai/qa-agent ``` See the full request/response schema and parameters in the API Reference. ## Pricing See [Credits & Pricing Guide](/guides/credits-and-pricing) for credit costs. ## Errors For error responses (400, 403, 422, etc.), see [Handling Errors](/api-reference/errors). ## Using the `struct` field The `struct` field controls which fields appear in the response's `structured_data`. Each key becomes a field in `structured_data`, and its value tells the agent what to find. **Only fields specified in `struct` are included in the response.** Each value must be a plain-English string description: ```json theme={null} { "struct": { "suitability_score": "Overall qualification score (0-10)", "final_verdict": "Clear YES/NO decision" } } ``` The agent uses these descriptions to guide its evaluation. Be specific — `"Overall qualification score from 0-10 based on weighted criteria"` returns better results than `"score"`. QA Agent returns these extra fields as strings. It does not accept typed `struct` objects. ## Parameters ### `qualification_criteria` Each criteria object requires: * `criteria_name` — Unique identifier for the criteria. * `description` — Detailed description of what to evaluate. * `weight` — Importance multiplier (higher = more important). * `threshold` — Optional minimum score requirement. ### `references` Supported URL types for automatic research: * LinkedIn member profiles (`https://linkedin.com/in/...`) * LinkedIn company profiles (`https://linkedin.com/company/...`) * Instagram profiles (`https://instagram.com/...`) * General web pages ## Example usage ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/qa-agent" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "data": { "name": "Alex Johnson", "country": "United Kingdom", "business_type": "e-commerce seller", "follower_count": "2500" }, "qualification_criteria": [ { "criteria_name": "Physical Inventory", "description": "Must own and store physical products", "weight": 10.0, "threshold": 8.0 } ], "struct": { "suitability_score": "Overall score (0-10)", "final_verdict": "YES/NO decision" } }' ``` ```python Python theme={null} import requests response = requests.post( "https://api.sixtyfour.ai/qa-agent", headers={ "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, json={ "data": { "name": "Alex Johnson", "country": "United Kingdom", "business_type": "e-commerce seller", "follower_count": "2500" }, "qualification_criteria": [ { "criteria_name": "Physical Inventory", "description": "Must own and store physical products", "weight": 10.0, "threshold": 8.0 } ], "struct": { "suitability_score": "Overall score (0-10)", "final_verdict": "YES/NO decision" } } ) response.raise_for_status() results = response.json() ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.sixtyfour.ai/qa-agent", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ data: { name: "Alex Johnson", country: "United Kingdom", business_type: "e-commerce seller", follower_count: "2500" }, qualification_criteria: [ { criteria_name: "Physical Inventory", description: "Must own and store physical products", weight: 10.0, threshold: 8.0 } ], struct: { suitability_score: "Overall score (0-10)", final_verdict: "YES/NO decision" } }) }); const results = await response.json(); console.log(results); ``` # Reverse Email Source: https://docs.sixtyfour.ai/api-reference/endpoint/reverse-email Find a person from their email address. ## Use case Identify the person behind an email address — name, employer, title, LinkedIn, and other contact details. Use it to enrich inbound signups, qualify support tickets, deduplicate CRM records, or investigate unknown senders. ## Endpoint ```http theme={null} POST https://api.sixtyfour.ai/reverse-email ``` See the full request/response schema and parameters in the API Reference. ## Pricing See [Credits & Pricing Guide](/guides/credits-and-pricing) for credit costs. ## Errors For error responses (400, 403, 422, etc.), see [Handling Errors](/api-reference/errors). ## Sync usage ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/reverse-email" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "lead": { "email": "sarah.johnson@example.com" } }' ``` ```python Python theme={null} import requests response = requests.post( "https://api.sixtyfour.ai/reverse-email", headers={ "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, json={ "lead": { "email": "sarah.johnson@example.com" } } ) response.raise_for_status() result = response.json() ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.sixtyfour.ai/reverse-email", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ lead: { email: "sarah.johnson@example.com" } }) }); const result = await response.json(); ``` ## Async pattern For production workflows, use `/reverse-email-async` to submit a job and poll for results. This avoids long-lived HTTP connections and lets you parallelize many lookups without blocking your client. The flow is: 1. **Submit** — `POST /reverse-email-async` with the same body as the sync endpoint. Response includes a `task_id`. 2. **Poll** — `GET /job-status/{task_id}` until `status` is `completed`, `failed`, `cancelled`, `terminated`, or `timed_out`. 3. **Read result** — When `completed`, the resolved person record is in the `result` field, in the same shape as the sync response. The async start endpoint returns uppercase `RUNNING`. Subsequent `/job-status/{task_id}` calls return lowercase statuses. `charge_amount` is returned in cents, not credits. ### Polling example ```python Python theme={null} import requests import time response = requests.post( "https://api.sixtyfour.ai/reverse-email-async", headers={"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"}, json={ "lead": { "email": "sarah.johnson@example.com" } } ) response.raise_for_status() task_id = response.json()["task_id"] while True: status = requests.get( f"https://api.sixtyfour.ai/job-status/{task_id}", headers={"x-api-key": "YOUR_API_KEY"} ).json() if status["status"] == "completed": results = status["result"] break if status["status"] in ("failed", "cancelled", "terminated", "timed_out"): raise RuntimeError(f"Job {status['status']}: {status.get('error', 'Unknown error')}") time.sleep(5) ``` ```javascript JavaScript theme={null} async function runAsync() { const start = await fetch("https://api.sixtyfour.ai/reverse-email-async", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ lead: { email: "sarah.johnson@example.com" } }) }); if (!start.ok) throw new Error(`Submit failed: ${start.status}`); const { task_id } = await start.json(); while (true) { const pollRes = await fetch( `https://api.sixtyfour.ai/job-status/${task_id}`, { headers: { "x-api-key": "YOUR_API_KEY" } } ); if (!pollRes.ok) throw new Error(`Poll failed: ${pollRes.status}`); const status = await pollRes.json(); if (status.status === "completed") return status.result; if (["failed", "cancelled", "terminated", "timed_out"].includes(status.status)) { throw new Error(`Job ${status.status}: ${status.error || "Unknown error"}`); } await new Promise(r => setTimeout(r, 5000)); } } ``` ### Webhook callback Pass a `webhook_url` to receive the result via HTTP POST instead of polling. The signed payload, retry behavior, and verification steps are documented in [Outgoing Webhooks](/api-reference/webhooks/outgoing). ```json theme={null} POST /reverse-email-async { "lead": { "email": "sarah.johnson@example.com" }, "webhook_url": "https://your-server.com/webhooks/sixtyfour" } ``` ## Bulk processing Use `/reverse-email-bulk` (sync) or `/reverse-email-bulk-async` (async) to resolve up to 100 email addresses in a single call. Both accept a `leads` array and the same `webhook_url` field as the single-lead endpoints. ### Bulk sync Returns 200 OK with results once every lead is processed. Best for small batches where you want a single round-trip. ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/reverse-email-bulk" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "leads": [ { "email": "sarah.johnson@example.com" }, { "email": "john.doe@acme.com" } ] }' ``` ```python Python theme={null} import requests response = requests.post( "https://api.sixtyfour.ai/reverse-email-bulk", headers={"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"}, json={ "leads": [ {"email": "sarah.johnson@example.com"}, {"email": "john.doe@acme.com"} ] } ) response.raise_for_status() results = response.json() ``` ### Bulk async Submit a batch, get back a `task_id`, then poll `/job-status/{task_id}` (or set `webhook_url` to receive a callback). Recommended for batches of more than a handful of leads, or when you don't want to hold a long-lived HTTP connection. ```python Python theme={null} import requests import time response = requests.post( "https://api.sixtyfour.ai/reverse-email-bulk-async", headers={"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"}, json={ "leads": [ {"email": "sarah.johnson@example.com"}, {"email": "john.doe@acme.com"} ] } ) response.raise_for_status() task_id = response.json()["task_id"] while True: status = requests.get( f"https://api.sixtyfour.ai/job-status/{task_id}", headers={"x-api-key": "YOUR_API_KEY"} ).json() if status["status"] == "completed": results = status["result"] break if status["status"] in ("failed", "cancelled", "terminated", "timed_out"): raise RuntimeError(f"Job {status['status']}: {status.get('error', 'Unknown error')}") time.sleep(10) ``` # Reverse Phone Source: https://docs.sixtyfour.ai/api-reference/endpoint/reverse-phone Find a person from their phone number. ## Use case Identify the person behind a phone number — name, employer, title, LinkedIn, and other contact details. Use it to enrich inbound calls, qualify form submissions, deduplicate CRM records, or investigate unknown numbers. ## Endpoint ```http theme={null} POST https://api.sixtyfour.ai/reverse-phone ``` See the full request/response schema and parameters in the API Reference. ## Pricing See [Credits & Pricing Guide](/guides/credits-and-pricing) for credit costs. ## Errors For error responses (400, 403, 422, etc.), see [Handling Errors](/api-reference/errors). ## Sync usage ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/reverse-phone" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "lead": { "phone": "+14155551234" } }' ``` ```python Python theme={null} import requests response = requests.post( "https://api.sixtyfour.ai/reverse-phone", headers={ "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, json={ "lead": { "phone": "+14155551234" } } ) response.raise_for_status() result = response.json() ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.sixtyfour.ai/reverse-phone", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ lead: { phone: "+14155551234" } }) }); const result = await response.json(); ``` ## Async pattern For production workflows, use `/reverse-phone-async` to submit a job and poll for results. This avoids long-lived HTTP connections and lets you parallelize many lookups without blocking your client. The flow is: 1. **Submit** — `POST /reverse-phone-async` with the same body as the sync endpoint. Response includes a `task_id`. 2. **Poll** — `GET /job-status/{task_id}` until `status` is `completed`, `failed`, `cancelled`, `terminated`, or `timed_out`. 3. **Read result** — When `completed`, the resolved person record is in the `result` field, in the same shape as the sync response. The async start endpoint returns uppercase `RUNNING`. Subsequent `/job-status/{task_id}` calls return lowercase statuses. `charge_amount` is returned in cents, not credits. ### Polling example ```python Python theme={null} import requests import time response = requests.post( "https://api.sixtyfour.ai/reverse-phone-async", headers={"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"}, json={ "lead": { "phone": "+14155551234" } } ) response.raise_for_status() task_id = response.json()["task_id"] while True: status = requests.get( f"https://api.sixtyfour.ai/job-status/{task_id}", headers={"x-api-key": "YOUR_API_KEY"} ).json() if status["status"] == "completed": results = status["result"] break if status["status"] in ("failed", "cancelled", "terminated", "timed_out"): raise RuntimeError(f"Job {status['status']}: {status.get('error', 'Unknown error')}") time.sleep(5) ``` ```javascript JavaScript theme={null} async function runAsync() { const start = await fetch("https://api.sixtyfour.ai/reverse-phone-async", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ lead: { phone: "+14155551234" } }) }); if (!start.ok) throw new Error(`Submit failed: ${start.status}`); const { task_id } = await start.json(); while (true) { const pollRes = await fetch( `https://api.sixtyfour.ai/job-status/${task_id}`, { headers: { "x-api-key": "YOUR_API_KEY" } } ); if (!pollRes.ok) throw new Error(`Poll failed: ${pollRes.status}`); const status = await pollRes.json(); if (status.status === "completed") return status.result; if (["failed", "cancelled", "terminated", "timed_out"].includes(status.status)) { throw new Error(`Job ${status.status}: ${status.error || "Unknown error"}`); } await new Promise(r => setTimeout(r, 5000)); } } ``` ### Webhook callback Pass a `webhook_url` to receive the result via HTTP POST instead of polling. The signed payload, retry behavior, and verification steps are documented in [Outgoing Webhooks](/api-reference/webhooks/outgoing). ```json theme={null} POST /reverse-phone-async { "lead": { "phone": "+14155551234" }, "webhook_url": "https://your-server.com/webhooks/sixtyfour" } ``` ## Bulk processing Use `/reverse-phone-bulk` (sync) or `/reverse-phone-bulk-async` (async) to resolve up to 100 phone numbers in a single call. Both accept a `leads` array and the same `webhook_url` field as the single-lead endpoints. ### Bulk sync Returns 200 OK with results once every lead is processed. Best for small batches where you want a single round-trip. ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/reverse-phone-bulk" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "leads": [ { "phone": "+14155551234" }, { "phone": "+16195555678" } ] }' ``` ```python Python theme={null} import requests response = requests.post( "https://api.sixtyfour.ai/reverse-phone-bulk", headers={"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"}, json={ "leads": [ {"phone": "+14155551234"}, {"phone": "+16195555678"} ] } ) response.raise_for_status() results = response.json() ``` ### Bulk async Submit a batch, get back a `task_id`, then poll `/job-status/{task_id}` (or set `webhook_url` to receive a callback). Recommended for batches of more than a handful of leads, or when you don't want to hold a long-lived HTTP connection. ```python Python theme={null} import requests import time response = requests.post( "https://api.sixtyfour.ai/reverse-phone-bulk-async", headers={"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"}, json={ "leads": [ {"phone": "+14155551234"}, {"phone": "+16195555678"} ] } ) response.raise_for_status() task_id = response.json()["task_id"] while True: status = requests.get( f"https://api.sixtyfour.ai/job-status/{task_id}", headers={"x-api-key": "YOUR_API_KEY"} ).json() if status["status"] == "completed": results = status["result"] break if status["status"] in ("failed", "cancelled", "terminated", "timed_out"): raise RuntimeError(f"Job {status['status']}: {status.get('error', 'Unknown error')}") time.sleep(10) ``` # Verify Email Source: https://docs.sixtyfour.ai/api-reference/endpoint/verify-email Check whether one email address can receive mail. ## Use case Check a specific address you already have, such as an email typed into a signup or booking form, before you rely on it. The request returns the verdict directly, typically in under a second. `/verify-email` checks the exact address you send. To discover an address for a person, use [Find Email](/api-reference/endpoint/find-email). ## Endpoint ```http theme={null} POST https://api.sixtyfour.ai/verify-email ``` See the full request/response schema and parameters in the API Reference. ## Pricing See [Credits & Pricing Guide](/guides/credits-and-pricing) for credit costs. ## Errors For error responses (400, 403, 422, 429, 503, etc.), see [Handling Errors](/api-reference/errors). Each organization can make 120 requests a minute. Past that, the API returns `429` with a `Retry-After` header. ## Request | Field | Type | Description | | - | - | - | | `email` | string | The address to check. Required. | ## Response | Field | Type | Description | | - | - | - | | `email` | string | The address that was checked. | | `status` | string | `OK`, `RISKY`, `INVALID`, or `UNKNOWN`. See [Statuses](#statuses). | | `score` | integer or null | 0 to 100. Higher is more likely to be delivered. | ### Statuses `OK` and `RISKY` mean the same as in [Find Email](/api-reference/endpoint/find-email). Here `UNKNOWN` always means the check was inconclusive; Find Email can also return `UNKNOWN` as a placeholder for an address you supplied and it did not check. Verify Email returns `INVALID` for an invalid address. Find Email leaves invalid addresses it discovers out, and returns `NOT_FOUND` when it has no address. | Status | Meaning | | - | - | | `OK` | The mail server accepted the address. | | `RISKY` | The address can receive mail but delivery is uncertain. Most often the company's domain accepts every address, so the mailbox itself cannot be confirmed. | | `INVALID` | The address or its domain does not exist, or the server rejected it. | | `UNKNOWN` | The mail server did not give a clear answer. | ## Usage ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/verify-email" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"email": "jane@gmial.com"}' ``` ```python Python theme={null} import requests response = requests.post( "https://api.sixtyfour.ai/verify-email", headers={ "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, json={"email": "jane@gmial.com"} ) response.raise_for_status() result = response.json() ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.sixtyfour.ai/verify-email", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ email: "jane@gmial.com" }) }); if (!response.ok) { throw new Error(`Verification failed (${response.status}): ${await response.text()}`); } const result = await response.json(); console.log(result); ``` ```json Response theme={null} { "email": "jane@gmial.com", "status": "INVALID", "score": 0 } ``` ## Verify many addresses To check a list of addresses, use the `verify_email` block in a [workflow](/api-reference/workflows/workflow-blocks). # Company Intelligence (Async) Source: https://docs.sixtyfour.ai/api-reference/enrichment/company-intelligence-async https://api.sixtyfour.ai/openapi.json post /company-intelligence-async Find and enrich a single company; returns a job ID to poll. # Company Intelligence (Bulk) Source: https://docs.sixtyfour.ai/api-reference/enrichment/company-intelligence-bulk https://api.sixtyfour.ai/openapi.json post /bulk-intelligence/company Upload a CSV/JSON/NDJSON file and start a background bulk intelligence run. Poll `GET /job-status/{task_id}`; while running the response includes `progress` with the rows enriched so far out of the total, and on completion it includes signed download links and `charge_amount` (total cents charged). Row-level failures pass through un-enriched; source-block failures fail the run. # Company Intelligence (Sync) Source: https://docs.sixtyfour.ai/api-reference/enrichment/company-intelligence-sync https://api.sixtyfour.ai/openapi.json post /company-intelligence Find and enrich a single company. # Enrich LinkedIn Profile Source: https://docs.sixtyfour.ai/api-reference/enrichment/enrich-linkedin-profile https://api.sixtyfour.ai/openapi.json post /enrich-linkedin Fetch a LinkedIn profile and extract structured fields in a single LLM call. # Find Email (Async) Source: https://docs.sixtyfour.ai/api-reference/enrichment/find-email-async https://api.sixtyfour.ai/openapi.json post /find-email-async Find a single person's email; returns a job ID to poll. # Find Email (Bulk, Async) Source: https://docs.sixtyfour.ai/api-reference/enrichment/find-email-bulk-async https://api.sixtyfour.ai/openapi.json post /find-email-bulk-async Find email addresses for a batch of leads; returns a job ID to poll. # Find Email (Bulk, Sync) Source: https://docs.sixtyfour.ai/api-reference/enrichment/find-email-bulk-sync https://api.sixtyfour.ai/openapi.json post /find-email-bulk Find email addresses for a batch of leads in one call. # Find Email (Sync) Source: https://docs.sixtyfour.ai/api-reference/enrichment/find-email-sync https://api.sixtyfour.ai/openapi.json post /find-email Find a single person's email address. # Find Phone (Async) Source: https://docs.sixtyfour.ai/api-reference/enrichment/find-phone-async https://api.sixtyfour.ai/openapi.json post /find-phone-async Find a single person's phone number; returns a job ID to poll. # Find Phone (Bulk, Async) Source: https://docs.sixtyfour.ai/api-reference/enrichment/find-phone-bulk-async https://api.sixtyfour.ai/openapi.json post /find-phone-bulk-async Find phone numbers for a batch of leads; returns a job ID to poll. # Find Phone (Bulk, Sync) Source: https://docs.sixtyfour.ai/api-reference/enrichment/find-phone-bulk-sync https://api.sixtyfour.ai/openapi.json post /find-phone-bulk Find phone numbers for a batch of leads in one call. # Find Phone (Sync) Source: https://docs.sixtyfour.ai/api-reference/enrichment/find-phone-sync https://api.sixtyfour.ai/openapi.json post /find-phone Find a single person's phone number. # Job Status Source: https://docs.sixtyfour.ai/api-reference/enrichment/job-status https://api.sixtyfour.ai/openapi.json get /job-status/{job_id} Poll the status of an async job and retrieve results when complete. cost_units="credits" returns cost fields in credits (charge_credits, cost_credits) instead of cents fields (charge_amount, cost_cents); the charge itself is unchanged. # People Intelligence (Async) Source: https://docs.sixtyfour.ai/api-reference/enrichment/people-intelligence-async https://api.sixtyfour.ai/openapi.json post /people-intelligence-async Find and enrich a single person; returns a job ID to poll. # People Intelligence (Bulk) Source: https://docs.sixtyfour.ai/api-reference/enrichment/people-intelligence-bulk https://api.sixtyfour.ai/openapi.json post /bulk-intelligence/people Upload a CSV/JSON/NDJSON file and start a background bulk intelligence run. Poll `GET /job-status/{task_id}`; while running the response includes `progress` with the rows enriched so far out of the total, and on completion it includes signed download links and `charge_amount` (total cents charged). Row-level failures pass through un-enriched; source-block failures fail the run. # People Intelligence (Sync) Source: https://docs.sixtyfour.ai/api-reference/enrichment/people-intelligence-sync https://api.sixtyfour.ai/openapi.json post /people-intelligence Find and enrich a single person. # QA Agent (Async) Source: https://docs.sixtyfour.ai/api-reference/enrichment/qa-agent-async https://api.sixtyfour.ai/openapi.json post /qa-agent-async Run a research agent that answers a question about a person or company; returns a job ID to poll. # QA Agent (Sync) Source: https://docs.sixtyfour.ai/api-reference/enrichment/qa-agent-sync https://api.sixtyfour.ai/openapi.json post /qa-agent Run a research agent that answers a question about a person or company. # Reverse Email Lookup (Async) Source: https://docs.sixtyfour.ai/api-reference/enrichment/reverse-email-lookup-async https://api.sixtyfour.ai/openapi.json post /reverse-email-async Resolve a person from their email address; returns a job ID to poll. # Reverse Email Lookup (Bulk, Async) Source: https://docs.sixtyfour.ai/api-reference/enrichment/reverse-email-lookup-bulk-async https://api.sixtyfour.ai/openapi.json post /reverse-email-bulk-async Resolve people from a batch of email addresses; returns a job ID to poll. # Reverse Email Lookup (Bulk, Sync) Source: https://docs.sixtyfour.ai/api-reference/enrichment/reverse-email-lookup-bulk-sync https://api.sixtyfour.ai/openapi.json post /reverse-email-bulk Resolve people from a batch of email addresses in one call. # Reverse Email Lookup (Sync) Source: https://docs.sixtyfour.ai/api-reference/enrichment/reverse-email-lookup-sync https://api.sixtyfour.ai/openapi.json post /reverse-email Resolve a person from their email address. # Reverse Phone Lookup (Async) Source: https://docs.sixtyfour.ai/api-reference/enrichment/reverse-phone-lookup-async https://api.sixtyfour.ai/openapi.json post /reverse-phone-async Resolve a person from their phone number; returns a job ID to poll. # Reverse Phone Lookup (Bulk, Async) Source: https://docs.sixtyfour.ai/api-reference/enrichment/reverse-phone-lookup-bulk-async https://api.sixtyfour.ai/openapi.json post /reverse-phone-bulk-async Resolve people from a batch of phone numbers; returns a job ID to poll. # Reverse Phone Lookup (Bulk, Sync) Source: https://docs.sixtyfour.ai/api-reference/enrichment/reverse-phone-lookup-bulk-sync https://api.sixtyfour.ai/openapi.json post /reverse-phone-bulk Resolve people from a batch of phone numbers in one call. # Reverse Phone Lookup (Sync) Source: https://docs.sixtyfour.ai/api-reference/enrichment/reverse-phone-lookup-sync https://api.sixtyfour.ai/openapi.json post /reverse-phone Resolve a person from their phone number. # Verify Email Source: https://docs.sixtyfour.ai/api-reference/enrichment/verify-email https://api.sixtyfour.ai/openapi.json post /verify-email Check whether one email address can receive mail. Responds in about a second. # Handling Errors Source: https://docs.sixtyfour.ai/api-reference/errors Error codes, rate limits, and common troubleshooting steps for the Sixtyfour API. ## Error Response Format All Sixtyfour API errors return a JSON response with a consistent structure: ```json theme={null} { "error": "Error Type", "message": "Detailed error message" } ``` Some endpoints may use an alternative format: ```json theme={null} { "detail": "Error description" } ``` Workflows and other endpoints may return 422 validation errors with a structured `detail` array: ```json theme={null} { "detail": [ { "loc": ["body", "workflow_name"], "msg": "field required", "type": "value_error.missing" } ] } ``` ## HTTP Status Codes The API uses standard HTTP status codes to indicate success or failure. | Status Code | Meaning | Description | | - | - | - | | 200 | OK | Request succeeded | | 400 | Bad Request | Invalid request body, missing required fields, or malformed data | | 401 | Unauthorized | Missing or invalid API key | | 402 | Payment Required | Insufficient balance to cover the estimated cost of a bulk job | | 403 | Forbidden | Feature not enabled for your organization (e.g., high or xhigh tier enrichment) | | 404 | Not Found | Resource not found (e.g., workflow or run ID, or an unknown profile photo key) | | 409 | Conflict | The resource is not in a state that allows the request (e.g., triggering a cancelled monitor), or an identical request is still being processed | | 422 | Validation Error | Invalid request parameters; see `detail` array for field-level errors | | 429 | Too Many Requests | Rate limit exceeded | | 500 | Internal Server Error | Server error - retry after a brief wait | | 502 | Bad Gateway | An upstream source failed (e.g., a profile photo could not be fetched) - retry after a brief wait | | 503 | Service Unavailable | Temporarily unavailable or at capacity - retry after a brief wait, or after the `Retry-After` header when present | ## Common Error Types ### 400 Bad Request **Causes:** * Missing required fields in request body * Invalid field types or formats * Malformed JSON payload * Empty or invalid lead/company data * Sending `webhook_payload` to `/workflows/run` for a workflow whose source block is not `webhook` * Sending `webhook_payload` with JWT (dashboard session) auth instead of an API key * Triggering a `read_csv` workflow without a valid `specs_override.resource_handle_id` from `/storage/csv/upload`, or with a `handle_id` that has expired or belongs to a different organization * `webhook_payload` is missing required columns from the `webhook` block's `input_schema`, or is malformed JSON * Invalid search exclusions: sending `exclude_public_ids` in company mode (people-only) or alongside `exclude_entity_ids`, using a saved exclusion list whose entity type doesn't match the search mode or that is not yet `ready`, or sending exclusions on a cursor request — see [Search Exclusions](/api-reference/search/search-exclusions) * Invalid monitor definitions: a `frequency` outside `1h`–`30d`, a `tier` other than `low`, `medium`, or `high`, a `subject_type` other than `company` or `lead`, an empty `struct`, a column used as both a key column and a watched field, a row with no value in any key column, two rows with the same key values, or a non-public `webhook_url` — see [Monitor Endpoints](/api-reference/monitors/monitors-endpoints) * Invalid monitor uploads: a file that is not `.csv`, `.json`, `.jsonl`, or `.ndjson`, is larger than 100 MB, has no rows or more than 100,000 rows, or cannot be parsed; a `config` that is not valid JSON, contains an unknown field, has an empty or invalid `webhook_event_types` list, or names a `key_columns` entry missing from the file— see [Create monitor from a file](/api-reference/monitors/monitors-endpoints#create-monitor-from-a-file) * A `cursor` for `GET /monitors/{monitor_id}/rows` that did not come from a previous `next_cursor` — see [List rows](/api-reference/monitors/monitors-endpoints#list-rows) **Examples:** ```json theme={null} { "error": "Bad Request", "message": "Invalid company data" } ``` ```json theme={null} { "error": "Bad Request", "message": "Invalid lead data" } ``` ```json theme={null} { "detail": "Lead data is required" } ``` ```json theme={null} { "error": "Bad Request", "message": "Missing required field: qualification_criteria" } ``` ```json theme={null} { "detail": "Rows 0 and 3 have the same identity values (Acme Robotics, acmerobotics.com). Add a column that tells them apart, or remove the duplicate." } ``` **How to fix:** * Verify all required fields are included in your request * Check that field types match the API specification * Ensure JSON is properly formatted * Validate input data before making the request * For workflow runs, match the request body to the source block: `webhook_payload` for `webhook` workflows ([Incoming Webhooks](/api-reference/webhooks/incoming)) or `specs_override.resource_handle_id` for `read_csv` workflows ([Workflow Execution](/api-reference/workflows/workflows-execution)). `webhook_payload` requires API key or OAuth auth — JWT (dashboard session) is rejected. ### 401 Unauthorized **Causes:** * Missing `x-api-key` header * Invalid or expired API key * API key not properly formatted **Example:** ```json theme={null} { "error": "Unauthorized", "message": "Invalid API key" } ``` **How to fix:** * Verify your API key is correct * Ensure the `x-api-key` header is included in every request * Check that your API key hasn't been revoked or expired * Get a new API key from the [dashboard](https://app.sixtyfour.ai) ### 402 Payment Required **Causes:** * Your balance doesn't cover the estimated cost of a [bulk intelligence](/api-reference/endpoint/bulk-intelligence) job (`rows × per-row tier price`) **How to fix:** * Add credits in the [dashboard](https://app.sixtyfour.ai) before resubmitting * Reduce the row count or use a cheaper tier to lower the estimated cost Nothing is charged when a request returns 402. ### 403 Forbidden **Causes:** * Requesting a feature that is not enabled for your organization (e.g., `tier: "micro"`, `tier: "high"`, or `tier: "xhigh"` enrichment) * Calling any `/monitors` endpoint when Monitors are not enabled for your organization * Creating or updating a monitor with the `5m` frequency or `micro` tier when testing monitors are not enabled for your organization **Examples:** ```json theme={null} { "detail": "High tier company enrichment is not enabled for this org. Contact sales to request access." } ``` ```json theme={null} { "detail": "X-High tier lead enrichment is not enabled for this org. Contact sales to request access." } ``` ```json theme={null} { "detail": "Monitors are not enabled for your organization." } ``` **How to fix:** * Verify your organization has access to the requested feature * Contact sales to request access to restricted tiers * Use a lower tier (`"low"`, `"medium"`, or `"high"` if available) if xhigh tier access is not available ### 409 Conflict (Monitors) **Causes:** * Triggering a monitor that is cancelled or paused * Updating a cancelled monitor or switching one of its rows on or off * Reusing a monitor idempotency key with different creation settings or file contents, or with an older explicit key whose original request cannot be verified * Triggering a monitor, or changing its `frequency` or `is_active`, while its `status` is `starting` or `failed`, before every row from an uploaded file is added * Sending a create request while an identical one, with the same idempotency key, is still being processed **Examples:** ```json theme={null} { "detail": "This monitor is not active." } ``` ```json theme={null} { "detail": "This request is already being processed. Retry shortly." } ``` **How to fix:** * For a paused monitor, resume it with `POST /monitors/{monitor_id}/update` and `"is_active": true`, then trigger it * For a monitor whose `status` is `starting`, wait until it is `active`. For one whose `status` is `failed`, send the same upload request again, with the same file and settings, to finish it * A cancelled monitor cannot be resumed or changed. Create a new monitor to start checks again * For an idempotency conflict, retry with the original creation payload. Use a new key for a new monitor, or when an older explicit key cannot be verified * If the request is still being processed, retry after a few seconds with the same payload and key; it returns what the first attempt created ### 502 Bad Gateway (Monitor Cancellation) If cancellation is saved but stopping the schedule fails, the API returns: ```json theme={null} { "detail": "Monitor is cancelled, but its schedule could not be paused. Retry cancellation to finish cleanup." } ``` The monitor remains cancelled and no further row checks run. Retry `POST /monitors/{monitor_id}/cancel` to finish pausing the schedule. Repeating cancellation is safe. ### 404 Not Found (Workflows) **Causes:** * Workflow ID does not exist or was deleted * Run/job ID does not exist or is invalid * Resource does not belong to your organization * Monitor or row ID does not exist, or the monitor belongs to another team **Example:** ```json theme={null} { "detail": "Workflow not found" } ``` **How to fix:** * Verify the workflow ID using `GET /workflows` * Verify the job\_id from your run response * Ensure you're accessing resources created by your organization ### 422 Validation Error (Monitors) `POST /monitors/{monitor_id}/update` rejects any request containing `struct`, including `null`, an empty object, or the existing definition. The entire request is rejected before other fields are applied. Create a new monitor to change its watched fields. For JSON monitor creation (`POST /monitors`) and updates (`POST /monitors/{monitor_id}/update`), `webhook_event_types` must be a nonempty list containing only `monitor.field.changed`, `monitor.execution.completed`, `monitor.execution.failed`, or `monitor.run.completed`. Empty lists, unknown names, and invalid field types return `422` with a field-level `detail` array. Send `null` on update to restore the default subscription, or omit the field to leave it unchanged. ### 422 Validation Error (Workflows and Other Monitor Requests) **Causes:** * Invalid workflow definition (empty blocks, incompatible block types) * Missing required fields in create/update requests * Invalid block or edge configuration * Unknown fields in monitor create, update, row-toggle, source, or history-export JSON requests, such as `frequncy` instead of `frequency` * Toggling a monitor row without the required JSON body `{"is_active": false}` or `{"is_active": true}`; the query parameter alone is not accepted **Example:** ```json theme={null} { "detail": [ { "loc": ["body", "workflow_definition", "blocks"], "msg": "blocks cannot be empty", "type": "value_error" } ] } ``` **How to fix:** * Ensure workflow has at least one block and valid edges * Check block compatibility (e.g., webhook → company\_enrichment) * Use the workflow editor's "Workflow API Reference" for block specs ### 429 Rate Limit Exceeded **Causes:** * Exceeded the rate limit of 500 requests per minute * Exceeded 120 requests per minute for your organization on `POST /verify-email` **Example:** ```json theme={null} { "error": "Too Many Requests", "message": "Rate limit exceeded" } ``` **How to fix:** * Implement exponential backoff and retry logic * Wait the number of seconds in the `Retry-After` header when present, otherwise 60 seconds, before retrying * Spread requests over time to stay under the limit * Use async endpoints for bulk operations * Contact support if you need a higher rate limit ### 500 Internal Server Error **Causes:** * Temporary server issue * Unexpected error during processing * Service degradation **Example:** ```json theme={null} { "error": "Internal Server Error", "message": "An unexpected error occurred" } ``` **How to fix:** * Wait a few seconds and retry the request * Implement retry logic with exponential backoff * If the error persists, contact support at [support@sixtyfour.ai](mailto:support@sixtyfour.ai) ### 503 Service Unavailable (Verify Email) When the email check returns no verdict, `POST /verify-email` returns: ```json theme={null} { "detail": "Email verification is temporarily unavailable. Retry shortly." } ``` The request is not charged. Retry it after a brief wait. ## Rate Limits The Sixtyfour API enforces the following rate limits: * **500 requests per minute** per API key * **5 concurrent deep searches** per organization * **120 requests per minute** per organization on `POST /verify-email` Rate limit information may be included in response headers (if available): * `X-RateLimit-Limit`: Maximum requests allowed per minute * `X-RateLimit-Remaining`: Remaining requests in current window * `X-RateLimit-Reset`: Unix timestamp when the limit resets ### Best Practices for Rate Limiting 1. **Implement Retry Logic** ```python theme={null} import requests import time def make_request_with_retry(url, headers, data, max_retries=3): for attempt in range(max_retries): try: response = requests.post(url, headers=headers, json=data) if response.status_code == 429: wait_time = 2 ** attempt # Exponential backoff print(f"Rate limit exceeded. Waiting {wait_time} seconds...") time.sleep(wait_time) continue response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: if attempt == max_retries - 1: raise time.sleep(2 ** attempt) raise Exception("Max retries exceeded") ``` 2. **Use Async Endpoints or Workflows for Bulk Operations** For processing multiple items, use async endpoints (`/company-intelligence-async`, `/people-intelligence-async`) or the [Workflows API](/api-reference/workflows/workflows-overview) instead of making many synchronous calls. 3. **Implement Request Queuing** Queue requests and process them at a controlled rate to stay under the limit. ## Timeout Issues ### Long-Running Requests Some endpoints perform deep research and can take several minutes: * **People Intelligence**: P95 runtime \~5 minutes, up to 10 minutes * **Company Intelligence**: Several minutes depending on research depth * **QA Agent**: Varies based on criteria complexity **Solutions:** 1. **Set Appropriate Timeouts** ```python Python theme={null} import requests # For sync endpoints, set timeout to at least 15 minutes (900 seconds) response = requests.post( "https://api.sixtyfour.ai/people-intelligence", headers=headers, json=data, timeout=900 # 15 minutes ) response.raise_for_status() ``` ```javascript JavaScript theme={null} // For sync endpoints, set timeout to at least 15 minutes (900000 ms) const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 900000); // 15 minutes try { const response = await fetch("https://api.sixtyfour.ai/people-intelligence", { method: "POST", headers: headers, body: JSON.stringify(data), signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); } catch (error) { clearTimeout(timeoutId); if (error.name === 'AbortError') { console.error('Request timed out after 15 minutes'); } else { throw error; } } ``` 2. **Use Async Endpoints** ```python Python theme={null} import requests import time # Start async job response = requests.post( "https://api.sixtyfour.ai/people-intelligence-async", headers=headers, json=data ) response.raise_for_status() task_id = response.json()["task_id"] # Poll for results while True: status_response = requests.get( f"https://api.sixtyfour.ai/job-status/{task_id}", headers=headers ) status_response.raise_for_status() status = status_response.json() if status["status"] == "completed": results = status["result"] break elif status["status"] in ("failed", "cancelled", "terminated", "timed_out"): print(f"Job {status['status']}: {status.get('error')}") break time.sleep(10) ``` ```javascript JavaScript theme={null} // Start async job const response = await fetch("https://api.sixtyfour.ai/people-intelligence-async", { method: "POST", headers: headers, body: JSON.stringify(data) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const taskInfo = await response.json(); const taskId = taskInfo.task_id; // Poll for results while (true) { const statusResponse = await fetch( `https://api.sixtyfour.ai/job-status/${taskId}`, { headers: headers } ); if (!statusResponse.ok) { throw new Error(`HTTP error! status: ${statusResponse.status}`); } const status = await statusResponse.json(); if (status.status === "completed") { const results = status.result; break; } else if (["failed", "cancelled", "terminated", "timed_out"].includes(status.status)) { console.error(`Job ${status.status}: ${status.error || "Unknown error"}`); break; } // Wait 10 seconds before polling again await new Promise(resolve => setTimeout(resolve, 10000)); } ``` 3. **Use Webhooks** Configure webhooks to receive notifications when async jobs complete. See [Outgoing Webhooks](/api-reference/webhooks/outgoing). ## Common Troubleshooting Scenarios ### Issue: "Invalid API key" error **Check:** * Is the `x-api-key` header included? * Is the API key copied correctly (no extra spaces)? * Has the API key been revoked or expired? **Solution:** Get your API key from the [dashboard](https://app.sixtyfour.ai) and verify it's correctly included in the header. *** ### Issue: Request times out **Check:** * Are you using a sync endpoint for long-running operations? * Is your HTTP client timeout set appropriately? **Solution:** Use async endpoints or increase client timeout to at least 15 minutes (900 seconds). *** ### Issue: Empty or incomplete results **Check:** * Did you provide enough input data for the enrichment? * Is the `struct` field properly defined with clear descriptions? * Are you requesting data that may not be publicly available? **Solution:** * Provide as much input data as possible (name, company, LinkedIn, etc.) * Write clear, detailed descriptions in the `struct` field * Check the `confidence_score` in the response to gauge data quality *** ### Issue: Rate limit exceeded frequently **Check:** * Are you making more than 500 requests per minute? * Are you implementing retry logic properly? **Solution:** * Use async endpoints for bulk operations * Implement request queuing to control the rate * Add delays between requests * Contact support if you need a higher limit *** ### Issue: "Bad Request" with valid data **Check:** * Is the JSON properly formatted? * Are all required fields included? * Are field types correct (object vs string, etc.)? **Solution:** Validate your JSON payload and compare against the endpoint documentation. *** ### Issue: Workflow run stuck or not progressing **Check:** * Are you polling `GET /workflows/runs/{run_id}/live_status`? * Is the `overall_status` still `queued` or `running`? * Has the workflow been running for an unusually long time? **Solution:** * Poll every 5-10 seconds; workflow runs can take several minutes * Use `overall_progress_percentage` and `blocks` to see per-block progress * To stop a run: `POST /workflows/cancel?job_id={job_id}` * If a run appears stuck, check `error_message` in block status; consider cancelling and retrying *** ### Issue: Webhook signature verification fails or deliveries stop **Check:** * Is your verifier using the **raw request body bytes**, not a re-serialized JSON parse? * During rotation, does it iterate every `v1=` segment (not only the last)? * Is your server clock within 5 minutes of UTC? * Are you using the plaintext secret (`sk_whsec_<64 hex>`), not the masked preview shown in the dashboard? **Solution:** * Log the raw body, the `Sixtyfour-Signature` header, and the HMAC your server computes; compare against what we sent. * See [Signing Secrets & Verification](/api-reference/webhooks/signing-secrets) for the verification algorithm and reference code. * If deliveries stop entirely, we may be refusing to deliver unsigned when signing is configured (fail-closed by design). Results remain available via `/job-status/{task_id}`. ### Verify Phone column mappings Rows with a phone but no value in the configured `name_column` skip verification without failing the batch. They return `Missing Name` in `phone_warnings` and `MissingRequiredInput` details in `phone_errors`. The name mapping must match the input column. For example, data containing `fullName` requires `name_column: "fullName"`. See [Configure Verify Phone](/guides/building-workflows#configure-verify-phone) to update a saved mapping. A missing phone column fails the block; a blank phone value skips the row. ## Getting Help If you're experiencing issues not covered in this guide: 1. Check the [API Reference](/api-quick-start) for endpoint-specific requirements 2. Review the [starter notebooks](/starter-notebooks/company-intelligence-tutorial) for working examples 3. Contact support at [support@sixtyfour.ai](mailto:support@sixtyfour.ai) 4. Include the following in your support request: * Request/response details (remove sensitive data) * Error message * Endpoint being called * Approximate timestamp of the error # Age Verification Source: https://docs.sixtyfour.ai/api-reference/intelligence/age-verification https://api.sixtyfour.ai/openapi.json post /verify-age Start an age verification for a person. Returns a task_id to poll `GET /verify-age/{task_id}`. # Age Verification Status Source: https://docs.sixtyfour.ai/api-reference/intelligence/age-verification-status https://api.sixtyfour.ai/openapi.json get /verify-age/{task_id} Poll an age verification job. Returns `{task_id, status}` while running or in a terminal non-completed state, and the age/DOB envelope once completed. # Cancel Monitor Source: https://docs.sixtyfour.ai/api-reference/monitors/cancel-monitor https://api.sixtyfour.ai/openapi.json post /monitors/{monitor_id}/cancel Permanently stop future checks. Create a new monitor to start again. # Create Monitor Source: https://docs.sixtyfour.ai/api-reference/monitors/create-monitor https://api.sixtyfour.ai/openapi.json post /monitors Create one monitor from a table, with a row per subject. The watched cells that arrive become each row's baseline, so the first check reports the ones that were already stale rather than adopting them. # Download The Table As It Stood Source: https://docs.sixtyfour.ai/api-reference/monitors/download-the-table-as-it-stood https://api.sixtyfour.ai/openapi.json get /monitors/{monitor_id}/snapshots/{as_of} One snapshot as a CSV: a row per subject, watched fields as columns. `as_of` is a timestamp from /snapshots, or `latest` for the present. # List A Row's Events Source: https://docs.sixtyfour.ai/api-reference/monitors/list-a-rows-events https://api.sixtyfour.ai/openapi.json get /monitors/{monitor_id}/rows/{row_id}/events One row's events, newest first. `field` narrows to a single cell's whole history. Quiet runs are hidden unless `include_completions` is set. # List Every State This Monitor's Data Has Been In Source: https://docs.sixtyfour.ai/api-reference/monitors/list-every-state-this-monitors-data-has-been-in https://api.sixtyfour.ai/openapi.json get /monitors/{monitor_id}/snapshots Every moment this table changed, newest first, each with a download link. Includes initial values and subsequent changes, not unchanged checks. Pass the last as_of as before to fetch the next page. # List Monitors Source: https://docs.sixtyfour.ai/api-reference/monitors/list-monitors https://api.sixtyfour.ai/openapi.json get /monitors One page of monitors, newest first. Set include_inactive=true and include_cancelled=false to include paused and other disabled monitors while hiding cancellations. # Monitor Notifications Source: https://docs.sixtyfour.ai/api-reference/monitors/monitor-notifications Webhook notifications a monitor sends after each check, their payload, and how to read the events behind them. ## Use case Get told when a monitored field changes or a check fails, instead of polling. A monitor can notify you after each row check or after all selected rows finish a run. Your receiver reads row events or downloads the whole table from the API. ## Pricing Notifications are free. See [Credits & Pricing Guide](/guides/credits-and-pricing) for the cost of monitor checks. ## Errors For error responses (400, 403, 404, etc.), see [Handling Errors](/api-reference/errors). ## Subscribe Set `webhook_url` and `webhook_event_types` when you [create the monitor](/api-reference/monitors/monitors-endpoints#create-monitor) or [update it](/api-reference/monitors/monitors-endpoints#update-monitor). A monitor sends only the event types you list. The default subscription is `["monitor.field.changed"]`; delivery also requires a `webhook_url`. | Event type | Sent when | | - | - | | `monitor.field.changed` | The check reported at least one `change` event. | | `monitor.execution.completed` | The check succeeded without a `change` event, including a first run that establishes initial values. | | `monitor.execution.failed` | The row check could not run. The next check re-examines the same time window. | | `monitor.run.completed` | All selected row workflows finished for one monitor run. Includes total, succeeded, and skipped row counts. | Checks that are skipped send no row notification. A check is skipped when your balance cannot cover it, which also records an `error` event on the row, or when the team's spend limit is reached, which records nothing. To catch skipped checks, compare each row's `last_run_at` with the monitor's frequency, or list a row's events with `include_completions=true` and look for gaps. A row's `last_run_at` advances only when a check of that row completes. The monitor's own `last_run_at` advances at the start of every tick, so it does not show skipped rows. `webhook_url` must be a public HTTPS or HTTP address. You can change or clear it later with `POST /monitors/{monitor_id}/update`. Change `webhook_event_types` through the same endpoint to replace the subscription for every row. Omit the field to keep it unchanged, or send `null` to restore `["monitor.field.changed"]`. Lists must be nonempty and contain only `monitor.field.changed`, `monitor.execution.completed`, `monitor.execution.failed`, or `monitor.run.completed`; duplicates collapse in first-occurrence order. Monitor and row responses include the effective subscription. The three row event types send one notification per row per check when subscribed to the resulting event type. `monitor.run.completed` sends one notification for the entire run. Subscribe to `monitor.execution.completed` to receive initial results and later checks that find no changes. ### First-run notifications The first check enriches each row and establishes starting values for future comparisons. A watched field that was blank or omitted in the input is recorded as an `established` event when its initial value is found. Establishing a value does not count as a change. * If the first check succeeds without changing a prefilled value, it sends `monitor.execution.completed` when subscribed. * If it changes a prefilled value, it sends `monitor.field.changed` instead of `monitor.execution.completed`. * If the check fails, it sends `monitor.execution.failed` when subscribed. Skipped checks send no notification. The linked [create-monitor example](/api-reference/monitors/monitors-endpoints#create-monitor) subscribes to the three row event types, including completion notifications. There is no separate initial-findings notification type. Subscribe to both `monitor.execution.completed` and `monitor.field.changed` to receive successful first-run results whether fields start empty or prefilled. Subscribing only to changes and failures does not notify you when the first check simply establishes initial values. ## Row notification payload ```json theme={null} { "type": "monitor.field.changed", "monitor_id": "d5a8c2f1-7e6b-4a93-b012-8f6e3d9c5a42", "row_id": "8c3e1f2a-6b4d-4c9e-a1f7-2e5d9b3c7a10", "event_group_id": "a7d2e9f1-3c5b-4e8a-9b1d-6f4c2e8a0b37", "metadata": { "portfolio_id": "p_42" } } ``` | Field | Description | | - | - | | `type` | The event type. | | `monitor_id` | The ID of the monitor containing the checked row. Use it as `{monitor_id}` in the events URL. | | `row_id` | The ID of the row that was checked, used as `{row_id}` in the events URL. | | `event_group_id` | Identifies one check of the specified row. Each check gets a new ID; all events from that check share it. | | `metadata` | The `metadata` you set on the monitor, echoed so you can route the notification without a lookup. | The payload identifies the check but does not include the changed values. Read them with [List row events](/api-reference/monitors/monitors-endpoints#list-row-events), filtered to the check: ```http theme={null} GET https://api.sixtyfour.ai/monitors/{monitor_id}/rows/{row_id}/events?event_group_id={event_group_id}&limit=500 ``` The events endpoint returns 20 events by default and at most 500, with no pagination. The limit applies to the total number of returned events, including completion events when requested. Both path IDs come directly from the notification. No row-to-monitor lookup is required. For a `monitor.execution.completed` notification, use the same endpoint with `include_completions=true`: ```http theme={null} GET https://api.sixtyfour.ai/monitors/{monitor_id}/rows/{row_id}/events?event_group_id={event_group_id}&include_completions=true&limit=500 ``` The response is a JSON array of events for that check of that row, not a fixed pair. On an initial check with no changes, it contains an `established` event for each initial field value found and a `completion` event. The established events contain the values in `current_value`; the completion event has a `payload` such as: ```json theme={null} { "first_run": true, "recheck_ran": true, "fields_established": 1 } ``` These details are in the fetched events, not in the webhook body. Without `include_completions=true`, completion events are hidden; established events are still returned. Previously, `monitor_id` contained the row ID. Receivers using that payload must read the row ID from `row_id` and the parent monitor ID from `monitor_id`. ## Whole-monitor completion Subscribe to `monitor.run.completed` in `webhook_event_types` when creating a monitor to receive one notification after every row selected for that run has finished. This includes the initial run. The existing `monitor.execution.completed` event remains per row and only covers checks without changes. To receive only whole-monitor notifications, include this field alongside rows, watched fields, and `webhook_url`: ```json theme={null} { "webhook_event_types": ["monitor.run.completed"] } ``` The notification body is: ```json theme={null} { "type": "monitor.run.completed", "monitor_id": "d5a8c2f1-7e6b-4a93-b012-8f6e3d9c5a42", "run_id": "c93dc1be-fdb7-4b36-9f74-23b284eaf56e", "completed_at": "2026-10-01T10:00:00+00:00", "rows": { "total": 100, "succeeded": 97, "skipped": 3 }, "metadata": { "portfolio_id": "p_42" } } ``` | Field | Description | | - | - | | `monitor_id` | The monitor whose run finished. | | `run_id` | Identifies this whole-monitor run. Stable across delivery retries; use with `type` to deduplicate. | | `completed_at` | ISO 8601 timestamp after the selected row workflows finished, fixed across delivery retries. | | `rows.total` | Number of active rows selected at the start of the run. Excluded rows are not counted. | | `rows.succeeded` | Rows whose check succeeded, with or without changes. | | `rows.skipped` | Rows without a confirmed successful check, including errors and checks skipped because a spending limit was reached. | | `metadata` | User-supplied metadata from the monitor configuration at delivery time, or `null` when unset. | This notification means the run finished, not that every row was refreshed. Check the counts before treating the whole table as refreshed; skipped rows may retain older values. `rows.succeeded + rows.skipped` equals `rows.total`. A monitor that is paused, deleted, or has no active rows when its run starts sends no run notification. Pausing a monitor after a run starts does not cancel that in-flight run. A run interrupted by workflow cancellation or a failed batch sends no run notification because it cannot establish that every row finished. Notification delivery failure does not rerun enrichment. Download the whole table using [Download snapshot](/api-reference/monitors/monitors-endpoints#download-snapshot): ```http theme={null} GET https://api.sixtyfour.ai/monitors/{monitor_id}/snapshots/{completed_at} ``` URL-encode `completed_at` for the path. This returns CSV with the values recorded by that time, including unchanged values and excluded rows. Because `rows.total` counts only active rows selected for this run, the CSV can contain more rows than the notification counts. Use `snapshots/latest` for current values, which may include later runs if delivery was delayed. These requests require your API key. A run notification has no `row_id` or `event_group_id`; its `run_id` is not a filter for the row-events endpoint. Individual row notifications retain their own execution IDs. You can subscribe to the run event alone or alongside row events. ## Delivery * Notifications are signed when your organization has a signing secret. See [Signing Secrets & Verification](/api-reference/webhooks/signing-secrets). * `Sixtyfour-Event-Type` carries the event type and `Sixtyfour-Event-Id` carries the row notification’s `event_group_id` or the whole-monitor notification’s `run_id`. Use the pair to deduplicate: a notification can arrive more than once. * A failed delivery is retried with exponential backoff. Each attempt times out after 10 seconds. * A notification that cannot be delivered does not affect the check. Its events are stored and readable from the API. ## Example receiver Verify each notification against its raw body, then read the check's events using `monitor_id`, `row_id`, and `event_group_id` from the payload. The examples below process field changes. Set `MONITOR_IDS` to a comma-separated list of monitor IDs to restrict this receiver; unset, it processes field changes from any monitor in the organization. An explicitly empty or whitespace-only list fails at startup. The signing secret verifies the sender, not which monitors this receiver handles. This receiver requires your organization to have a webhook signing secret. Without one, notifications arrive unsigned and this receiver rejects them with `400`. Generate a secret in [Settings → Webhooks](https://app.sixtyfour.ai/settings/webhooks). ```python Python theme={null} import hashlib import hmac import json import os import time import requests from flask import Flask, abort, request API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.sixtyfour.ai" SECRET = "sk_whsec_..." # store in a secret manager TOLERANCE = 300 monitor_ids_raw = os.getenv("MONITOR_IDS") MONITOR_IDS = {value.strip() for value in (monitor_ids_raw or "").split(",") if value.strip()} if monitor_ids_raw is not None and not MONITOR_IDS: raise ValueError("MONITOR_IDS must list at least one monitor ID when set") app = Flask(__name__) def verify(raw: bytes, header: str, secret: str) -> bool: parts = [p.strip() for p in header.split(",")] try: t = int(next(p.split("=", 1)[1] for p in parts if p.startswith("t="))) except (StopIteration, ValueError): return False if abs(int(time.time()) - t) > TOLERANCE: return False expected = hmac.new( secret.encode("utf-8"), f"{t}.".encode("utf-8") + raw, hashlib.sha256 ).hexdigest() return any( p.startswith("v1=") and hmac.compare_digest(expected, p.split("=", 1)[1]) for p in parts ) @app.post("/hooks/sixtyfour") def monitor_notification(): raw = request.get_data() # raw bytes, before JSON parse if not verify(raw, request.headers.get("Sixtyfour-Signature", ""), SECRET): abort(400) body = json.loads(raw) if body["type"] != "monitor.field.changed": return "", 204 row_id = body["row_id"] monitor_id = body["monitor_id"] if monitor_id is None: app.logger.error("Notification missing monitor_id: row %s, check %s", row_id, body["event_group_id"]) abort(422) if MONITOR_IDS and monitor_id not in MONITOR_IDS: return "", 204 # intentionally outside this receiver events = requests.get( f"{BASE_URL}/monitors/{monitor_id}/rows/{row_id}/events", headers={"x-api-key": API_KEY}, params={"event_group_id": body["event_group_id"], "limit": 500}, ) events.raise_for_status() for event in events.json(): if event["event_type"] == "change": print(event["field"], event["previous_value"], "->", event["current_value"]) return "", 204 ``` ```javascript JavaScript theme={null} import crypto from "crypto"; import express from "express"; const API_KEY = "YOUR_API_KEY"; const BASE_URL = "https://api.sixtyfour.ai"; const SECRET = process.env.SIXTYFOUR_WEBHOOK_SECRET; const TOLERANCE = 300; const monitorIdsRaw = process.env.MONITOR_IDS; const MONITOR_IDS = new Set( (monitorIdsRaw ?? "").split(",").map((value) => value.trim()).filter(Boolean), ); if (monitorIdsRaw !== undefined && MONITOR_IDS.size === 0) { throw new Error("MONITOR_IDS must list at least one monitor ID when set"); } const app = express(); app.use(express.raw({ type: "application/json" })); function verify(raw, header, secret) { const parts = header.split(",").map((p) => p.trim()); const tPart = parts.find((p) => p.startsWith("t=")); if (!tPart) return false; const t = parseInt(tPart.slice(2), 10); if (!Number.isFinite(t)) return false; if (Math.abs(Math.floor(Date.now() / 1000) - t) > TOLERANCE) return false; const expected = crypto .createHmac("sha256", secret) .update(Buffer.concat([Buffer.from(`${t}.`), raw])) .digest("hex"); return parts.some((p) => { if (!p.startsWith("v1=")) return false; const got = Buffer.from(p.slice(3), "utf8"); const exp = Buffer.from(expected, "utf8"); return got.length === exp.length && crypto.timingSafeEqual(got, exp); }); } app.post("/hooks/sixtyfour", async (req, res) => { if (!verify(req.body, req.header("Sixtyfour-Signature") || "", SECRET)) { return res.status(400).send("invalid signature"); } const body = JSON.parse(req.body.toString("utf8")); if (body.type !== "monitor.field.changed") return res.sendStatus(204); const rowId = body.row_id; const monitorId = body.monitor_id; if (!monitorId) { console.error("Notification missing monitor_id", rowId, body.event_group_id); return res.status(422).send("notification missing monitor_id"); } if (MONITOR_IDS.size && !MONITOR_IDS.has(monitorId)) return res.sendStatus(204); const params = new URLSearchParams({ event_group_id: body.event_group_id, limit: "500" }); const response = await fetch( `${BASE_URL}/monitors/${monitorId}/rows/${rowId}/events?${params}`, { headers: { "x-api-key": API_KEY } }, ); if (!response.ok) return res.status(502).send("could not read events"); const events = await response.json(); for (const event of events.filter((e) => e.event_type === "change")) { console.log(event.field, event.previous_value, "->", event.current_value); } res.sendStatus(204); }); app.listen(3000); ``` # Monitor Endpoints Source: https://docs.sixtyfour.ai/api-reference/monitors/monitors-endpoints Create monitors from rows or a file, read, update, trigger, and cancel them, and read a row's change history and table snapshots. ## Use case Watch fields on a list of people or companies and read what changed. Use these endpoints to create a monitor from rows or from a file, inspect its rows and events, export the table at any point in time, and change or stop it. See [Monitors Overview](/api-reference/monitors/monitors-overview) for how checks work. Monitors are available to organizations with Monitors enabled. Requests from other organizations return `403`. See the full request/response schema and parameters in the API Reference. ## Pricing After a row's first check, every tick bills a probe per active row. A full re-check at the monitor's tier is billed only when the probe finds a likely change. See [Credits & Pricing Guide](/guides/credits-and-pricing) for more information. ## Errors For error responses (400, 403, 404, 409, etc.), see [Handling Errors](/api-reference/errors). ## Create monitor Create one monitor from a table, with one row per subject. ```http theme={null} POST https://api.sixtyfour.ai/monitors ``` | Field | Type | Required | Description | | - | - | - | - | | `name` | string | Yes | Monitor name, 1–120 characters. Each row is named after it, followed by the row's key values. | | `rows` | array of objects | Yes | The table. At least one row. | | `key_columns` | array of strings | Yes | Columns that identify each row. Every row needs a value in at least one of them. | | `struct` | object | Yes | Watched fields: field name → description, or an object with `type`, `description`, and `subfields`. At least one field. A row column with the same name as a watched field sets that field's starting value. | | `frequency` | string | No | How often to check, `1h` to `30d`. Default `1d`. | | `tier` | string | No | Depth of the full re-check: `low` (default), `medium`, or `high`. | | `subject_type` | string | No | `company` (default) or `lead`. | | `research_plan` | string | No | Instructions for the enrichment that re-derives the fields. | | `webhook_url` | string | No | Public URL that receives notifications. See [Monitor Notifications](/api-reference/monitors/monitor-notifications). | | `webhook_event_types` | array of strings | No | Nonempty list of `monitor.field.changed`, `monitor.execution.completed`, `monitor.execution.failed`, or `monitor.run.completed`. Defaults to `["monitor.field.changed"]`. Duplicates are removed in first-occurrence order. | | `metadata` | object | No | Free-form data stored on the monitor and echoed in events and notifications. | | `is_active` | boolean | No | Default `true`. Set to `false` to create the monitor inactive: nothing is checked until you resume it with `POST /monitors/{monitor_id}/update` and `"is_active": true`. A monitor created inactive has `status: "paused"` and `disabled_reason: "paused"`. | | `first_check` | string | No | `now` (default) checks every row immediately. `next_tick` waits for the first scheduled fire. | | `idempotency_key` | string | No | Up to 200 characters. Resending the same request with the same key returns what the first attempt created. Keep the original settings when retrying, even if the monitor has since been updated. When omitted, the key is derived from the request body, so an identical resend also replays. | Unknown request fields are rejected. The `struct` field uses the same name in monitor and row responses. Creation is all or nothing. If any row is invalid, nothing is created. The response is `201` with the monitor, without its rows. Read them with [List rows](#list-rows). ### Example request ```json theme={null} { "name": "Portfolio leadership", "key_columns": ["company_name", "domain"], "rows": [ { "company_name": "Acme Robotics", "domain": "acmerobotics.com", "ceo_name": "Dana Lee" }, { "company_name": "Northwind Labs", "domain": "northwindlabs.io" } ], "struct": { "ceo_name": "Full name of the current CEO", "headcount": { "type": "int", "description": "Current number of employees" } }, "frequency": "1d", "tier": "low", "subject_type": "company", "webhook_url": "https://example.com/hooks/sixtyfour", "webhook_event_types": ["monitor.field.changed", "monitor.execution.completed", "monitor.execution.failed"], "metadata": { "portfolio_id": "p_42" } } ``` ### Example response ```json theme={null} { "id": "0b7f2c6e-4a1d-4a8e-9f3b-2d5c8e1a7b90", "org_id": "org_abc123", "team_id": "5e1d9c3a-7b2f-4e6a-8c1d-3f9a2b4c6d8e", "name": "Portfolio leadership", "frequency": "1d", "struct": { "ceo_name": "Full name of the current CEO", "headcount": { "type": "int", "description": "Current number of employees" } }, "tier": "low", "subject_type": "company", "research_plan": null, "is_active": true, "disabled_reason": null, "webhook_url": "https://example.com/hooks/sixtyfour", "webhook_event_types": ["monitor.field.changed", "monitor.execution.completed", "monitor.execution.failed"], "metadata": { "portfolio_id": "p_42" }, "last_run_at": null, "next_run_times": [ "2026-09-29T00:00:00+00:00", "2026-09-30T00:00:00+00:00", "2026-10-01T00:00:00+00:00" ], "row_count": 2, "active_row_count": 2, "status": "active", "created_at": "2026-09-28T17:05:12.482Z", "updated_at": "2026-09-28T17:05:12.482Z" } ``` *** ## Create monitor from a file Create one monitor from an uploaded file, with one row per subject. Use this endpoint for large tables: the request returns as soon as the file is validated, and the rows are added afterwards. ```http theme={null} POST https://api.sixtyfour.ai/monitors/upload ``` The request is `multipart/form-data` with two parts. | Part | Type | Required | Description | | - | - | - | - | | `file` | file | Yes | A `.csv`, `.json`, `.jsonl`, or `.ndjson` file. Maximum 100 MB and 100,000 rows. CSV files need a header row. | | `config` | string | Yes | The monitor's settings as a JSON-encoded string. Maximum 300,000 characters. | `config` takes the same fields as [Create monitor](#create-monitor), except `rows` and `idempotency_key`: `name`, `key_columns`, `struct`, `frequency`, `tier`, `subject_type`, `research_plan`, `webhook_url`, `webhook_event_types`, `metadata`, `is_active`, and `first_check`. Unknown fields are rejected. Every column in `key_columns` must exist in the file. | Header | Required | Description | | - | - | - | | `Idempotency-Key` | No | Up to 200 characters. Resending the same file and settings with the same key returns the monitor the first attempt created. When omitted, the key is derived from `config` and the file's contents, so resending the same file and settings also replays. | The file is validated in full before anything is created. If any row is invalid, nothing is created. The response is `202` with the monitor, without its rows. `status` is `starting` and `row_count` is `0`. Rows are then added in the background, in the file's order, and `row_count` rises as they arrive. When every row is in, `status` becomes `active`, `next_run_times` is populated, and the first check runs if `first_check` is `now`. Poll `GET /monitors/{monitor_id}` to follow progress. While `status` is `starting`, no row is checked or billed. `POST /monitors/{monitor_id}/trigger` returns `409`, and so does `POST /monitors/{monitor_id}/update` when it changes `frequency` or `is_active`. `POST /monitors/{monitor_id}/cancel` is accepted: the rows finish adding and the monitor stays cancelled. Uploads are limited to 10 per minute per organization. Replayed requests count toward the limit. ### Example request ```bash theme={null} curl -X POST "https://api.sixtyfour.ai/monitors/upload" \ -H "x-api-key: YOUR_API_KEY" \ -F "file=@companies.csv" \ -F 'config={ "name": "Portfolio leadership", "key_columns": ["company_name", "domain"], "struct": { "ceo_name": "Full name of the current CEO", "headcount": {"type": "int", "description": "Current number of employees"} }, "frequency": "1d", "tier": "low", "subject_type": "company" }' ``` ### Example response ```json theme={null} { "id": "0b7f2c6e-4a1d-4a8e-9f3b-2d5c8e1a7b90", "org_id": "org_abc123", "team_id": "5e1d9c3a-7b2f-4e6a-8c1d-3f9a2b4c6d8e", "name": "Portfolio leadership", "frequency": "1d", "struct": { "ceo_name": "Full name of the current CEO", "headcount": { "type": "int", "description": "Current number of employees" } }, "tier": "low", "subject_type": "company", "research_plan": null, "is_active": true, "disabled_reason": "Rows are still being added.", "webhook_url": null, "metadata": null, "last_run_at": null, "next_run_times": [], "row_count": 0, "active_row_count": 0, "status": "starting", "created_at": "2026-09-28T17:05:12.482Z", "updated_at": "2026-09-28T17:05:12.482Z" } ``` ### Interrupted uploads If adding rows fails, `status` becomes `failed` and `disabled_reason` explains the failure. Send the same request again to finish it: rows that were already added are kept, and only the missing rows are written. A monitor that stays in `starting` with a `row_count` that has stopped rising was interrupted. Send the same request again once five minutes have passed since `updated_at`. *** ## List monitors Retrieve your team's monitors, newest first. Rows are not included. ```http theme={null} GET https://api.sixtyfour.ai/monitors ``` | Query parameter | Default | Description | | - | - | - | | `include_inactive` | `false` | Include inactive monitors. | | `include_cancelled` | `true` | Include cancelled monitors when `include_inactive` is `true`. Set to `false` to exclude cancelled monitors while retaining active and paused monitors. | | `limit` | `50` | Page size, 1–200. | | `cursor` | — | `next_cursor` from the previous page. | The response is `{ "monitors": [...], "next_cursor": "..." }`. `next_cursor` is `null` on the last page. *** ## Get monitor Retrieve a monitor: its settings, `status`, and row counts. Rows are not included; read them with [List rows](#list-rows). ```http theme={null} GET https://api.sixtyfour.ai/monitors/{monitor_id} ``` This endpoint previously returned every row in a `rows` field. It now returns the monitor only. Read rows with `GET /monitors/{monitor_id}/rows`. Every monitor response includes `status`: | `status` | Meaning | | - | - | | `starting` | Rows from an uploaded file are still being added. Nothing is checked yet. | | `failed` | Adding rows from an uploaded file failed. Nothing is checked. Send the upload request again to finish it. | | `active` | The monitor is checking its rows on schedule. | | `paused` | Checks are paused and can be resumed. `is_active` is `false`; `disabled_reason` says why. | | `cancelled` | Future checks are permanently stopped. The monitor cannot be resumed or updated. | Paused and cancelled monitors return `next_run_times: []`. *** ## List rows Retrieve a page of a monitor's rows, switched-off rows included, in the order they were added. ```http theme={null} GET https://api.sixtyfour.ai/monitors/{monitor_id}/rows ``` | Query parameter | Default | Description | | - | - | - | | `limit` | `100` | Rows per page, 1–1000. | | `cursor` | — | `next_cursor` from the previous page. | The response is `{ "rows": [...], "next_cursor": "..." }`. `next_cursor` is `null` on the last page. To read the whole table at once as CSV, [download a snapshot](#download-snapshot) instead. A row has no schedule of its own. Its `next_run_times` are the monitor's, and are empty when the row is switched off or the monitor is paused or cancelled. ### Example response ```json theme={null} { "rows": [ { "id": "8c3e1f2a-6b4d-4c9e-a1f7-2e5d9b3c7a10", "org_id": "org_abc123", "team_id": "5e1d9c3a-7b2f-4e6a-8c1d-3f9a2b4c6d8e", "name": "Portfolio leadership — Acme Robotics, acmerobotics.com", "frequency": "1d", "subject": { "company_name": "Acme Robotics", "domain": "acmerobotics.com" }, "struct": { "ceo_name": "Full name of the current CEO", "headcount": { "type": "int", "description": "Current number of employees" } }, "baseline": { "ceo_name": "Dana Lee" }, "tier": "low", "subject_type": "company", "research_plan": null, "is_active": true, "disabled_reason": null, "webhook_url": "https://example.com/hooks/sixtyfour", "webhook_event_types": ["monitor.field.changed", "monitor.execution.completed", "monitor.execution.failed"], "metadata": { "portfolio_id": "p_42" }, "last_run_at": null, "next_run_times": ["2026-09-29T00:00:00+00:00"], "created_at": "2026-09-28T17:05:12.531Z", "updated_at": "2026-09-28T17:05:12.531Z" } ], "next_cursor": null } ``` *** ## Get the monitor a workflow run started Retrieve the monitor that a workflow's `monitor` block started. Read its rows with [List rows](#list-rows). ```http theme={null} GET https://api.sixtyfour.ai/monitors/for-run/{workflow_run_id}/{block_id} ``` Returns `404` when that block did not start a monitor, or when the monitor belongs to another team. See the [`monitor` block](/api-reference/workflows/workflow-blocks#monitor). *** ## Update monitor Change a monitor’s settings, including how often it runs, how deeply it researches, and where it notifies. ```http theme={null} POST https://api.sixtyfour.ai/monitors/{monitor_id}/update ``` `struct` is fixed at creation. Changing watched fields requires a new monitor, with its own baseline and history. All body fields are optional: `name`, `frequency`, `tier`, `webhook_url`, `webhook_event_types`, `metadata`, `is_active`. Changes apply to every row. Send `null` for `webhook_url` or `metadata` to clear them. Unknown fields are rejected. Cancelled monitors cannot be updated. `webhook_event_types` replaces the subscription with a nonempty list of `monitor.field.changed`, `monitor.execution.completed`, `monitor.execution.failed`, or `monitor.run.completed`. Omit it to keep the current subscription, or send `null` to restore `["monitor.field.changed"]`. Duplicate names collapse in first-occurrence order. The monitor and all its rows return the effective subscription in `webhook_event_types`. `is_active: false` pauses the whole monitor; `is_active: true` resumes a paused monitor. Pausing through this endpoint sets `disabled_reason: "paused"`, and resuming clears it. A row you switched off stays off when the monitor resumes. ### Example request ```json theme={null} { "frequency": "6h", "tier": "medium", "webhook_event_types": ["monitor.field.changed", "monitor.execution.completed"] } ``` *** ## Trigger monitor Check every active row now, without waiting for the next scheduled fire. ```http theme={null} POST https://api.sixtyfour.ai/monitors/{monitor_id}/trigger ``` Returns `202` with `{ "status": "accepted", "monitor_id": "..." }`. Returns `409` if the monitor is not active, or its `status` is `starting` or `failed`. If a check of the monitor is already running, the triggered one is skipped. *** ## Cancel monitor Permanently stop future checks for the whole monitor. Existing baselines and history remain readable. Cancellation cannot be undone; create a new monitor to start checks again. To stop checks temporarily, [pause the monitor](#update-monitor) instead. ```http theme={null} POST https://api.sixtyfour.ai/monitors/{monitor_id}/cancel ``` Returns the monitor with `status: "cancelled"`, `is_active: false`, `disabled_reason: "cancelled"`, and `next_run_times: []`. Cancelling again returns the cancelled monitor. *** ## Get row Retrieve one row, including the values its watched fields currently hold in `baseline`. ```http theme={null} GET https://api.sixtyfour.ai/monitors/{monitor_id}/rows/{row_id} ``` *** ## Switch a row on or off Include or exclude one row from checks. ```http theme={null} POST https://api.sixtyfour.ai/monitors/{monitor_id}/rows/{row_id} Content-Type: application/json {"is_active": false} ``` `is_active` is a required boolean in the JSON body. Send `true` to include the row again. Unknown body fields are rejected. Rows of a cancelled monitor cannot be changed. A switched-off row is skipped on every tick, is not billed, and keeps its baseline and history. Its `disabled_reason` is `excluded`. *** ## List row events Retrieve one row's events, newest first. ```http theme={null} GET https://api.sixtyfour.ai/monitors/{monitor_id}/rows/{row_id}/events ``` | Query parameter | Default | Description | | - | - | - | | `event_group_id` | — | Only the events from one check, for example the `event_group_id` from a notification. | | `field` | — | Only one field's history. | | `include_completions` | `false` | Include `completion` events for checks that found no change. | | `limit` | `20` | 1–500. | ### Example response ```json theme={null} [ { "id": "e41c7a2b-9d3f-4b6e-8a1c-5f2d7e9b3a60", "event_group_id": "a7d2e9f1-3c5b-4e8a-9b1d-6f4c2e8a0b37", "event_type": "change", "field": "ceo_name", "previous_value": "Dana Lee", "current_value": "Priya Raman", "payload": { "sources": ["https://acmerobotics.com/about/leadership"], "confidence": 92, "justification": "The company's leadership page lists Priya Raman as CEO as of September 2026.", "hint": "Leadership page updated with a new CEO.", "metadata": { "portfolio_id": "p_42" } }, "created_at": "2026-10-02T00:04:51.118Z" } ] ``` See [Events](/api-reference/monitors/monitors-overview#events) for every event type. *** ## List snapshots List every moment the monitor's data changed, newest first, each with a download link. A snapshot is recorded per change, not per check: the changes from one tick share one snapshot. ```http theme={null} GET https://api.sixtyfour.ai/monitors/{monitor_id}/snapshots ``` Optional query parameter: `limit` — 1–500, default `100`. ### Example response ```json theme={null} [ { "as_of": "2026-10-02T00:04:51+00:00", "changed_fields": ["ceo_name"], "subjects_changed": 1, "url": "https://api.sixtyfour.ai/monitors/0b7f2c6e-4a1d-4a8e-9f3b-2d5c8e1a7b90/snapshots/2026-10-02T00%3A04%3A51%2B00%3A00" } ] ``` *** ## Download snapshot Download the table as it stood at a moment, as CSV: one row per subject, with the key columns followed by the watched fields. ```http theme={null} GET https://api.sixtyfour.ai/monitors/{monitor_id}/snapshots/{as_of} ``` `as_of` is an `as_of` value from the snapshot list, URL-encoded, or `latest` for the current values. Any ISO 8601 timestamp is accepted and returns the values held at that time. *** ## Example usage ### Create from a file Upload a file, then wait until every row is added. ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/monitors/upload" \ -H "x-api-key: YOUR_API_KEY" \ -F "file=@companies.csv" \ -F 'config={"name": "Portfolio leadership", "key_columns": ["company_name", "domain"], "struct": {"ceo_name": "Full name of the current CEO"}, "frequency": "1d", "subject_type": "company"}' curl "https://api.sixtyfour.ai/monitors/MONITOR_ID" \ -H "x-api-key: YOUR_API_KEY" ``` ```python Python theme={null} import json import time import requests API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.sixtyfour.ai" headers = {"x-api-key": API_KEY} config = { "name": "Portfolio leadership", "key_columns": ["company_name", "domain"], "struct": {"ceo_name": "Full name of the current CEO"}, "frequency": "1d", "subject_type": "company", } with open("companies.csv", "rb") as f: response = requests.post( f"{BASE_URL}/monitors/upload", headers=headers, files={"file": ("companies.csv", f, "text/csv")}, data={"config": json.dumps(config)}, ) response.raise_for_status() monitor = response.json() while monitor["status"] == "starting": time.sleep(5) poll = requests.get(f"{BASE_URL}/monitors/{monitor['id']}", headers=headers) poll.raise_for_status() monitor = poll.json() if monitor["status"] == "failed": raise RuntimeError(monitor["disabled_reason"]) print(monitor["status"], monitor["row_count"]) ``` ```javascript JavaScript theme={null} import { readFile } from "node:fs/promises"; const API_KEY = "YOUR_API_KEY"; const BASE_URL = "https://api.sixtyfour.ai"; const headers = { "x-api-key": API_KEY }; const form = new FormData(); form.append( "file", new Blob([await readFile("companies.csv")], { type: "text/csv" }), "companies.csv", ); form.append( "config", JSON.stringify({ name: "Portfolio leadership", key_columns: ["company_name", "domain"], struct: { ceo_name: "Full name of the current CEO" }, frequency: "1d", subject_type: "company", }), ); const response = await fetch(`${BASE_URL}/monitors/upload`, { method: "POST", headers, body: form, }); if (!response.ok) { throw new Error(`Failed to create monitor: ${await response.text()}`); } let monitor = await response.json(); while (monitor.status === "starting") { await new Promise((resolve) => setTimeout(resolve, 5000)); const poll = await fetch(`${BASE_URL}/monitors/${monitor.id}`, { headers }); if (!poll.ok) { throw new Error(`Failed to read monitor: ${await poll.text()}`); } monitor = await poll.json(); } if (monitor.status === "failed") { throw new Error(monitor.disabled_reason); } console.log(monitor.status, monitor.row_count); ``` ### Create from rows Create a monitor, then read the changes for one of its rows. ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/monitors" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Portfolio leadership", "key_columns": ["company_name", "domain"], "rows": [ {"company_name": "Acme Robotics", "domain": "acmerobotics.com", "ceo_name": "Dana Lee"} ], "struct": {"ceo_name": "Full name of the current CEO"}, "frequency": "1d", "subject_type": "company" }' curl "https://api.sixtyfour.ai/monitors/MONITOR_ID/rows" \ -H "x-api-key: YOUR_API_KEY" curl "https://api.sixtyfour.ai/monitors/MONITOR_ID/rows/ROW_ID/events?field=ceo_name" \ -H "x-api-key: YOUR_API_KEY" ``` ```python Python theme={null} import requests API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.sixtyfour.ai" headers = {"x-api-key": API_KEY, "Content-Type": "application/json"} response = requests.post( f"{BASE_URL}/monitors", headers=headers, json={ "name": "Portfolio leadership", "key_columns": ["company_name", "domain"], "rows": [ {"company_name": "Acme Robotics", "domain": "acmerobotics.com", "ceo_name": "Dana Lee"} ], "struct": {"ceo_name": "Full name of the current CEO"}, "frequency": "1d", "subject_type": "company", }, ) response.raise_for_status() monitor = response.json() rows = requests.get(f"{BASE_URL}/monitors/{monitor['id']}/rows", headers=headers) rows.raise_for_status() row_id = rows.json()["rows"][0]["id"] events = requests.get( f"{BASE_URL}/monitors/{monitor['id']}/rows/{row_id}/events", headers=headers, params={"field": "ceo_name"}, ) events.raise_for_status() for event in events.json(): print(event["event_type"], event["previous_value"], "->", event["current_value"]) ``` ```javascript JavaScript theme={null} const API_KEY = "YOUR_API_KEY"; const BASE_URL = "https://api.sixtyfour.ai"; const headers = { "x-api-key": API_KEY, "Content-Type": "application/json" }; const response = await fetch(`${BASE_URL}/monitors`, { method: "POST", headers, body: JSON.stringify({ name: "Portfolio leadership", key_columns: ["company_name", "domain"], rows: [ { company_name: "Acme Robotics", domain: "acmerobotics.com", ceo_name: "Dana Lee" }, ], struct: { ceo_name: "Full name of the current CEO" }, frequency: "1d", subject_type: "company", }), }); if (!response.ok) { throw new Error(`Failed to create monitor: ${await response.text()}`); } const monitor = await response.json(); const rowsResponse = await fetch(`${BASE_URL}/monitors/${monitor.id}/rows`, { headers, }); if (!rowsResponse.ok) { throw new Error(`Failed to read rows: ${await rowsResponse.text()}`); } const rowId = (await rowsResponse.json()).rows[0].id; const eventsResponse = await fetch( `${BASE_URL}/monitors/${monitor.id}/rows/${rowId}/events?field=ceo_name`, { headers }, ); const events = await eventsResponse.json(); for (const event of events) { console.log(event.event_type, event.previous_value, "->", event.current_value); } ``` # Monitors Overview Source: https://docs.sixtyfour.ai/api-reference/monitors/monitors-overview How monitors keep watched fields on a table of people or companies current, and how checks, events, and schedules work. A monitor watches a table of people or companies and re-checks chosen fields on a schedule. When a value changes, the monitor records the change with its sources and can notify your webhook. Monitors are available to organizations with Monitors enabled. Requests from other organizations return `403`. Contact [support@sixtyfour.ai](mailto:support@sixtyfour.ai) for access. See the full request/response schema and parameters in the API Reference. ## Concepts | Term | Meaning | | - | - | | Monitor | One table, created from a list of rows, with one schedule. Its settings (watched fields, frequency, tier, webhook) apply to every row. | | Row | One person or company in the table. Each row has its own current values, event history, and on/off switch. | | Subject | The values that identify a row, taken from the row's `key_columns` (for example `company_name` and `domain`). A subject is never re-derived. | | Watched fields | The fields the monitor keeps current, passed as `struct`. Same shape as the enrichment `struct`: a description string, or an object with `type`, `description`, and `subfields` for nested fields. | | Baseline | The values a row currently holds for its watched fields. Every reported change updates it. | | Subject type | `company` re-derives fields with Company Intelligence. `lead` uses People Intelligence. | | Tier | Depth of the full re-check: `low`, `medium`, or `high`. | A column cannot be both a key column and a watched field. `POST /monitors` and `POST /monitors/upload` reject two rows with the same subject values. The workflow `monitor` block watches such rows once. ## How a check works Every tick of the schedule checks each active row in the monitor. ```mermaid theme={null} flowchart TD A[Schedule tick] --> B[Each active row] B --> C{First check for this row?} C -- Yes --> D[Enrich watched fields at the monitor tier] D --> E[Report uploaded values that are already stale] C -- No --> F[Probe: has anything changed since the last check?] F -- No --> G[completion event] F -- Yes --> H[Re-check watched fields at the monitor tier] H --> I[change event per field that moved] E --> J[Webhook notification] G --> J I --> J ``` * First check: a watched field whose name matches a column in the uploaded row starts from that cell. The first check enriches the row at the monitor's tier and reports a `change` for every uploaded value that is already out of date. Fields with no uploaded value get their first value. Monitors started by the workflow [`monitor` block](/api-reference/workflows/workflow-blocks#monitor) skip this full first check for rows whose input already supplies every watched field. * Later checks: the monitor looks at the row's known sources and fresh search results, then runs a lightweight probe that asks whether any watched field changed since the last check. Most checks end here with a `completion` event. * Re-check: when the probe finds a likely change, the monitor re-derives the watched fields at its tier and records one `change` event per field whose value moved, with sources, confidence, and a justification. A value equal to the field's most recently reported value is not reported again, so a field that changes back reports each transition once. * Failures: a check that cannot finish records an `error` event and leaves its time window open. The next check re-examines the same window, so no change is lost to a failed run. ## Events Each check has an `event_group_id` shared by every event it produced. | `event_type` | Meaning | | - | - | | `change` | A watched field's value changed. Carries `previous_value`, `current_value`, and a `payload` with `sources`, `confidence` (0–100, or `null` when the field was not scored), `justification`, and your `metadata`. `payload.first_run` is `true` when the first check found the uploaded value stale. | | `established` | A field received its first value: either the value uploaded at creation or the first value a check found. Recorded for history only. It does not count as a change. | | `completion` | The check ran and found no change. `payload` describes what was checked and the time window. Hidden from event listings unless `include_completions=true`. | | `error` | The check could not run. `payload.reason` explains why, for example `insufficient balance`. | ## Frequency `frequency` is a whole number followed by a unit: `m` (minutes), `h` (hours), `d` (days), or `w` (weeks). For example `6h`, `1d`, or `2w`. The default is `1d`. * The supported range is `1h` to `30d`. * `5m` is a testing frequency available only to organizations with it enabled. Other organizations receive `403`. * Schedules align to UTC. An hourly monitor fires at the top of each hour and a daily monitor at 00:00 UTC. `next_run_times` on every monitor response lists the next three fire times. * `first_check: "now"` (default) checks every row as soon as the monitor is created. `first_check: "next_tick"` waits for the first scheduled fire. * Checks never overlap. If a tick is still running when the next one is due, the next one is skipped. ## Billing After a row's first check, every tick bills a probe for that row. The full re-check is billed at the monitor's tier only for rows where the probe found a likely change. The first check of each row is billed at the monitor's tier. Switched-off rows and cancelled monitors are not billed. The probe, first check, and re-check are billed separately, and each is charged only if it runs to completion. A probe that completed is still charged when the re-check after it fails. Monitor charges appear in usage logs as `monitor_recheck`. See [Credits & Pricing Guide](/guides/credits-and-pricing) for more information. ## Access and ownership * A monitor belongs to the team of the credential that created it. Only credentials for that team can read or change it. Other teams receive `404`. * A monitor runs on behalf of the API key or user that created it. If that API key is revoked or expires, or the user leaves the organization or the team, the affected rows pause and `disabled_reason` is set to `key_revoked`, `owner_removed`, or `team_removed`. * When the team's spend limit is reached, checks are skipped until the limit clears. ## Errors For error responses (400, 403, 404, 409, etc.), see [Handling Errors](/api-reference/errors). # Retrieve Monitor Source: https://docs.sixtyfour.ai/api-reference/monitors/retrieve-monitor https://api.sixtyfour.ai/openapi.json get /monitors/{monitor_id} The monitor: its settings, status and row counts. Its rows are read from `GET /monitors/{monitor_id}/rows`. # Retrieve One Row Source: https://docs.sixtyfour.ai/api-reference/monitors/retrieve-one-row https://api.sixtyfour.ai/openapi.json get /monitors/{monitor_id}/rows/{row_id} One subject of the table, with everything its fields currently hold. # Retrieve The Monitor A Workflow Run Started Source: https://docs.sixtyfour.ai/api-reference/monitors/retrieve-the-monitor-a-workflow-run-started https://api.sixtyfour.ai/openapi.json get /monitors/for-run/{workflow_run_id}/{block_id} The monitor a run's `monitor` block started. Its rows are read from `GET /monitors/{monitor_id}/rows`. Team-gated: the run being visible does not make the monitor visible. # Switch A Row On Or Off Source: https://docs.sixtyfour.ai/api-reference/monitors/switch-a-row-on-or-off https://api.sixtyfour.ai/openapi.json post /monitors/{monitor_id}/rows/{row_id} Include or exclude one row from the monitor's checks. A switched-off row is skipped by the fan-out, so it costs nothing, and its baseline and history are kept. # Trigger Monitor Run Source: https://docs.sixtyfour.ai/api-reference/monitors/trigger-monitor-run https://api.sixtyfour.ai/openapi.json post /monitors/{monitor_id}/trigger Run every active row now, without waiting for the next interval. # Update Monitor Source: https://docs.sixtyfour.ai/api-reference/monitors/update-monitor https://api.sixtyfour.ai/openapi.json post /monitors/{monitor_id}/update Change monitor settings. Watched fields are fixed at creation. Applied to every row as well as the monitor, because the rows are what run. `is_active` is not pushed down: a row switched off by hand stays off. # Create Exclusion List Endpoint Source: https://docs.sixtyfour.ai/api-reference/search/create-exclusion-list-endpoint https://api.sixtyfour.ai/openapi.json post /search/exclusion-lists Create an exclusion list (asynchronous materialization). Lists are immutable in v1: to change one, create a new list and delete the old. Up to 20 active lists per organization (40 for organizations with extended exclusion limits). # Deep Search Status Source: https://docs.sixtyfour.ai/api-reference/search/deep-search-status https://api.sixtyfour.ai/openapi.json get /search/deep-search-status/{task_id} Poll the status of a deep search task and retrieve results when complete. # Delete Exclusion List Endpoint Source: https://docs.sixtyfour.ai/api-reference/search/delete-exclusion-list-endpoint https://api.sixtyfour.ai/openapi.json delete /search/exclusion-lists/{list_id} Soft-delete an exclusion list and evict its cached member set. # Download Search Results Source: https://docs.sixtyfour.ai/api-reference/search/download-search-results https://api.sixtyfour.ai/openapi.json get /search/download Generate a 15-minute signed download URL for a search result. # Filter Field Values Source: https://docs.sixtyfour.ai/api-reference/search/filter-field-values https://api.sixtyfour.ai/openapi.json post /search/filter-field-values Get the top values by count for a single filter-search field. # Filter Search Source: https://docs.sixtyfour.ai/api-reference/search/filter-search Reference for filter capabilities, field-value discovery, and paginated filter search for people and companies. ## Use case Search people or companies with `simple_filters` or `filters`. Discover valid fields first, inspect top values for one field, then run the paginated search. See the full request/response schema and parameters in the API Reference. ## Pricing See [Credits & Pricing Guide](/guides/credits-and-pricing) for credit costs. ## Errors For error responses (400, 403, 404, 409, 503, etc.), see [Handling Errors](/api-reference/errors). ## Rate limits Filter-search-specific API rate limits. | Limit | Value | | - | - | | `POST /search/query` | 200 requests per minute per organization | | `POST /search/filter-field-values` | 20 requests per minute per organization | ## Core concepts | Term | Description | | - | - | | `simple_filters` | The primary query input format for search and field-values requests. Supports Mongo-style operators such as `$eq`, `$in`, `$gte`, `$match`, and `$and`. | | `filters` | The DSL backup query format. The API rejects queries that exceed the field and query limits defined by `GET /search/filter-capabilities`. | | `exclude_entity_ids` | Excludes specific people or companies from `/search/query` results by ID. In people mode it accepts LinkedIn public IDs or profile URLs; in company mode, LinkedIn company IDs, URLs, slugs, or website domains. See [Search Exclusions](/api-reference/search/search-exclusions). | | `exclude_list_ids` | Excludes members of a reusable saved exclusion list. The list's entity type must match the search mode. See [Search Exclusions](/api-reference/search/search-exclusions). | | `fields` | Unified field metadata returned by `GET /search/filter-capabilities`. Use it to discover queryable, sortable, rangeable, and top-value-capable fields. | | `filter_snippet` | A ready-to-use exact-match clause in `filters` format returned alongside each top value. Use it when you need `filters`. | | `cursor` | A pagination token returned as `next_cursor`. Pass it as the only field in follow-up requests to fetch the next page. | ## Recommended flow
```mermaid theme={null} flowchart TD A["GET
/search/filter-capabilities"] -->|"Discover valid fields & limits"| B["POST
/search/filter-field-values"] B -->|"Inspect top values for a field"| C["POST
/search/query"] C --> D{has_more?} D -->|"true — send {cursor} only"| C D -->|false| E[Done] ```
## Simple filters `simple_filters` is the primary query format for this API. It supports common exact-match, range, logical, and text-search operations with concise JSON syntax. ### Top-level shape ```json theme={null} { "simple_filters": { "hq_country_iso2": { "$eq": "US" }, "employees_count": { "$gte": 100, "$lte": 5000 } } } ``` ### Supported operators The capabilities response exposes supported simple-query operators in `simple_query_operators`. | Operator | Description | | - | - | | `$eq`, `$ne` | Exact match or exact exclusion. | | `$gt`, `$gte`, `$lt`, `$lte` | Numeric or date range comparisons. | | `$in`, `$nin` | Match or exclude multiple exact values. | | `$exists` | Match documents where a field is present or absent. | | `$match`, `$phrase` | Text matching for one field. | | `$and`, `$or`, `$not`, `$nor` | Logical composition. | | `$elemMatch` | Match nested array elements. See [Nested array matching](#nested-array-matching-elemmatch) for path and type requirements. | | `$search` | Full-text search across supported fields. | | `$sort` | Sort results in search requests. | | `$limit` | Limit result count in search requests. | ### Nested array matching (\$elemMatch) `$elemMatch` queries fields inside nested arrays. Two mistakes account for nearly all failed queries: * Relative path — `company.companyId` instead of the full dotted path `currentCompanies.company.companyId` * Wrong type — passing a numeric ID as a string (`"1234567"`) when the field's `value_type` is `long` A relative path or type mismatch inside `$elemMatch` returns zero results or a misleading 400 — not always a clear error. Check the full dotted path and `value_type` in `GET /search/filter-capabilities` before querying. Find people currently at a specific company by numeric LinkedIn company ID: Company results expose this ID as the string `source_id`. Convert it to a JSON number for the long people field `currentCompanies.company.companyId`. ```json theme={null} { "mode": "people", "simple_filters": { "currentCompanies": { "$elemMatch": { "currentCompanies.company.companyId": 1234567 } } } } ``` The common wrong form — same request shape, but relative path and string ID: ```json theme={null} { "mode": "people", "simple_filters": { "currentCompanies": { "$elemMatch": { "company.companyId": "1234567" } } } } ``` Both the relative path (`company.companyId` instead of `currentCompanies.company.companyId`) and the string ID (`"1234567"` instead of `1234567`) cause this query to fail. `$elemMatch` must be keyed on the field's `nested_path` from capabilities — not always the outermost array. `currentCompanies.company.companyId` has `nested_path: "currentCompanies"`, so `$elemMatch` keys on `currentCompanies`. `currentCompanies.positions.title` has `nested_path: "currentCompanies.positions"`, so `$elemMatch` on `currentCompanies` cannot reference a positions field. ### Excluding results There are two ways to exclude records: * Use `simple_filters` operators such as `$ne`, `$nin`, `$not`, and `$nor` to exclude records that match field conditions. * Use the top-level exclusion fields — `exclude_entity_ids` or `exclude_list_ids` — to suppress specific people or companies by ID. See [Search Exclusions](/api-reference/search/search-exclusions) for identifier formats, saved lists, and limits. Operator-based exclusions affect matching, pagination, and result counts. ID-based exclusions are applied after matching, so pages can return fewer rows than `page_size`. Don't rely on `total_available` to count excluded records or size pagination — page until `next_cursor` is `null`. Cursor requests must send only `{"cursor": "..."}`. You cannot add or change exclusions while paging through an existing cursor. Include exclusions in the first `/search/query` request. ### Query examples Find US-based companies: ```json theme={null} { "mode": "company", "simple_filters": { "hq_country_iso2": { "$eq": "US" } } } ``` Find a company by website domain: ```json theme={null} { "mode": "company", "simple_filters": { "website_domain": "stripe.com" } } ``` Find US-based companies with at least 4 employees: ```json theme={null} { "mode": "company", "simple_filters": { "hq_country_iso2": { "$eq": "US" }, "employees_count": { "$gte": 4 } } } ``` Exclude companies in specific industries: ```json theme={null} { "mode": "company", "simple_filters": { "industry": { "$nin": ["Staffing", "Recruiting"] } } } ``` Find US-based companies while excluding public companies: ```json theme={null} { "mode": "company", "simple_filters": { "hq_country_iso2": { "$eq": "US" }, "is_public": { "$ne": true } } } ``` Exclude records using logical negation: ```json theme={null} { "mode": "company", "simple_filters": { "$and": [ { "hq_country_iso2": { "$eq": "US" } }, { "$not": { "categories_and_keywords": { "$match": "staffing" } } } ] } } ``` Exclude records that match any disallowed condition: ```json theme={null} { "mode": "company", "simple_filters": { "$nor": [ { "industry": { "$eq": "Staffing" } }, { "categories_and_keywords": { "$match": "recruiting" } } ] } } ``` Find US-based companies with at least 4 employees where a post mentions YC X25: ```json theme={null} { "mode": "company", "simple_filters": { "$and": [ { "hq_country_iso2": { "$eq": "US" } }, { "employees_count": { "$gte": 4 } }, { "company_updates.description": { "$match": "(YC X25)" } } ] } } ``` Find US-based companies sorted by employee count descending, capped to 8 total results: ```json theme={null} { "mode": "company", "simple_filters": { "hq_country_iso2": { "$eq": "US" }, "$sort": [{ "employees_count": "desc" }], "$limit": 8 } } ``` Find Canadian companies that have raised any amount and mention technology in categories or keywords: ```json theme={null} { "mode": "company", "simple_filters": { "$and": [ { "hq_country_iso2": { "$eq": "CA" } }, { "last_funding_round_amount_raised": { "$gt": 0 } }, { "categories_and_keywords": { "$match": "technology" } } ] } } ``` Full-text search across specific fields: ```json theme={null} { "mode": "company", "simple_filters": { "$search": { "query": "cloud security", "fields": ["description", "categories_and_keywords"] } } } ``` Find people whose headline matches "engineer": ```json theme={null} { "mode": "people", "simple_filters": { "headline": { "$match": "engineer" } } } ``` Find people by current job title: ```json theme={null} { "mode": "people", "simple_filters": { "currentCompanies.positions.title": { "$match": "engineering manager" } } } ``` Fields with a doubly nested path accept `$elemMatch` on the inner nested path: ```json theme={null} { "mode": "people", "simple_filters": { "currentCompanies.positions": { "$elemMatch": { "currentCompanies.positions.title": { "$match": "engineering manager" } } } } } ``` Find people currently at a specific company by numeric LinkedIn company ID — see the full example and path/type requirements in [Nested array matching (\$elemMatch)](#nested-array-matching-elemmatch). ## DSL filters `filters` accepts a constrained DSL subset for cases where `simple_filters` is not expressive enough. For DSL syntax and clause behavior, see the [OpenSearch Query DSL docs](https://opensearch.org/docs/latest/query-dsl/). ### Top-level shape ```json theme={null} { "filters": { "query": { ... }, "sort": [ ... ], "size": 100 } } ``` ### Supported query clauses | Clause | Description | | - | - | | `bool` | Combine conditions with `must`, `filter`, `should`, and `must_not`. | | `term` | Exact-match query for a single value. | | `terms` | Exact-match query for multiple values. | | `range` | Numeric or date range with `gt`, `gte`, `lt`, `lte`. | | `exists` | Match documents where a field is present. | | `match` | Full-text match query. | | `match_phrase` | Phrase query. | | `multi_match` | Search the same value across multiple fields. | | `nested` | Query nested arrays and nested objects. | | `match_all` | Match all records in the selected mode. | ### Query examples Exact match: ```json theme={null} { "term": { "website_domain": "stripe.com" } } ``` Range filter: ```json theme={null} { "range": { "employees_count": { "gte": 100, "lte": 5000 } } } ``` Multi-field full-text search: ```json theme={null} { "multi_match": { "query": "cloud security", "fields": [ "description", "categories_and_keywords" ], "type": "best_fields", "operator": "and" } } ``` Nested query: ```json theme={null} { "nested": { "path": "funding_rounds", "query": { "match_phrase": { "funding_rounds.name": "Series B" } } } } ``` Exclude with `must_not`: ```json theme={null} { "bool": { "must": [ { "term": { "hq_country_iso2": "US" } } ], "must_not": [ { "term": { "industry": "Staffing" } } ] } } ``` ## Filter capabilities ```http theme={null} GET https://api.sixtyfour.ai/search/filter-capabilities ``` Returns all available fields for filter search — the complete list of fields you can query, sort, range, or aggregate against, plus the supported `simple_filters` operators and DSL limits. Call this first to discover what is filterable before constructing any `simple_filters` or `filters` request. The response is the source of truth: a field that is not in `fields` cannot be queried. ### Query parameters | Parameter | Type | Description | | - | - | - | | `mode` | string | `people` or `company`. Defaults to `company`. | | `refresh` | boolean | Force a mapping refresh instead of returning cached capabilities. Defaults to `false`. | ### Example request ```bash cURL theme={null} curl -X GET "https://api.sixtyfour.ai/search/filter-capabilities?mode=company" \ -H "x-api-key: YOUR_API_KEY" ``` ```python Python theme={null} import requests response = requests.get( "https://api.sixtyfour.ai/search/filter-capabilities", headers={"x-api-key": "YOUR_API_KEY"}, params={"mode": "company"}, ) print(response.json()) ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.sixtyfour.ai/search/filter-capabilities?mode=company", { headers: { "x-api-key": "YOUR_API_KEY" } }, ); const data = await response.json(); console.log(data); ``` ### Response shape | Field | Type | Description | | - | - | - | | `fields` | array | Every queryable field in the selected mode. See [Field entry](#field-entry). | | `nested_paths` | string\[] | Top-level paths that map to nested arrays or objects. Fields underneath these paths require a `nested` clause when using `filters`. | | `simple_query_operators` | string\[] | Operators allowed in `simple_filters` (`$eq`, `$in`, `$gte`, `$match`, etc.). | | `limits` | object | Hard limits applied to `filters` queries. See [Limits](#limits). | | `cache_ttl_seconds` | integer | Seconds the response is cached server-side. Pass `refresh=true` to bypass. | | `generated_at_epoch_ms` | integer | When the cached payload was built. | | `mapping_hash` | string | Hash of the underlying index mapping. Changes when fields are added or removed. | ### Limits | Limit | Description | | - | - | | `max_query_depth` | Maximum nesting depth of `filters.query`. | | `max_clause_count` | Maximum total clauses across a `filters` query. | | `max_terms_per_clause` | Maximum values in a single `terms` or `$in` clause. | | `max_sort_clauses` | Maximum sort fields. | | `max_string_length` | Maximum length of any string value in a query. | ### Field entry Each item in `fields` describes one queryable field. | Property | Type | Description | | - | - | - | | `field` | string | The field path to use in `simple_filters` and `filters`. | | `field_type` | string | Underlying index type (`keyword`, `text`, `long`, `double`, `integer`, `date`, `search_as_you_type`). | | `value_type` | string | The value type clients should send. Matches `field_type` except for text fields, which expose `keyword` for filtering. | | `nested_path` | string \| null | Set when the field lives inside a nested array. Use a `nested` clause with `path` set to this value for DSL queries. | | `queryable` | boolean | Whether the field is valid in `simple_filters` and `filters`. | | `sortable` | boolean | Whether the field can be used in `$sort` or DSL `sort`. | | `rangeable` | boolean | Whether the field accepts `$gt`, `$gte`, `$lt`, `$lte`, or DSL `range`. | | `sort_field` | string \| null | Alternate path used when sorting. Text fields sort on the `.keyword` subfield. | | `aggregation_field` | string \| null | Path used for top-value aggregation in `POST /search/filter-field-values`. | | `supports_top_values` | boolean | Whether the field can be passed to `POST /search/filter-field-values`. | | `supports_exact_filter_snippet` | boolean | Whether a `filter_snippet` is returned alongside top values for this field. | | `related_fields` | string\[] | Other fields that pair with this one (e.g. range pairs, currency + value). | | `preferred_for_exact_match` | boolean | When multiple fields describe the same concept, the preferred field for exact-match queries. | ### Example response Trimmed to two representative fields — a top-level keyword and a nested numeric. ```json theme={null} { "fields": [ { "field": "website", "field_type": "keyword", "value_type": "keyword", "nested_path": null, "queryable": true, "sortable": true, "rangeable": false, "sort_field": null, "aggregation_field": "website", "supports_top_values": true, "supports_exact_filter_snippet": true, "related_fields": [], "preferred_for_exact_match": false }, { "field": "stock_information.marketcap", "field_type": "double", "value_type": "double", "nested_path": "stock_information", "queryable": true, "sortable": true, "rangeable": true, "sort_field": null, "aggregation_field": "stock_information.marketcap", "supports_top_values": true, "supports_exact_filter_snippet": true, "related_fields": [], "preferred_for_exact_match": false } ], "nested_paths": [ "funding_rounds", "stock_information", "technologies_used", "visits_breakdown_by_country" ], "simple_query_operators": [ "$eq", "$ne", "$gt", "$gte", "$lt", "$lte", "$in", "$nin", "$exists", "$match", "$phrase", "$and", "$or", "$not", "$nor", "$elemMatch", "$search", "$sort", "$limit" ], "limits": { "max_query_depth": 12, "max_clause_count": 200, "max_terms_per_clause": 25, "max_sort_clauses": 2, "max_string_length": 200 }, "cache_ttl_seconds": 600, "generated_at_epoch_ms": 1779726915580, "mapping_hash": "3c507eb2e597605197fac2beb483fbbf619f628ce45b2c2c446825289347a24a0" } ``` ### Common lookups Filter the `fields` array client-side to find what is available for a given operation. | Goal | Property | | - | - | | Fields usable in `simple_filters` or `filters` | `queryable: true` | | Fields usable in `$sort` or DSL `sort` | `sortable: true` | | Fields that accept range operators | `rangeable: true` | | Fields that can be passed to `/search/filter-field-values` | `supports_top_values: true` | | Fields that require a `nested` clause in DSL | `nested_path` is non-null | | Fields that return `filter_snippet` for reuse in `filters` | `supports_exact_filter_snippet: true` | ### Find specific fields To confirm that a field exists and see what operators it accepts, filter the `fields` array by name. The examples below check `hq_country_iso2` and `employees_count` — the same fields used in the `simple_filters` examples earlier on this page. ```bash cURL theme={null} curl -s "https://api.sixtyfour.ai/search/filter-capabilities?mode=company" \ -H "x-api-key: YOUR_API_KEY" \ | jq '.fields[] | select(.field == "hq_country_iso2" or .field == "employees_count")' ``` ```python Python theme={null} import requests caps = requests.get( "https://api.sixtyfour.ai/search/filter-capabilities", headers={"x-api-key": "YOUR_API_KEY"}, params={"mode": "company"}, ).json() wanted = {"hq_country_iso2", "employees_count"} for f in caps["fields"]: if f["field"] in wanted: print(f["field"], "queryable=", f["queryable"], "rangeable=", f["rangeable"]) ``` ```javascript JavaScript theme={null} const caps = await fetch( "https://api.sixtyfour.ai/search/filter-capabilities?mode=company", { headers: { "x-api-key": "YOUR_API_KEY" } }, ).then(r => r.json()); const wanted = new Set(["hq_country_iso2", "employees_count"]); caps.fields .filter(f => wanted.has(f.field)) .forEach(f => console.log(f.field, "queryable=", f.queryable, "rangeable=", f.rangeable)); ``` If a field is missing from `fields`, it cannot be queried. If `queryable` is `false`, the field is exposed but reserved for sort or aggregation only. If `nested_path` is non-null, wrap the clause in a `nested` query when using `filters`. To search by partial name (e.g. every revenue-related field): ```bash theme={null} curl -s "https://api.sixtyfour.ai/search/filter-capabilities?mode=company" \ -H "x-api-key: YOUR_API_KEY" \ | jq '.fields[] | select(.field | test("revenue")) | .field' ``` Capabilities are cached for `cache_ttl_seconds` (typically 600s). Pass `refresh=true` only when verifying schema changes — every refresh rebuilds the mapping and is slower. ## Field values ```http theme={null} POST https://api.sixtyfour.ai/search/filter-field-values ``` Returns top values for one field, ranked by descending scoped document count. There is no batch variant. ### Scoped query * Omit both `simple_filters` and `filters` for global scope. * Send either `simple_filters` or `filters` for scoped discovery. * `filters.sort`, `filters.size`, and mixed `filters` plus `simple_filters` requests are not allowed on this endpoint. * `simple_filters.$sort` and `simple_filters.$limit` are not supported on this endpoint. ```json theme={null} { "simple_filters": { "hq_country_iso2": { "$eq": "US" } } } ``` Use `filter-field-values` with a high `top_k` to enumerate valid values for any enum-like field before filtering. This prevents querying with values that return zero results. Discover all available industries: ```bash theme={null} curl -s -X POST "https://api.sixtyfour.ai/search/filter-field-values" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"mode": "company", "field": "industry", "top_k": 100}' ``` The same pattern works for any enum-like field: `ownership_status`, `hq_country_iso2`, `funding_stage`, and so on. Check `supports_top_values: true` in the capabilities response to confirm the field supports this endpoint. ### Example request Global: ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/search/filter-field-values" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"mode": "company", "field": "hq_country_iso2", "top_k": 4}' ``` ```python Python theme={null} import requests response = requests.post( "https://api.sixtyfour.ai/search/filter-field-values", headers={"x-api-key": "YOUR_API_KEY"}, json={"mode": "company", "field": "hq_country_iso2", "top_k": 4}, ) print(response.json()) ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.sixtyfour.ai/search/filter-field-values", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ mode: "company", field: "hq_country_iso2", top_k: 4 }), }); const data = await response.json(); console.log(data); ``` Scoped: ```json theme={null} { "mode": "company", "field": "industry", "top_k": 4, "simple_filters": { "hq_country_iso2": { "$eq": "US" }, "employees_count": { "$gte": 200, "$lte": 5000 }, "ownership_status": { "$eq": "private" } } } ``` ### Nested fields For nested fields such as `funding_rounds.name`, counts are document counts and each `filter_snippet` is wrapped in a `nested` clause so it can be reused directly in DSL flows. ## Search query ```http theme={null} POST https://api.sixtyfour.ai/search/query ``` Runs a paginated search. Fresh searches accept `simple_filters`, `filters`, `parsed_query`, or a `search_id` to re-run a previous search. ### Request flow 1. First request: send exactly one of `simple_filters`, `filters`, `parsed_query`, or `search_id`, plus optional `page_size`, `max_results`, and exclusions (`exclude_entity_ids`, `exclude_list_ids`). 2. Next page request: send only `{"cursor":"..."}`. 3. Requests that mix `cursor` with query or pagination fields return `400`. ### Effective result cap * `page_size` controls rows per page. Range `1..100`. Default `10`. * `max_results` controls the total row cap across all pages. Range `1..5000`. * `simple_filters.$limit` and `filters.size` are optional query-level caps. * When both `max_results` and a query-level cap are set, the smaller value applies. * When no total cap is set, the backend default applies. ### Example request Fresh search: ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/search/query" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "mode": "company", "simple_filters": { "hq_country_iso2": { "$eq": "US" }, "employees_count": { "$gte": 100, "$lte": 5000 } } }' ``` ```python Python theme={null} import requests response = requests.post( "https://api.sixtyfour.ai/search/query", headers={"x-api-key": "YOUR_API_KEY"}, json={ "mode": "company", "simple_filters": { "hq_country_iso2": {"$eq": "US"}, "employees_count": {"$gte": 100, "$lte": 5000}, }, }, ) print(response.json()) ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.sixtyfour.ai/search/query", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ mode: "company", simple_filters: { hq_country_iso2: { $eq: "US" }, employees_count: { $gte: 100, $lte: 5000 }, }, }), }); const data = await response.json(); console.log(data); ``` Exclude specific people with `exclude_entity_ids`: ```json theme={null} { "mode": "people", "parsed_query": { "jobTitleExperience": { "titles": ["VP Engineering", "Head of Engineering"], "searchBoth": false } }, "exclude_entity_ids": ["john-doe-123", "https://linkedin.com/in/jane-smith-456"] } ``` Use exclusions when re-running or refining a search and you want to avoid returning records the user has already seen. People searches also accept `exclude_public_ids` for backward compatibility. For full details — company identifiers, saved lists, and limits — see [Search Exclusions](/api-reference/search/search-exclusions). Re-run a previous search: ```json theme={null} { "search_id": "e3b0c442-98fc-1c14-9afb-f4c8996fb924" } ``` Next page: ```json theme={null} { "cursor": "eyJpZCI6I..." } ``` `raw_source` returns the document directly. It does not use grouped objects such as `identity`, `profile`, or `location`. ### Photo and logo URLs People rows carry image URLs that need no credentials, so they can be embedded directly in an `` tag, CRM record, or spreadsheet cell. | Field | Contains | | - | - | | `logoUrl` | The person's profile photo, served from `https://api.sixtyfour.ai/v1/media/profiles/{key}`. Empty when the person has no photo. | | `currentCompanies[].company.logo` in `raw_source`, `currentCompanyLogo` in workflow rows and CSV exports | The employer's LinkedIn logo. Empty when no company logo is available. | Profile photo URLs are permanent. Each returns the image (JPEG, PNG, GIF, or WebP) with `Cache-Control: public, max-age=86400, stale-while-revalidate=604800`. Profile photo URLs allow 600 requests per minute per client IP. For `404`, `429`, `502`, and `503` responses, see [Handling Errors](/api-reference/errors). Older saved rows can hold a bare 32-character key in `logoUrl` instead of a URL. `https://api.sixtyfour.ai/v1/media/profiles/{key}` serves those keys as well. ## Export search results Export results from any search as a CSV. See [Export Search Results](/api-reference/search/search-endpoints#export-search-results) for the full endpoint reference. ## Example usage The following examples walk through the full recommended flow: discover valid fields, inspect top values for one field, then paginate through results. ```bash cURL theme={null} API_KEY="YOUR_API_KEY" BASE_URL="https://api.sixtyfour.ai" # 1. Get filter capabilities to discover valid fields curl -s "$BASE_URL/search/filter-capabilities" \ -H "x-api-key: $API_KEY" \ | jq '[.fields[] | select(.queryable) | .field] | .[0:5]' # 2. Inspect top values for a field to build a simple filter TOP_COUNTRY=$(curl -s -X POST "$BASE_URL/search/filter-field-values" \ -H "x-api-key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{"mode": "company", "field": "hq_country_iso2", "top_k": 4}' \ | jq -r '.values[0].value') echo "Top country: $TOP_COUNTRY" # 3. Run the paginated filter search, following cursors until has_more is false CURSOR="" while true; do if [ -z "$CURSOR" ]; then PAYLOAD=$(jq -nc --arg c "$TOP_COUNTRY" \ '{mode:"company", simple_filters:{hq_country_iso2:{"$eq":$c}, employees_count:{"$gte":100}}}') else PAYLOAD=$(jq -nc --arg cur "$CURSOR" '{cursor:$cur}') fi RESP=$(curl -s -X POST "$BASE_URL/search/query" \ -H "x-api-key: $API_KEY" \ -H "Content-Type: application/json" \ -d "$PAYLOAD") echo "$RESP" | jq -r '"Page \(.page_number) — \(.page_count) rows"' HAS_MORE=$(echo "$RESP" | jq -r '.has_more') [ "$HAS_MORE" != "true" ] && break CURSOR=$(echo "$RESP" | jq -r '.next_cursor') done ``` ```python Python theme={null} import requests API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.sixtyfour.ai" headers = {"x-api-key": API_KEY, "Content-Type": "application/json"} # 1. Get filter capabilities to discover valid fields caps = requests.get( f"{BASE_URL}/search/filter-capabilities", headers=headers, ).json() queryable_fields = [f["field"] for f in caps["fields"] if f["queryable"]][:5] print(f"Available queryable fields: {queryable_fields}") print(f"Supported simple operators: {caps['simple_query_operators']}") # 2. Inspect top values for a field to build a simple filter values_resp = requests.post( f"{BASE_URL}/search/filter-field-values", headers=headers, json={"mode": "company", "field": "hq_country_iso2", "top_k": 4}, ).json() top_country = values_resp["values"][0]["value"] print(f"Top country: {values_resp['values'][0]['value']}") # 3. Run the paginated filter search using simple_filters all_results = [] cursor = None while True: if cursor: payload = {"cursor": cursor} else: payload = { "mode": "company", "simple_filters": { "hq_country_iso2": {"$eq": top_country}, "employees_count": {"$gte": 100}, }, } resp = requests.post( f"{BASE_URL}/search/query", headers=headers, json=payload, ).json() all_results.extend(resp["results"]) print(f"Page {resp['page_number']} — {resp['page_count']} rows") if not resp["has_more"]: break cursor = resp["next_cursor"] print(f"Total rows fetched: {len(all_results)}") ``` ```javascript JavaScript theme={null} const API_KEY = "YOUR_API_KEY"; const BASE_URL = "https://api.sixtyfour.ai"; const headers = { "x-api-key": API_KEY, "Content-Type": "application/json" }; // 1. Get filter capabilities to discover valid fields const capsResp = await fetch(`${BASE_URL}/search/filter-capabilities`, { headers }); const caps = await capsResp.json(); const queryableFields = caps.fields.filter(f => f.queryable).slice(0, 5).map(f => f.field); console.log("Available queryable fields:", queryableFields); console.log("Supported simple operators:", caps.simple_query_operators); // 2. Inspect top values for a field to build a simple filter const valuesResp = await fetch(`${BASE_URL}/search/filter-field-values`, { method: "POST", headers, body: JSON.stringify({ mode: "company", field: "hq_country_iso2", top_k: 4 }), }); const valuesData = await valuesResp.json(); const topCountry = valuesData.values[0].value; console.log("Top country:", valuesData.values[0].value); // 3. Run the paginated filter search using simple_filters const allResults = []; let cursor = null; while (true) { const payload = cursor ? { cursor } : { mode: "company", simple_filters: { hq_country_iso2: { $eq: topCountry }, employees_count: { $gte: 100 }, }, }; const resp = await fetch(`${BASE_URL}/search/query`, { method: "POST", headers, body: JSON.stringify(payload), }); const data = await resp.json(); allResults.push(...data.results); console.log(`Page ${data.page_number} — ${data.page_count} rows`); if (!data.has_more) break; cursor = data.next_cursor; } console.log(`Total rows fetched: ${allResults.length}`); ``` # Filter Search Capabilities Source: https://docs.sixtyfour.ai/api-reference/search/filter-search-capabilities https://api.sixtyfour.ai/openapi.json get /search/filter-capabilities Get the fields, operators, and limits supported by filter search. # Get Exclusion List Source: https://docs.sixtyfour.ai/api-reference/search/get-exclusion-list https://api.sixtyfour.ai/openapi.json get /search/exclusion-lists/{list_id} Fetch one exclusion list, including materialization status and size. # List Exclusion Lists Endpoint Source: https://docs.sixtyfour.ai/api-reference/search/list-exclusion-lists-endpoint https://api.sixtyfour.ai/openapi.json get /search/exclusion-lists List the organization's exclusion lists (most recent first). # Lookalike Search Source: https://docs.sixtyfour.ai/api-reference/search/lookalike-search Find people similar to a seed cohort using LinkedIn profiles or a CSV, then page the results by similarity. Lookalike Search analyzes a seed cohort and finds similar people across Sixtyfour's people index. It infers the cohort's shared professional traits, builds an eligible audience, excludes the seed people, and ranks each match using grounded profile signals and semantic similarity. Lookalike Search currently supports people only. Company lookalike search is not available yet. ```mermaid theme={null} flowchart LR Seeds["LinkedIn URLs or CSV"] --> Start["POST /search/lookalike"] Start --> Poll["GET /search/lookalike/{task_id}"] Poll --> Results["POST /search/query"] Results --> Cursor["Continue with next_cursor"] ``` ## Prepare the seed cohort Every request accepts exactly one seed source. | Seed source | Request field | Requirements | | - | - | - | | LinkedIn profiles | `linkedin_urls` | 1–1,000 LinkedIn profile URLs. Each URL must be 500 characters or fewer. | | Uploaded CSV | `csv_resource_handle_id` | Handle returned by `POST /storage/csv/upload`. The CSV can contain up to 1,000 data rows. | For CSV seeds, a LinkedIn profile URL column produces the most reliable identity matches. Recognized headers include `linkedin`, `linkedin url`, `linkedin profile`, and `profile url`. Rows without a LinkedIn URL need a full name plus supporting identity data such as company, company domain, role, location, or education. Sixtyfour drops weak or ambiguous identity matches instead of guessing. Use `guidance` only for requirements that are not implied by the cohort, such as `"Only people in the United States with 8+ years of experience"`. Sixtyfour infers roles, skills, industries, seniority, employers, and other shared traits from the seeds. `analysis_sample_size` controls the maximum number of representative resolved profiles analyzed for cohort traits. It defaults to 100 and accepts values from 1 to 100. ## Run a lookalike search Submit the seed cohort, poll the dedicated status endpoint until the search completes, then pass its `search_id` to `/search/query`. ```bash cURL theme={null} API_KEY="YOUR_API_KEY" BASE_URL="https://api.sixtyfour.ai" START_RESPONSE=$(curl -fsS -X POST "$BASE_URL/search/lookalike" \ -H "x-api-key: $API_KEY" \ -H "Content-Type: application/json" \ -d '{ "linkedin_urls": [ "https://www.linkedin.com/in/ada-lovelace", "https://www.linkedin.com/in/grace-hopper" ], "guidance": "Only people in the United States" }') TASK_ID=$(printf '%s' "$START_RESPONSE" | jq -r '.task_id') DEADLINE=$(( $(date +%s) + 30 * 60 )) while true; do STATUS_RESPONSE=$(curl -fsS --connect-timeout 10 --max-time 30 \ "$BASE_URL/search/lookalike/$TASK_ID?include_filters=false&include_analysis=false" \ -H "x-api-key: $API_KEY") STATUS=$(printf '%s' "$STATUS_RESPONSE" | jq -r '.status') if [ "$STATUS" = "completed" ]; then SEARCH_ID=$(printf '%s' "$STATUS_RESPONSE" | jq -r '.search_id') break fi case "$STATUS" in failed|cancelled|terminated|timed_out) printf '%s\n' "$STATUS_RESPONSE" exit 1 ;; esac if [ "$(date +%s)" -ge "$DEADLINE" ]; then printf '%s\n' "Search did not complete within 30 minutes" exit 1 fi sleep 10 done curl -fsS -X POST "$BASE_URL/search/query" \ -H "x-api-key: $API_KEY" \ -H "Content-Type: application/json" \ -d "{\"search_id\":\"$SEARCH_ID\",\"page_size\":25}" ``` ```python Python theme={null} import time import requests API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.sixtyfour.ai" HEADERS = { "x-api-key": API_KEY, "Content-Type": "application/json", } TERMINAL_FAILURES = {"failed", "cancelled", "terminated", "timed_out"} start_response = requests.post( f"{BASE_URL}/search/lookalike", headers=HEADERS, json={ "linkedin_urls": [ "https://www.linkedin.com/in/ada-lovelace", "https://www.linkedin.com/in/grace-hopper", ], "guidance": "Only people in the United States", }, ) start_response.raise_for_status() task_id = start_response.json()["task_id"] deadline = time.monotonic() + 30 * 60 while True: status_response = requests.get( f"{BASE_URL}/search/lookalike/{task_id}", headers=HEADERS, params={"include_filters": "false", "include_analysis": "false"}, timeout=(10, 30), ) status_response.raise_for_status() status_data = status_response.json() if status_data["status"] == "completed": search_id = status_data["search_id"] break if status_data["status"] in TERMINAL_FAILURES: raise RuntimeError( status_data.get("error") or f"Search {status_data['status']}" ) if time.monotonic() >= deadline: raise TimeoutError("Search did not complete within 30 minutes") time.sleep(10) page_response = requests.post( f"{BASE_URL}/search/query", headers=HEADERS, json={"search_id": search_id, "page_size": 25}, ) page_response.raise_for_status() page = page_response.json() for person in page["results"]: print( person["raw_source"].get("fullName"), person["lookalike_score"], person["lookalike_evidence"], ) ``` ```javascript JavaScript theme={null} const API_KEY = "YOUR_API_KEY"; const BASE_URL = "https://api.sixtyfour.ai"; const headers = { "x-api-key": API_KEY, "Content-Type": "application/json", }; const terminalFailures = new Set([ "failed", "cancelled", "terminated", "timed_out", ]); async function requestJson(url, options = {}) { const response = await fetch(url, options); if (!response.ok) { throw new Error(`${response.status}: ${await response.text()}`); } return response.json(); } async function main() { const started = await requestJson(`${BASE_URL}/search/lookalike`, { method: "POST", headers, body: JSON.stringify({ linkedin_urls: [ "https://www.linkedin.com/in/ada-lovelace", "https://www.linkedin.com/in/grace-hopper", ], guidance: "Only people in the United States", }), }); let searchId; const deadline = Date.now() + 30 * 60_000; while (!searchId) { const params = new URLSearchParams({ include_filters: "false", include_analysis: "false", }); const status = await requestJson( `${BASE_URL}/search/lookalike/${started.task_id}?${params}`, { headers, signal: AbortSignal.timeout(30_000) }, ); if (status.status === "completed") { searchId = status.search_id; break; } if (terminalFailures.has(status.status)) { throw new Error(status.error ?? `Search ${status.status}`); } if (Date.now() >= deadline) { throw new Error("Search did not complete within 30 minutes"); } await new Promise((resolve) => setTimeout(resolve, 10_000)); } const page = await requestJson(`${BASE_URL}/search/query`, { method: "POST", headers, body: JSON.stringify({ search_id: searchId, page_size: 25 }), }); for (const person of page.results) { console.log( person.raw_source?.fullName, person.lookalike_score, person.lookalike_evidence, ); } } main().catch((error) => { console.error(error); process.exitCode = 1; }); ``` Poll lookalike tasks with `GET /search/lookalike/{task_id}`, not `GET /search/status/{task_id}`. If the start request times out, do not submit it again automatically—the search may already be running. ## Start response The start endpoint returns HTTP 202 after resolving the seeds and launching the search. ```json theme={null} { "task_id": "lookalike_search_11111111-1111-4111-8111-111111111111", "search_id": "11111111-1111-4111-8111-111111111111", "status": "running", "seed_diagnostics": { "seed_type": "linkedin_urls", "supplied": 2, "parsed": 2, "resolved": 2, "skipped": 0, "analyzed": 2, "errors": [], "warnings": [], "identity_resolution": { "exact_matches": 2, "similarity_matches": 0, "dropped_below_threshold": 0, "ambiguous_matches": 0, "duplicates_removed": 0, "average_confidence": 1.0 } } } ``` Use `seed_diagnostics` to confirm how much of the supplied cohort Sixtyfour resolved and analyzed. The search proceeds when at least one usable seed resolves. ## Completed status Lookalike searches typically complete in 1–3 minutes. Poll every 10 seconds and stop on `completed`, `failed`, `cancelled`, `terminated`, or `timed_out`. ```json theme={null} { "task_id": "lookalike_search_11111111-1111-4111-8111-111111111111", "status": "completed", "start_time": "2026-08-12T18:00:00+00:00", "close_time": "2026-08-12T18:02:14+00:00", "search_id": "11111111-1111-4111-8111-111111111111", "total_results": 12480, "seed_diagnostics": { "seed_type": "linkedin_urls", "supplied": 2, "parsed": 2, "resolved": 2, "skipped": 0, "analyzed": 2, "errors": [], "warnings": [], "identity_resolution": { "exact_matches": 2, "similarity_matches": 0, "dropped_below_threshold": 0, "ambiguous_matches": 0, "duplicates_removed": 0, "average_confidence": 1.0 } }, "excluded_public_ids": ["ada-lovelace", "grace-hopper"] } ``` The status endpoint includes generated `filters` and cohort `analysis` by default. Set `include_filters=false&include_analysis=false` when only progress and the completed `search_id` are needed. ## Page ranked results Use the completed `search_id` for the first page: ```json theme={null} { "search_id": "11111111-1111-4111-8111-111111111111", "page_size": 25 } ``` A lookalike result adds ranking fields to the standard raw people-search row: ```json theme={null} { "search_id": "11111111-1111-4111-8111-111111111111", "next_cursor": "eyJ2IjoxLCJraWQiOiJr...", "cursor_expires_in_seconds": 1800, "has_more": true, "page_size": 25, "page_count": 25, "total_results": 25, "total_available": 12480, "results": [ { "raw_source": { "publicId": "jane-doe", "fullName": "Jane Doe", "headline": "Staff Data Engineer" }, "lookalike_score": 0.82, "lookalike_rank": 1, "lookalike_evidence": [ { "signal_id": "skill:python", "dimension": "skill", "value": "Python", "support": 0.78, "coverage": 0.91, "weight": 0.34 } ] } ] } ``` | Field | Description | | - | - | | `lookalike_score` | Normalized similarity score from 0 to 1. It measures fit to this cohort, not a probability. | | `lookalike_rank` | Global 1-based rank across the lookalike result set. | | `lookalike_evidence` | Up to five matched cohort signals, ordered by ranking weight. | For every later page, send only the cursor returned by the previous response: ```json theme={null} { "cursor": "eyJ2IjoxLCJraWQiOiJr..." } ``` A page can contain fewer rows than `page_size` while `has_more` is `true`. Continue until `has_more` is `false`; do not infer completion from the number of rows returned. ## Use a CSV seed cohort Create a CSV with no more than 1,000 data rows: ```csv theme={null} linkedin_url,name,current_company,current_role https://www.linkedin.com/in/ada-lovelace,Ada Lovelace,Analytical Engines,Mathematician https://www.linkedin.com/in/grace-hopper,Grace Hopper,United States Navy,Computer Scientist ``` Upload it, then pass the returned `handle_id` as `csv_resource_handle_id`. ```bash cURL theme={null} UPLOAD=$(curl -fsS -X POST "https://api.sixtyfour.ai/storage/csv/upload" \ -H "x-api-key: YOUR_API_KEY" \ -F "file=@lookalike-seeds.csv;type=text/csv") HANDLE_ID=$(printf '%s' "$UPLOAD" | jq -r '.handle_id') curl -fsS -X POST "https://api.sixtyfour.ai/search/lookalike" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d "{\"csv_resource_handle_id\":\"$HANDLE_ID\"}" ``` ```python Python theme={null} import requests headers = {"x-api-key": "YOUR_API_KEY"} with open("lookalike-seeds.csv", "rb") as csv_file: upload_response = requests.post( "https://api.sixtyfour.ai/storage/csv/upload", headers=headers, files={"file": ("lookalike-seeds.csv", csv_file, "text/csv")}, ) upload_response.raise_for_status() handle_id = upload_response.json()["handle_id"] start_response = requests.post( "https://api.sixtyfour.ai/search/lookalike", headers={**headers, "Content-Type": "application/json"}, json={"csv_resource_handle_id": handle_id}, ) start_response.raise_for_status() print(start_response.json()) ``` ```javascript JavaScript theme={null} import { readFile } from "node:fs/promises"; const headers = { "x-api-key": "YOUR_API_KEY" }; const csv = await readFile("lookalike-seeds.csv"); const form = new FormData(); form.append( "file", new Blob([csv], { type: "text/csv" }), "lookalike-seeds.csv", ); const uploadResponse = await fetch( "https://api.sixtyfour.ai/storage/csv/upload", { method: "POST", headers, body: form }, ); if (!uploadResponse.ok) throw new Error(await uploadResponse.text()); const { handle_id } = await uploadResponse.json(); const startResponse = await fetch( "https://api.sixtyfour.ai/search/lookalike", { method: "POST", headers: { ...headers, "Content-Type": "application/json" }, body: JSON.stringify({ csv_resource_handle_id: handle_id }), }, ); if (!startResponse.ok) throw new Error(await startResponse.text()); console.log(await startResponse.json()); ``` After the start call, use the same status and result-pagination flow shown above. ## Export results Pass the completed lookalike `search_id` to `POST /search/export`, poll the returned export task through `GET /search/status/{task_id}`, then download the generated CSV. See [Export Search Results](/api-reference/search/search-endpoints#export-search-results). ## Pricing See [Credits & Pricing Guide](/guides/credits-and-pricing) for more information. ## Errors For error responses (400, 402, 403, 404, 422, 429, etc.), see [Handling Errors](/api-reference/errors). # Lookalike Search Status Source: https://docs.sixtyfour.ai/api-reference/search/lookalike-search-status https://api.sixtyfour.ai/openapi.json get /search/lookalike/{task_id} Poll lookalike progress and return generated filters on completion. # Deep Search Source: https://docs.sixtyfour.ai/api-reference/search/search-endpoints Full reference for the start, status, query, export, and download endpoints for deep search. ## Use case Turn natural language into a targeted list of people or companies — without manual prospecting. These endpoints let you submit a query, track progress asynchronously, and either browse results as paginated JSON or download them as a CSV. See the full request/response schema and parameters in the API Reference. ## Pricing See [Credits & Pricing Guide](/guides/credits-and-pricing) for credit costs. ## Errors For error responses (400, 403, 404, 409, 429, etc.), see [Handling Errors](/api-reference/errors). *** ## Start deep search Submit a natural language query to start an agentic search. Returns a `task_id` for polling. ```http theme={null} POST https://api.sixtyfour.ai/search/start-deep-search ``` ### Example request ```json theme={null} { "query": "VP of Engineering at Series B SaaS startups in New York", "mode": "people", "max_results": 500 } ``` Save the `task_id` returned in the response — you'll need it to poll for status. Each organization can run up to 5 concurrent deep searches. Additional requests receive a 429 response until an in-flight search completes. *** ## Get search status Poll for progress and results of a running or completed search. ```http theme={null} GET https://api.sixtyfour.ai/search/status/{task_id} ``` `task_id` is the value returned by Start Deep Search or Export. ### Status values | Status | Description | | - | - | | queued | Search is waiting to start. | | running | Search is actively running. Use `progress_message` and `iterations` to show progress. | | completed | Search finished. `resource_handle_id` is available for `"csv"` mode. `search_id` can be used with `/search/query` in either mode. | | failed | Search encountered an error — check the `error` field. | ### Polling recommendations * Poll every 10–15 seconds for active searches * Stop polling when `status` is `completed` or `failed` * Use `progress_message` to show users what the agent is currently doing Once `status` is `completed`, use `search_id` with `/search/query` for paginated JSON, or `resource_handle_id` with `/search/download` for the CSV. *** ## Download search results Get a signed URL to download the result CSV for a completed search. ```http theme={null} GET https://api.sixtyfour.ai/search/download ``` Pass `resource_handle_id` (the value returned in the completed status response) as a query parameter. Fetch the returned `url` with your `x-api-key` header. The signed token does not replace authentication. Downloads are served directly; disable redirects when sending your API key. Signed URLs expire after 15 minutes (900 seconds). Download the file immediately or request a new URL before it expires. *** ## Query search results Browse results from a completed deep search as paginated JSON. ```http theme={null} POST https://api.sixtyfour.ai/search/query ``` This endpoint is shared with filter search. Pass `search_id` to browse a completed deep search, or use `simple_filters`/`filters`/`parsed_query` for fresh searches. For pagination semantics, request flow, and the effective result cap, see [Filter Search → Search query](/api-reference/search/filter-search#search-query). ### Example: browse deep search results ```json theme={null} { "search_id": "e3b0c442-98fc-1c14-9afb-f4c8996fb924", "page_size": 25 } ``` ### Example: next page ```json theme={null} { "cursor": "eyJpZCI6I..." } ``` Cursor requests must send only `{"cursor": "..."}`. Do not include `search_id`, `page_size`, or other fields. `/search/query` also accepts `exclude_entity_ids` and `exclude_list_ids` on the first request to suppress known people or companies from results. A `search_id` stores the query, not exclusions — pass exclusions again when browsing its results. See [Search Exclusions](/api-reference/search/search-exclusions). *** ## Export search results Generate a CSV from a completed search. Runs asynchronously — poll for status, then download. ```http theme={null} POST https://api.sixtyfour.ai/search/export ``` Provide `search_id` to export an existing search, or `parsed_query` for a direct query (advanced) — not both. ### Export flow 1. POST to `/search/export` with `search_id` — receive `task_id`. 2. Poll `GET /search/status/{task_id}` until `completed` — response includes `resource_handle_id`. 3. Download via `GET /search/download?resource_handle_id=...`. If an export is already in progress for the given `search_id`, the API returns 409. Poll the existing `task_id` instead of starting a new export. *** ## Example usage ```bash cURL theme={null} # 1. Start a deep search curl -X POST "https://api.sixtyfour.ai/search/start-deep-search" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "VP of Engineering at Series B SaaS startups in New York", "mode": "people", "max_results": 500 }' # 2. Poll for status (use task_id from step 1) curl -X GET "https://api.sixtyfour.ai/search/status/TASK_ID" \ -H "x-api-key: YOUR_API_KEY" # 3. Download results (use resource_handle_id from step 2) curl -X GET "https://api.sixtyfour.ai/search/download?resource_handle_id=RESOURCE_HANDLE_ID" \ -H "x-api-key: YOUR_API_KEY" ``` ```python Python theme={null} import requests import time API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.sixtyfour.ai" headers = { "x-api-key": API_KEY, "Content-Type": "application/json" } start_response = requests.post( f"{BASE_URL}/search/start-deep-search", headers=headers, json={ "query": "VP of Engineering at Series B SaaS startups in New York", "mode": "people", "max_results": 500 } ) start_response.raise_for_status() task_id = start_response.json()["task_id"] print(f"Search started: {task_id}") while True: status_response = requests.get( f"{BASE_URL}/search/status/{task_id}", headers=headers ) status_response.raise_for_status() status_data = status_response.json() print(f"Status: {status_data['status']} — {status_data.get('progress_message', '')}") if status_data["status"] == "completed": resource_handle_id = status_data["resource_handle_id"] print(f"Total results: {status_data['total_results']}") break elif status_data["status"] == "failed": raise RuntimeError(f"Search failed: {status_data.get('error')}") time.sleep(10) download_response = requests.get( f"{BASE_URL}/search/download", headers=headers, params={"resource_handle_id": resource_handle_id} ) download_response.raise_for_status() download_url = download_response.json()["url"] print(f"Download URL: {download_url}") csv_response = requests.get( download_url, headers=headers, allow_redirects=False ) if csv_response.status_code != 200: raise RuntimeError(f"Download failed: {csv_response.status_code}") with open("search_results.csv", "wb") as f: f.write(csv_response.content) print("Results saved to search_results.csv") ``` ```javascript JavaScript theme={null} const API_KEY = "YOUR_API_KEY"; const BASE_URL = "https://api.sixtyfour.ai"; const headers = { "x-api-key": API_KEY, "Content-Type": "application/json" }; const startResponse = await fetch(`${BASE_URL}/search/start-deep-search`, { method: "POST", headers, body: JSON.stringify({ query: "VP of Engineering at Series B SaaS startups in New York", mode: "people", max_results: 500 }) }); if (!startResponse.ok) throw new Error(`Failed to start: ${await startResponse.text()}`); const { task_id } = await startResponse.json(); console.log(`Search started: ${task_id}`); let resourceHandleId; while (true) { const statusResponse = await fetch( `${BASE_URL}/search/status/${task_id}`, { headers } ); const statusData = await statusResponse.json(); console.log(`Status: ${statusData.status} — ${statusData.progress_message ?? ""}`); if (statusData.status === "completed") { resourceHandleId = statusData.resource_handle_id; console.log(`Total results: ${statusData.total_results}`); break; } else if (statusData.status === "failed") { throw new Error(`Search failed: ${statusData.error}`); } await new Promise((r) => setTimeout(r, 10000)); } const downloadResponse = await fetch( `${BASE_URL}/search/download?resource_handle_id=${resourceHandleId}`, { headers } ); if (!downloadResponse.ok) throw new Error(`Download request failed: ${downloadResponse.status}`); const { url } = await downloadResponse.json(); console.log(`Download URL: ${url}`); const csvResponse = await fetch(url, { headers, redirect: "error" }); if (!csvResponse.ok) throw new Error(`Download failed: ${csvResponse.status}`); const buffer = Buffer.from(await csvResponse.arrayBuffer()); const fs = await import("fs"); fs.writeFileSync("search_results.csv", buffer); console.log("Results saved to search_results.csv"); ``` # Search Exclusions Source: https://docs.sixtyfour.ai/api-reference/search/search-exclusions Suppress known people or companies from search results with inline IDs or reusable saved exclusion lists. ## Use case Exclusions keep known records out of search results — existing customers, accounts your sales team is already working, seed profiles that shouldn't appear in lookalike results, or people and companies that should never be contacted. The API searches first, then removes excluded records before returning results. Matching behavior is unchanged. ```mermaid theme={null} flowchart LR A[Search matches candidates] --> B[Remove inline excluded IDs] B --> C[Remove saved-list members] C --> D[Return eligible rows] ``` ## Errors For error responses (400, 403, 422, etc.), see [Handling Errors](/api-reference/errors). ## Choosing an approach | Need | Use | | - | - | | Exclude a few people in one request | `exclude_entity_ids` | | Exclude a few companies in one request | `exclude_entity_ids` | | Reuse a large suppression list | `exclude_list_ids` | | Build a saved list from IDs in the request | `POST /search/exclusion-lists` with `source.type = "entity_ids"` | | Build a saved list from a CSV upload | `POST /search/exclusion-lists` with `source.type = "resource_handle"` | | Build a saved list from a completed search | `POST /search/exclusion-lists` with `source.type = "search_id"` | ## Supported endpoints | Endpoint | Supports exclusions? | Notes | | - | - | - | | `POST /search/query` | Yes | Primary paginated search endpoint. Send exclusions on the first request. Cursor requests must send only `cursor`. | | `POST /search/start-deep-search` | Yes | Deep search accepts the same exclusion fields. See [Deep search](#deep-search). | | `GET /search/status/{task_id}` | N/A | Polls deep-search status. Completed responses include the `search_id` used for later `/search/query` browsing. | | `POST /search/exclusion-lists` | Yes | Creates reusable saved exclusion lists. | | `GET /search/exclusion-lists` | N/A | Lists saved exclusion lists for the organization. | | `GET /search/exclusion-lists/{list_id}` | N/A | Gets status and size for one saved list. | | `DELETE /search/exclusion-lists/{list_id}` | N/A | Deletes a saved list. | ## Inline exclusions Inline exclusions apply only to the request that sends them. ### People Use `exclude_entity_ids`. In people mode it accepts LinkedIn public IDs or LinkedIn profile URLs: ```json theme={null} { "mode": "people", "query": "VP Engineering in New York", "exclude_entity_ids": ["jane-doe", "https://linkedin.com/in/john-smith"] } ``` ### Companies Company searches use `exclude_entity_ids`: ```json theme={null} { "mode": "company", "simple_filters": { "hq_country_iso2": { "$eq": "US" } }, "exclude_entity_ids": [ "1441", "linkedin.com/company/openai", "stripe.com" ] } ``` Company identifiers can be: * Numeric LinkedIn company IDs * LinkedIn company URLs * LinkedIn company slugs * Exact website domains Identifiers that match no company (unresolvable) or more than one (ambiguous) are skipped. ## Saved exclusion lists Saved lists are reusable, organization-scoped suppression lists. Each list has an `entity_type` of `person` or `company`; a person list can only be used in people search, and a company list only in company search. Creating a list is asynchronous. A successful request returns `202 Accepted` with the list `id` and `status: "pending"`: ```json theme={null} { "id": "11111111-1111-1111-1111-111111111111", "status": "pending" } ``` Poll `GET /search/exclusion-lists/{list_id}` and use the list only after `status` is `ready`. ### Create from inline IDs Use `source.type = "entity_ids"` for both people and companies. People: ```json theme={null} { "name": "Do not contact people", "entity_type": "person", "source": { "type": "entity_ids", "entity_ids": ["jane-doe", "https://linkedin.com/in/john-smith"] } } ``` Companies: ```json theme={null} { "name": "Customer accounts", "entity_type": "company", "source": { "type": "entity_ids", "entity_ids": ["1441", "linkedin.com/company/openai", "stripe.com"] } } ``` ### Create from a CSV upload Upload the CSV first to get a `resource_handle_id`, then create the list: ```json theme={null} { "name": "CSV suppression list", "entity_type": "company", "source": { "type": "resource_handle", "resource_handle_id": "8f14e45f-1111-2222-3333-abcdefabcdef", "id_column": "company_url" } } ``` `id_column` is optional if the API can detect the right column. Recognized column names: | `entity_type` | Recognized columns | | - | - | | `person` | `public_id`, `publicId`, `linkedin_url`, `linkedinUrl` | | `company` | `entity_id`, `company_id`, `companyId`, `linkedin_url`, `linkedinUrl`, `website`, `domain` | ### Create from a completed search Use `source.type = "search_id"` to turn a completed search into a reusable list. The source search mode must match the list `entity_type`: a people search produces a `person` list, a company search a `company` list. ```json theme={null} { "name": "Companies from last search", "entity_type": "company", "source": { "type": "search_id", "search_id": "22222222-2222-2222-2222-222222222222", "max_results": 5000 } } ``` The `source` can carry its own exclusions, so the list is built from the search results minus another list: ```json theme={null} { "name": "Search results except current customers", "entity_type": "company", "source": { "type": "search_id", "search_id": "22222222-2222-2222-2222-222222222222", "max_results": 5000, "exclude_list_ids": ["11111111-1111-1111-1111-111111111111"] } } ``` `search_id` stores only the query — not any exclusions you applied when browsing the results. If the list should match what you previewed, send the same exclusions in the `source`. ## Using a saved list in search Pass the list ID in `exclude_list_ids`: ```json theme={null} { "mode": "company", "simple_filters": { "hq_country_iso2": { "$eq": "US" } }, "exclude_list_ids": ["11111111-1111-1111-1111-111111111111"] } ``` Saved lists and inline exclusions combine in the same request: ```json theme={null} { "mode": "company", "simple_filters": { "hq_country_iso2": { "$eq": "US" } }, "exclude_list_ids": ["11111111-1111-1111-1111-111111111111"], "exclude_entity_ids": ["stripe.com"] } ``` ## Limits | Limit | Value | | - | - | | Saved lists per request (`exclude_list_ids`) | 5 | | Inline IDs per request | 1,000 | | IDs per `entity_ids` source | 10,000 | | Members per saved list | 250,000 | ## Pagination Send exclusions only on the first `/search/query` request. Cursor requests must send only `cursor` — no `exclude_*`, `mode`, `query`, or `page_size`. First page: ```json theme={null} { "mode": "people", "query": "Founders in San Francisco", "exclude_entity_ids": ["jane-doe"], "page_size": 25 } ``` Next page: ```json theme={null} { "cursor": "eyJ..." } ``` With exclusions, a page can contain fewer rows than `page_size` — even zero rows while `has_more` is still `true`. Keep paging until `next_cursor` is `null`. Don't rely on `total_available` to size pagination or count how many records were excluded. Page until `next_cursor` is `null` to retrieve every eligible row. ## Deep search `POST /search/start-deep-search` accepts the same exclusion fields: `exclude_entity_ids` and `exclude_list_ids`. ```json theme={null} { "query": "VP Engineering at Series B SaaS companies in New York", "mode": "people", "max_results": 500, "exclude_list_ids": ["11111111-1111-1111-1111-111111111111"], "exclude_entity_ids": ["jane-doe"] } ``` For `output_mode: "csv"`, Sixtyfour applies exclusions before producing the CSV. For `output_mode: "query_only"`, the returned `search_id` stores the query, not your exclusions — send them again when browsing: 1. Call `POST /search/start-deep-search` and save the returned `task_id`. 2. Poll `GET /search/status/{task_id}` until the search is `completed`. 3. Read `search_id` from the completed status response. 4. Call `POST /search/query` with that `search_id` and any exclusions to apply. ```json theme={null} { "search_id": "22222222-2222-2222-2222-222222222222", "exclude_list_ids": ["11111111-1111-1111-1111-111111111111"], "exclude_entity_ids": ["jane-doe"] } ``` # Search Export Source: https://docs.sixtyfour.ai/api-reference/search/search-export https://api.sixtyfour.ai/openapi.json post /search/export Export search results as a CSV file. Poll /search/status/{task_id} for a presigned download URL when complete. # Deep Search Overview Source: https://docs.sixtyfour.ai/api-reference/search/search-overview Run agentic deep searches with natural language queries to find people or companies at scale. ## Use case Use the Search API to find people or companies using a natural language query — no manual list building required. The search runs asynchronously: you submit a query, poll for progress, and either browse results as paginated JSON or download them as a CSV. Ideal for prospecting, building targeted lead lists, or discovering contacts that match specific criteria across the web. ## Core concepts | Term | Description | | - | - | | `task_id` | Unique identifier returned when a search starts; used to poll for status | | `resource_handle_id` | Identifier for the result file, available once the search completes | | `mode` | Search mode — currently `"people"` (default) to find individual contacts | | `max_results` | Upper bound on how many results to return (default: 1000) | | `output_mode` | Controls result delivery — `"csv"` (default) produces a downloadable file, `"query_only"` lets you browse results as paginated JSON via `/search/query` | | `search_id` | Identifier for a completed search. Use with `/search/query` to browse results or `/search/export` to generate a CSV | ## Execution flows ### CSV download ```mermaid theme={null} theme={null} flowchart LR A[Start Deep Search] --> B[Poll Status] B --> C[Download CSV] ``` 1. Start — `POST /search/start-deep-search`. See [Start Deep Search](/api-reference/search/search-endpoints#start-deep-search). Submit your natural language query and receive a `task_id`. 2. Poll — `GET /search/status/{task_id}`. See [Get Search Status](/api-reference/search/search-endpoints#get-search-status). Poll until `status` is `completed`; the response includes `resource_handle_id`. 3. Download — `GET /search/download`. See [Download Search Results](/api-reference/search/search-endpoints#download-search-results). Request a signed URL with `resource_handle_id`, then download the CSV. The signed URL expires in 15 minutes. ### Paginated JSON ```mermaid theme={null} theme={null} flowchart LR A[Start Deep Search] --> B[Poll Status] B --> C[Browse JSON] C --> D[Export CSV] ``` 1. Start — `POST /search/start-deep-search` with `output_mode: "query_only"`. See [Start Deep Search](/api-reference/search/search-endpoints#start-deep-search). Receive a `task_id`. 2. Poll — `GET /search/status/{task_id}`. See [Get Search Status](/api-reference/search/search-endpoints#get-search-status). When `status` is `completed`, note the `search_id` from the response. 3. Browse — `POST /search/query`. See [Query Search Results](/api-reference/search/search-endpoints#query-search-results). Send the `search_id` to get paginated JSON results and use `cursor` for next pages. 4. Export *(optional)* — `POST /search/export`. See [Export Search Results](/api-reference/search/search-endpoints#export-search-results). Generate a CSV from the same `search_id`. ## Quick start ```bash cURL theme={null} # 1. Start a deep search curl -X POST "https://api.sixtyfour.ai/search/start-deep-search" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "VP of Engineering at Series B SaaS startups in New York", "mode": "people", "max_results": 500 }' # 2. Poll for status (use task_id from step 1) curl -X GET "https://api.sixtyfour.ai/search/status/TASK_ID" \ -H "x-api-key: YOUR_API_KEY" # 3. Download results (use resource_handle_id from step 2) curl -X GET "https://api.sixtyfour.ai/search/download?resource_handle_id=RESOURCE_HANDLE_ID" \ -H "x-api-key: YOUR_API_KEY" ``` ```python Python theme={null} import requests import time API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.sixtyfour.ai" headers = { "x-api-key": API_KEY, "Content-Type": "application/json" } # 1. Start the search start_response = requests.post( f"{BASE_URL}/search/start-deep-search", headers=headers, json={ "query": "VP of Engineering at Series B SaaS startups in New York", "mode": "people", "max_results": 500 } ) start_response.raise_for_status() task_id = start_response.json()["task_id"] print(f"Search started: {task_id}") # 2. Poll until complete while True: status_response = requests.get( f"{BASE_URL}/search/status/{task_id}", headers=headers ) status_response.raise_for_status() status_data = status_response.json() print(f"Status: {status_data['status']} — {status_data.get('progress_message', '')}") if status_data["status"] == "completed": resource_handle_id = status_data["resource_handle_id"] break elif status_data["status"] == "failed": raise RuntimeError(f"Search failed: {status_data.get('error')}") time.sleep(10) # 3. Get signed download URL download_response = requests.get( f"{BASE_URL}/search/download", headers=headers, params={"resource_handle_id": resource_handle_id} ) download_response.raise_for_status() download_url = download_response.json()["url"] print(f"Download URL: {download_url}") ``` ```javascript JavaScript theme={null} const API_KEY = "YOUR_API_KEY"; const BASE_URL = "https://api.sixtyfour.ai"; const headers = { "x-api-key": API_KEY, "Content-Type": "application/json" }; // 1. Start the search const startResponse = await fetch(`${BASE_URL}/search/start-deep-search`, { method: "POST", headers, body: JSON.stringify({ query: "VP of Engineering at Series B SaaS startups in New York", mode: "people", max_results: 500 }) }); if (!startResponse.ok) throw new Error(`Failed to start: ${await startResponse.text()}`); const { task_id } = await startResponse.json(); console.log(`Search started: ${task_id}`); // 2. Poll until complete let resourceHandleId; while (true) { const statusResponse = await fetch( `${BASE_URL}/search/status/${task_id}`, { headers } ); const statusData = await statusResponse.json(); console.log(`Status: ${statusData.status} — ${statusData.progress_message ?? ""}`); if (statusData.status === "completed") { resourceHandleId = statusData.resource_handle_id; break; } else if (statusData.status === "failed") { throw new Error(`Search failed: ${statusData.error}`); } await new Promise((r) => setTimeout(r, 10000)); } // 3. Get signed download URL const downloadResponse = await fetch( `${BASE_URL}/search/download?resource_handle_id=${resourceHandleId}`, { headers } ); const { url } = await downloadResponse.json(); console.log(`Download URL: ${url}`); ``` # Search Query Source: https://docs.sixtyfour.ai/api-reference/search/search-query https://api.sixtyfour.ai/openapi.json post /search/query Run a search query. Provide exactly one query source: query, simple_filters, filters, parsed_query, or search_id. For pagination, pass cursor alone. # Search Status Source: https://docs.sixtyfour.ai/api-reference/search/search-status https://api.sixtyfour.ai/openapi.json get /search/status/{task_id} Query the status of any async search task (deep search or export). # Start Deep Search (Async) Source: https://docs.sixtyfour.ai/api-reference/search/start-deep-search-async https://api.sixtyfour.ai/openapi.json post /search/start-deep-search Start a deep, agentic search from a natural language query. Returns a task ID to poll. # Start Lookalike Search (Async) Source: https://docs.sixtyfour.ai/api-reference/search/start-lookalike-search-async https://api.sixtyfour.ai/openapi.json post /search/lookalike Resolve seeds and start the async lookalike workflow. # Incoming Webhooks Source: https://docs.sixtyfour.ai/api-reference/webhooks/incoming Trigger workflows by POSTing data to Sixtyfour. ## Use Case Send data into Sixtyfour from an external system to start a workflow run. Use incoming webhooks to wire CRMs, schedulers, internal tools, or any service that needs to kick off enrichment automatically — no manual upload required. An incoming webhook is a workflow whose first block is the **`webhook` source block**. POST a payload to `/workflows/run`, and that payload becomes the workflow's input dataset. ## Headers | Name | Type | Required | Description | | - | - | - | - | | x-api-key | string | Yes | Your Sixtyfour API key | | Content-Type | string | Yes | `application/json` | Incoming webhooks require **API key authentication**. JWT (dashboard session) authentication is rejected when sending a webhook payload. ## Error Responses For error response shapes (400, 422, etc.), see [Handling Errors](/api-reference/errors). ## Configure the webhook block The `webhook` block is configured inside the workflow editor. It defines the shape of the data your workflow expects. | Spec field | Type | Required | Description | | - | - | - | - | | `input_schema` | object | Recommended | Map of column name → field schema. Declares the columns the workflow expects so they can be referenced by downstream blocks. | | `webhook_input` | string (JSON) | No | Default sample payload used when the workflow is run without an external POST (e.g. test runs in the editor). | | `dataframe_type` | string | Yes | `LEAD` or `COMPANY`. Determines which downstream blocks can consume the output. | Each row in your POSTed payload becomes one row in the workflow's input dataset. Columns not declared in `input_schema` are still passed through and surfaced as advisory columns to downstream blocks. See the [Workflow Blocks reference](/api-reference/workflows/workflow-blocks) for where this fits in the block catalog. ## Trigger a workflow run POST your payload to `/workflows/run` with the workflow ID as a query parameter. ```http theme={null} POST https://api.sixtyfour.ai/workflows/run?workflow_id={workflow_id} ``` ### Request Body | Field | Type | Description | | - | - | - | | `webhook_payload` | array or object | Input data for the workflow. Can be a list of records (multi-row) or a single object (one row). | | `specs_override` | object | Optional. Override specs of the source block. See the workflow editor's "Workflow API Reference" for block-specific options. | ### Example Request ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/workflows/run?workflow_id=wf_abc123" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "webhook_payload": [ { "company_name": "Acme Corp", "website": "acme.com", "industry": "Technology" }, { "company_name": "TechStart Inc", "website": "techstart.io", "industry": "Software" } ] }' ``` ```python Python theme={null} import requests response = requests.post( "https://api.sixtyfour.ai/workflows/run", headers={ "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json", }, params={"workflow_id": "wf_abc123"}, json={ "webhook_payload": [ {"company_name": "Acme Corp", "website": "acme.com"}, {"company_name": "TechStart Inc", "website": "techstart.io"}, ] }, ) job_id = response.json()["job_id"] ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.sixtyfour.ai/workflows/run?workflow_id=wf_abc123", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json", }, body: JSON.stringify({ webhook_payload: [ { company_name: "Acme Corp", website: "acme.com" }, { company_name: "TechStart Inc", website: "techstart.io" }, ], }), } ); const { job_id } = await response.json(); ``` ### Response (200) ```json theme={null} { "status": "queued", "workflow_id": "wf_abc123", "job_id": "run_xyz789", "cache_config": null } ``` Use the returned `job_id` to track execution. See [Workflow Execution](/api-reference/workflows/workflows-execution) for status, results, and cancellation endpoints. ## Rules and constraints * Source block must be `webhook`. Workflows whose source block is anything else cannot be triggered with `webhook_payload`. * API key authentication only. JWT (dashboard session) auth is rejected when sending `webhook_payload`. * `read_csv` source blocks are triggered with `specs_override.resource_handle_id` (from `/storage/csv/upload`), not `webhook_payload`. See [Workflow Execution](/api-reference/workflows/workflows-execution). * Payloads missing required columns from `input_schema`, or malformed JSON, are rejected with a validation error. * Workflows must have exactly one source block. The webhook block is consumed once per run. ## Receiving results Incoming webhooks only handle the trigger. To receive workflow output back at your URL, add an `outgoing_webhook` block to the workflow — see [Outgoing Webhooks](/api-reference/webhooks/outgoing). Alternatively, poll for completion and download results manually via the [Workflow Execution](/api-reference/workflows/workflows-execution) endpoints. # Outgoing Webhooks Source: https://docs.sixtyfour.ai/api-reference/webhooks/outgoing Receive HTTP notifications from Sixtyfour when async jobs and workflows complete. ## Use Case Sixtyfour POSTs to your URL when work completes — so you can react in real time instead of polling. Outgoing webhooks come from two sources, both delivered through the same signed, retried HTTP pipeline: | Source | Trigger | Payload | | - | - | - | | **Async-job webhook** | Pass `webhook_url` in the body of an async endpoint | Job result envelope | | **Workflow `outgoing_webhook` block** | Add the block to a workflow | Workflow run envelope with results | Headers, signing, retry behavior, and verification are identical for both sources — only the payload shape differs. ## Source 1: Async-job webhooks All async enrichment endpoints accept an optional `webhook_url` parameter. When the job completes, Sixtyfour POSTs the result to that URL. Poll `/job-status/{task_id}` if no callback arrives within your application's deadline. ### Supported endpoints | Endpoint | Description | | - | - | | `/find-email-async` | Single email enrichment | | `/find-phone-async` | Single phone enrichment | | `/find-email-bulk-async` | Bulk email enrichment (up to 100 leads) | | `/find-phone-bulk-async` | Bulk phone enrichment (up to 100 leads) | | `/people-intelligence-async` | People intelligence | | `/company-intelligence-async` | Company intelligence | | `/research-agent-async` | Research agent | | `/qa-agent-async` | QA agent evaluation | ### Request Add the optional `webhook_url` parameter to your async request: ```json theme={null} POST /find-email-async { "lead": { "name": "John Doe", "company": "Acme Inc", "linkedin_url": "https://linkedin.com/in/johndoe" }, "webhook_url": "https://your-server.com/webhooks/sixtyfour" } ``` The async endpoint immediately returns a `task_id`: ```json theme={null} { "task_id": "550e8400-e29b-41d4-a716-446655440000", "status": "RUNNING", "total_leads": 1, "mode": "PROFESSIONAL" } ``` ### Payload — successful job ```json theme={null} { "task_id": "550e8400-e29b-41d4-a716-446655440000", "status": "completed", "task_type": "find_email", "result": { "leads": [ { "name": "John Doe", "company": "Acme Inc", "email": [["john.doe@acme.com", "OK", "COMPANY"]], "other_emails": [] } ], "total_leads": 1, "emails_found": 1, "cost_cents": 5, "mode": "PROFESSIONAL" }, "error": null } ``` Find Email and Find Phone callbacks wrap records in `result.leads`, including single-record requests. Single-record sync and `/job-status` responses unwrap the record. Find Phone callbacks currently carry email-style metadata: `task_type: "find_email"`, `emails_found`, and an email-based `cost_cents`. Read phone numbers from `result.leads`, correlate callbacks by `task_id`, and retrieve `charge_amount` from `/job-status/{task_id}`. ### Job failures Find Email and Find Phone, including their bulk endpoints, do not send a failure callback when enrichment fails. Failure callbacks are not guaranteed for other endpoints, cancellations, terminations, or job timeouts. Poll `/job-status/{task_id}` if no callback arrives within your application's deadline. ### Payload fields | Field | Type | Description | | - | - | - | | `task_id` | string | Unique identifier for the job. | | `status` | string | `"completed"` for successful job callbacks. | | `task_type` | string | Job type metadata. Match callbacks to requests using `task_id`. | | `result` | object | Completed job result data. | | `error` | string \| null | Null on success. Retrieve job failures through `/job-status/{task_id}`. | ## Source 2: Workflow `outgoing_webhook` block Add an `outgoing_webhook` block to a workflow to POST the full run result to an external URL when the workflow completes. One request per workflow run, not per row. ### Block specs | Field | Type | Default | Description | | - | - | - | - | | `url` | string | required | Destination URL. Validated against SSRF rules — must be a public host. | | `headers` | object | none | Optional HTTP headers to include in the request. Header names are sanitized (CR/LF stripped). | | `timeout_seconds` | float | `10` | Per-attempt request timeout. Range `0 < x ≤ 300`. Internally capped at 150s to fit the activity heartbeat window. | | `include_csv_download_link` | boolean | `false` | If true, waits up to 120s for the previous block's CSV result and includes a signed `results_download_url` in the payload. | The block also adds three columns to the workflow output dataset: `webhook_status_code`, `webhook_response`, and `webhook_success`. ### Payload envelope ```json theme={null} { "event_id": "f7c2a4e0-9b5d-4a1f-8d2c-1e3b5f7a9c0d", "event": "outgoing_webhook.completed", "status": "success", "workflow_id": "wf_abc123", "run_id": "run_xyz789", "organization_id": "org_456", "row_count": 95, "previous_block_id": "blk_2", "previous_block_name": "lead_enrichment", "previous_block_number": 2, "completed_at": "2026-04-22T20:00:00Z", "duration_ms": 1234, "attempt": 1, "results_download_url": "https://...signed-csv-url...", "results": [ { "name": "John Doe", "email": "john@acme.com" } ] } ``` ### Payload fields | Field | Type | Description | | - | - | - | | `event_id` | string (UUID) | Unique per delivery. Use as your dedupe key — same as the `Sixtyfour-Event-Id` header. | | `event` | string | Always `outgoing_webhook.completed`. | | `status` | string | Always `success` for a delivered webhook. Failure is signaled by HTTP status, not this field. | | `workflow_id` | string | The workflow definition ID. | | `run_id` | string | The specific run ID. | | `organization_id` | string | Your organization ID. | | `row_count` | integer | Number of rows in the result, including rows that failed at an earlier step. | | `previous_block_id` | string | ID of the block immediately before the webhook block. | | `previous_block_name` | string | Block type of the previous block (e.g. `lead_enrichment`). | | `previous_block_number` | integer | Sequence number of the previous block. | | `completed_at` | string (ISO 8601 Z) | When the webhook was sent. | | `duration_ms` | integer | How long the webhook block took. | | `attempt` | integer | Delivery attempt counter (starts at 1, increments on retry). | | `results_download_url` | string \| null | Signed CSV download URL when `include_csv_download_link=true`. Expires per signing policy. | | `results` | array \| null | Inline result rows. Present when the result is small enough to embed; otherwise null and `results_download_url` carries the data. | ### Failed rows in the payload The webhook block receives every row, including rows that failed at an earlier step. A failed row has `Failed` in the columns its failing step would have filled, and no values from the steps after it. The `Status` column described in [Failed rows](/api-reference/workflows/workflows-execution#failed-rows) is added to the saved final result, not to this payload. The payload has no per-row outcome field, and an ordinary data value can also be `Failed`, so do not rely on that text or on `row_count` to tell which rows failed. To identify individual failed rows, wait for the run to reach a terminal status and read the outcome column in the final result ([Download results](/api-reference/workflows/workflows-execution#download-results)). Its name is `Status`, or `Workflow status` (possibly with a number) if those names are already taken, as described in [Failed rows](/api-reference/workflows/workflows-execution#failed-rows). When every row fails, the run ends as `failed` rather than `completed`, but its saved results can still be downloaded, so fetch them for `failed` runs too. The run's `failed_row_count` and `failed_rows` metrics provide aggregate counts, not the identities of failed rows. ## Shared delivery infrastructure Everything below applies to **both** sources above. ### Headers When your org has a signing secret configured, every delivery includes: | Header | Value | | - | - | | `Sixtyfour-Signature` | `t=,v1=[,v1=]` | | `Sixtyfour-Event-Id` | UUID per delivery — use as your dedupe key | | `Sixtyfour-Event-Type` | Routing hint (e.g., `find_email`, `outgoing_webhook.completed`) | | `Sixtyfour-Delivery-Attempt` | Attempt counter starting at `1`; increments on each retry | Until you create a signing secret, deliveries are sent unsigned for backward compatibility. ### Signing and verification One signing secret per organization covers all outgoing webhook traffic — async-job webhooks and `outgoing_webhook` blocks alike. Generate it in the dashboard at [Settings → Webhooks](https://app.sixtyfour.ai/settings/webhooks). See [Signing Secrets & Verification](/api-reference/webhooks/signing-secrets) for the full HMAC-SHA256 verification algorithm, working Python (Flask) and Node.js (Express) code samples, rotation behavior, and fail-closed semantics. ### Retry behavior If your endpoint is unavailable or returns a non-2xx status code, we retry delivery with exponential backoff: | Setting | Value | | - | - | | Attempts | Up to 5 attempts per delivery (1 initial attempt and 4 retries) | | Backoff | 1s, 2s, 4s, 8s between attempts | | Timeout | 10 seconds per attempt (async-job webhooks); configurable per `outgoing_webhook` block via `timeout_seconds` | If all retries fail, the webhook is marked as undelivered. The job or workflow itself remains successful and results can still be retrieved via the polling endpoints. ### Best practices * Return a 2xx status code within 10 seconds. Process the payload asynchronously if needed — returning non-2xx triggers retries even for events you intend to ignore. * Dedupe with `Sixtyfour-Event-Id`. Retries can cause the same event to arrive more than once, so treat the event ID as your idempotency key. * Use HTTPS endpoints for payload security. The `outgoing_webhook` block enforces this through its URL validation. * Match incoming deliveries against the `task_id` or `run_id` you initiated and drop unexpected payloads silently with a 2xx response. ## Fallback: polling Webhooks are optional. You can always poll for results if: * Your infrastructure doesn't support inbound webhooks * Webhook delivery fails after all retries * You need to retrieve results at a later time For async jobs: ```bash theme={null} GET /job-status/{task_id} ``` For workflow runs, see the status and download endpoints in [Workflow Execution](/api-reference/workflows/workflows-execution). # Signing Secrets & Verification Source: https://docs.sixtyfour.ai/api-reference/webhooks/signing-secrets Sign and verify Sixtyfour webhook deliveries with HMAC-SHA256. ## Use Case Every outgoing webhook from Sixtyfour can be signed with HMAC-SHA256 so you can verify it actually came from us. One signing secret per organization covers all outgoing webhook traffic: async-job webhooks ([receive results](/api-reference/webhooks/outgoing#source-1-async-job-webhooks)) and the workflow [`outgoing_webhook` block](/api-reference/webhooks/outgoing#source-2-workflow-outgoing_webhook-block). Until you create a signing secret, deliveries are sent **unsigned** for backward compatibility. Once you create one, all subsequent deliveries are signed. ## Generate a signing secret Generate a signing secret in the dashboard at [Settings → Webhooks](https://app.sixtyfour.ai/settings/webhooks). The plaintext secret (`sk_whsec_<64 hex>`) is shown only once at creation — store it immediately in a secret manager. Afterwards only a masked preview (`sk_whsec_••••…`) is displayed. ## Headers on signed deliveries When signing is enabled, every outgoing webhook includes: | Header | Value | | - | - | | `Sixtyfour-Signature` | `t=,v1=[,v1=]` | | `Sixtyfour-Event-Id` | UUID per delivery — use as your dedupe key | | `Sixtyfour-Event-Type` | Routing hint (e.g., `find_email`, `outgoing_webhook.completed`) | | `Sixtyfour-Delivery-Attempt` | Attempt counter starting at `1`; increments on each retry | ## Verifying signatures The signature is `HMAC_SHA256(secret_utf8_bytes, f"{t}.{raw_body}")`. To verify: 1. Parse `t=` (timestamp) and every `v1=` (signature) from the `Sixtyfour-Signature` header. 2. Reject if `|now - t| > 300` (5-minute clock-skew tolerance). 3. Recompute the HMAC against the **raw bytes you received**. 4. Accept if any `v1=` matches. Verify against the raw request body, before JSON parsing. Re-serializing a parsed dict changes the bytes (whitespace, key order) and verification will fail. During rotation the header carries two `v1=` segments (new, then old). Iterate every segment — do not parse the header into a dict, which drops duplicates. ## Example Webhook Verification ```python Python theme={null} import hmac, hashlib, time from flask import Flask, request, abort SECRET = "sk_whsec_..." # store in a secret manager TOLERANCE = 300 app = Flask(__name__) def verify(raw: bytes, header: str, secret: str) -> bool: parts = [p.strip() for p in header.split(",")] try: t = int(next(p.split("=", 1)[1] for p in parts if p.startswith("t="))) except (StopIteration, ValueError): return False if abs(int(time.time()) - t) > TOLERANCE: return False expected = hmac.new( secret.encode("utf-8"), f"{t}.".encode("utf-8") + raw, hashlib.sha256, ).hexdigest() return any( p.startswith("v1=") and hmac.compare_digest(expected, p.split("=", 1)[1]) for p in parts ) @app.post("/webhooks/sixtyfour") def handle(): raw = request.get_data() # raw bytes, before JSON parse sig = request.headers.get("Sixtyfour-Signature", "") if not verify(raw, sig, SECRET): abort(400) event = request.get_json() return ("", 200) ``` ```javascript JavaScript theme={null} import crypto from "crypto"; import express from "express"; const SECRET = process.env.SIXTYFOUR_WEBHOOK_SECRET; const TOLERANCE = 300; const app = express(); app.use(express.raw({ type: "application/json" })); function verify(raw, header, secret) { const parts = header.split(",").map((p) => p.trim()); const tPart = parts.find((p) => p.startsWith("t=")); if (!tPart) return false; const t = parseInt(tPart.slice(2), 10); if (!Number.isFinite(t)) return false; if (Math.abs(Math.floor(Date.now() / 1000) - t) > TOLERANCE) return false; const expected = crypto .createHmac("sha256", secret) .update(Buffer.concat([Buffer.from(`${t}.`), raw])) .digest("hex"); return parts.some((p) => { if (!p.startsWith("v1=")) return false; const got = Buffer.from(p.slice(3), "utf8"); const exp = Buffer.from(expected, "utf8"); return got.length === exp.length && crypto.timingSafeEqual(got, exp); }); } app.post("/webhooks/sixtyfour", (req, res) => { if (!verify(req.body, req.header("Sixtyfour-Signature") || "", SECRET)) { return res.status(400).send("invalid signature"); } const event = JSON.parse(req.body.toString("utf8")); res.status(200).send("OK"); }); ``` ## Rotation Rotate from the dashboard. The default overlap window is **24 hours**: during the window we dual-sign every delivery with both old and new secrets so you can deploy your new secret without dropping events. Set the overlap to **0** for an immediate cut-over (e.g. suspected compromise). Two rotation rules to remember in your verification code: * The `Sixtyfour-Signature` header carries multiple `v1=` segments during overlap. You should iterate every segment — accept the delivery if any one matches. See the code examples above for reference. * You should not parse the header into a dictionary keyed by name — that silently drops the second `v1=`. ## Fail-closed behavior If signing is configured but we cannot load your secret at delivery time we abort delivery rather than silently downgrade to unsigned. Once you opt into signing, every payload is signed or none are. If deliveries unexpectedly stop after enabling signing, results remain retrievable through the polling fallbacks: * Async jobs: `GET /job-status/{task_id}` * Workflow runs: see [Workflow Execution](/api-reference/workflows/workflows-execution) ## Troubleshooting For signature verification failures and signing-related delivery issues, see [Handling Errors](/api-reference/errors#issue-webhook-signature-verification-fails). # Create Schedule Source: https://docs.sixtyfour.ai/api-reference/workflow-schedules/create-schedule https://api.sixtyfour.ai/openapi.json post /schedules Create a schedule and register its Temporal Schedule. Bound to the caller. The schedule lives in its WORKFLOW's team (runs bill + attribute there), so cross-team creation is blocked for every caller — 404 to mask existence, matching the workflow list's partitioning. # Delete Schedule Source: https://docs.sixtyfour.ai/api-reference/workflow-schedules/delete-schedule https://api.sixtyfour.ai/openapi.json delete /schedules/{schedule_id} Delete a schedule. # Get Schedule Source: https://docs.sixtyfour.ai/api-reference/workflow-schedules/get-schedule https://api.sixtyfour.ai/openapi.json get /schedules/{schedule_id} Get a schedule. # List Schedules Source: https://docs.sixtyfour.ai/api-reference/workflow-schedules/list-schedules https://api.sixtyfour.ai/openapi.json get /schedules List schedules in the caller's active team, optionally for one workflow. # Update Schedule Source: https://docs.sixtyfour.ai/api-reference/workflow-schedules/update-schedule https://api.sixtyfour.ai/openapi.json patch /schedules/{schedule_id} Update a schedule. Reactivating a revoked schedule re-binds it to the current caller (the 're-own' / 're-bind' action) and clears disabled_reason. The schedule keeps its team across a re-bind — the fire-time check pauses it again if the new owner can't act there. # Cancel Workflow Run Source: https://docs.sixtyfour.ai/api-reference/workflow/cancel-workflow-run https://api.sixtyfour.ai/openapi.json post /workflows/cancel Cancel an in-progress workflow run. # Create Workflow Source: https://docs.sixtyfour.ai/api-reference/workflow/create-workflow https://api.sixtyfour.ai/openapi.json post /workflows/create_workflow Create a new workflow from a block graph definition. # Delete Workflow Source: https://docs.sixtyfour.ai/api-reference/workflow/delete-workflow https://api.sixtyfour.ai/openapi.json post /workflows/delete_workflow Permanently delete a workflow definition. Existing run history is preserved. # Get Workflow Source: https://docs.sixtyfour.ai/api-reference/workflow/get-workflow https://api.sixtyfour.ai/openapi.json get /workflows/{workflow_id} Retrieve a workflow's full details, including its block graph definition. # Get Workflow Run (Live Status) Source: https://docs.sixtyfour.ai/api-reference/workflow/get-workflow-run-live-status https://api.sixtyfour.ai/openapi.json get /workflows/runs/{run_id}/live_status Get the live status and per-block progress for a workflow run. # List Available Blocks Source: https://docs.sixtyfour.ai/api-reference/workflow/list-available-blocks https://api.sixtyfour.ai/openapi.json get /workflows/blocks List all workflow block types with their input specs schemas. # List Run Result Downloads Source: https://docs.sixtyfour.ai/api-reference/workflow/list-run-result-downloads https://api.sixtyfour.ai/openapi.json get /workflows/runs/{run_id}/results/download-links List a workflow run's results with 15-minute signed download URLs. # List Workflow Runs Source: https://docs.sixtyfour.ai/api-reference/workflow/list-workflow-runs https://api.sixtyfour.ai/openapi.json get /workflows/runs List recent workflow runs for the organization, sorted by most recent first. Optionally filter by `status` (`running`, `completed`, `cancelled`, or `active` for queued+running) and `workflow_id`. # List Workflows Source: https://docs.sixtyfour.ai/api-reference/workflow/list-workflows https://api.sixtyfour.ai/openapi.json get /workflows List the workflows in the organization the caller can access. Returns lightweight metadata without block definitions; use `GET /workflows/{workflow_id}` for the full definition. # Run Workflow Source: https://docs.sixtyfour.ai/api-reference/workflow/run-workflow https://api.sixtyfour.ai/openapi.json post /workflows/run Start a workflow run and return a job ID for tracking. Supports per-run spec overrides via `specs_override` and webhook input via `webhook_payload`. API-triggered `read_csv` workflows must provide `specs_override.resource_handle_id` from `/storage/csv/upload`. # Update Workflow Source: https://docs.sixtyfour.ai/api-reference/workflow/update-workflow https://api.sixtyfour.ai/openapi.json post /workflows/update_workflow Update a workflow's metadata or block definition. Creates the workflow if it doesn't exist. # Workflow Blocks Source: https://docs.sixtyfour.ai/api-reference/workflows/workflow-blocks Reference for all workflow block types supported by the Workflows API. ## Use case Discover which block types you can use when building workflows. Use `GET /workflows/blocks` to retrieve the full list and specs schemas programmatically. This page provides a quick reference for common use cases. See the full request/response schema and parameters in the API Reference. ## Pricing See [Credits & Pricing Guide](/guides/credits-and-pricing) for credit costs. ## Errors For error responses (400, 403, 404, etc.), see [Handling Errors](/api-reference/errors). ## Get available blocks Retrieve the list of all workflow block types supported by the API, along with their specs schemas. ```http theme={null} GET https://api.sixtyfour.ai/workflows/blocks ``` Returns an array of block descriptors. Each entry includes the block's name (used as `block_name` when building a workflow) and a `specs_schema` object describing its configurable fields. ## Available blocks The UI name is the name of the block as it appears in the Workflow Builder. | Block Name | UI Name | Use Case | | - | - | - | | `append_to_notebook` | Notebook | Append or upsert workflow rows into a notebook (supports duplicate detection and column mapping) | | `backlinks_referring_domains` | Backlinks | Get referring domains for a target domain or URL, with pagination | | `breached_db_search` | Breached Database Search | Look up whether email addresses appear in known data breaches | | `company_enrichment` | Enrich Company | Enrich company records; optionally find people, define lead struct, or use a research plan | | `company_to_leads` | Extract Leads | Expand company rows into individual lead records (select company/lead fields) | | `deduplicate` | Deduplicate | Remove duplicate rows from input based on specified column(s); choose first, last, or remove all | | `deduplicate_wrt_notebook` | Deduplicate with Notebook | Deduplicate against a notebook tab (single or multi-column matching); optionally keep only rows that exist in notebook | | `filter` | Filter | Filter rows by pandas query string or field-based conditions (include, exclude, contains, not\_contains) | | `find_email` | Email address | Find professional or personal email addresses for leads; optionally verify emails. Can skip rows that already have the email. See [Find Email](#find-email). | | `find_phone` | Phone | Find phone numbers for leads | | `find_subdomains` | Find Subdomains | Find subdomains for a domain (with optional time filters) | | `github_find_people` | GitHub Find People | Find people from GitHub repos: contributors, stargazers, or watchers; requires integration and repository column | | `github_search` | GitHub Search | Search GitHub for repositories, users, or code; requires integration, search type, and query | | `google_maps_search` | Google Maps | Search Google Maps for places by query, location, Place ID, or CID; returns phone numbers and business details; supports pagination | | `group_by` | Group By | Group rows by column(s) and aggregate others (first, last, count, sum, list, concat, min, max); optionally nest data | | `hubspot_import` | HubSpot Import | Import contacts or companies from HubSpot; requires integration and object type | | `kaggle_leaderboard` | Kaggle | Get leaderboard participants from Kaggle competitions; requires competition column | | `lead_enrichment` | Enrich People | Enrich lead records with return fields and optional research plan | | `leads_to_company` | Leads to Company | Group leads into company rows with nested leads list; inverse of `company_to_leads` | | `monitor` | Monitor | Start a monitor over the rows that reach this block, which then keeps their watched fields current on its own schedule. Terminal block; requires Monitors to be enabled. See [Monitor](#monitor). | | `llm_openai` | Generate Text | Generate text via prompt template over input rows; use `{column_name}` for column substitution | | `outgoing_webhook` | Outgoing Webhooks | POST workflow results to an external URL on completion. See [Outgoing Webhooks](/api-reference/webhooks/outgoing); signed with HMAC-SHA256 if the org has a signing secret — see [Signing Secrets & Verification](/api-reference/webhooks/signing-secrets). | | `qa_agent` | N/A | Run an agent that answers questions using tools; configurable max tool calls | | `read_notebook` | Read Notebook | Read data from a notebook tab; requires `notebook_id`, `tab_id`, and `dataframe` type (LEAD or COMPANY) | | `remove_columns` | Remove Columns | Remove specified columns from the data | | `research_agent` | Web Research Agent | Run research with optional struct and research plan | | `reverse_email` | Reverse Email | Look up person or company info from an email address | | `reverse_phone` | Reverse Phone | Look up person or company info from a phone number | | `scrape_web` | Scrape Website | Scrape web content from URLs in a column; output as markdown or HTML; supports geo, JS rendering, proxies | | `search` | Search | Load search results persisted to storage as CSV, or accept direct results; requires dataframe type | | `send_slack_message` | Slack | Send Slack messages to channels or DMs; use `{column}` for substitution, `{@column}` to tag by email | | `tiktok_get_following` | TikTok | Get TikTok following list from profile URLs or handles in a column | | `transform_data` | LLM Output | Generate structured or unstructured text using configurable LLM models | | `verify_email` | Verify Email | Verify email deliverability (default column: email) | | `verify_phone` | Verify Phone | Verify phone numbers and match them to the lead; requires `phone_column` and `name_column`. See [Verify Phone](#verify-phone). | | `webhook` | Incoming Webhooks | Accept external input via webhook; requires input\_schema and `dataframe_type` (workflow entry point). See [Incoming Webhooks](/api-reference/webhooks/incoming). | | `x_search` | X | Search X/Twitter for profiles by query (e.g., "AI researchers", "crypto founders in NYC") | | `yc_batches` | N/A | Fetch Y Combinator batch metadata | | `yc_companies` | Y Combinator | Search or filter Y Combinator companies by batch, industry, tags, or status | ### Find Email `find_email` finds a work email (`mode: "PROFESSIONAL"`) or a personal email (`mode: "PERSONAL"`) for each row. **Inputs** | Field | Default | Description | | - | - | - | | `mode` | — | Required. `PROFESSIONAL` finds a work email; `PERSONAL` finds a personal email. | | `skip_rows_with_existing_input` | `false` | When `true`, rows that already have the email skip the lookup, are not charged, and are returned unchanged with status `PROVIDED`. | **Skipping rows that already have the email** With `skip_rows_with_existing_input: true`, put the email you already have in one of these columns: | Mode | Column | Skipped when | | - | - | - | | `PROFESSIONAL` | `work_email`, `email` or `email_address` | The value is an email on a company domain. A personal address such as gmail.com does not count, so the row is still looked up. | | `PERSONAL` | `personal_email` | The value is an email. | Blank, `N/A` and malformed values count as missing, so the row is looked up. Skipped rows keep their position and input values, cost no credits, and get `email_status` (or `personal_email_status` in `PERSONAL` mode) set to `PROVIDED`. In `PROFESSIONAL` mode the matched work email is also written to `email` when that column is blank or holds a personal address. The run's cost estimate still counts every row, since which rows skip is only known once the run reads them. **Output columns** | Mode | Columns | | - | - | | `PROFESSIONAL` | `email`, `email_status`, `email_type`, `other_emails` | | `PERSONAL` | `personal_email`, `personal_email_status`, `personal_email_type`, `personal_other_emails` | ### Verify Phone `verify_phone` verifies each row's phone number against the person's full name using the configured input column names. All twelve output columns are included. Missing output fields start as null; skipped rows retain any existing verification values. See [Handling Errors](/api-reference/errors#verify-phone-column-mappings) for missing-input behavior. **Inputs** | Field | Default | Description | | - | - | - | | `phone_column` | `phone` | Column containing the phone number. Required. | | `name_column` | `name` | Column containing the person's full name, required for verification. Must match the actual input column, such as `fullName` or `full_name`. | | `email_column` | — | Optional column containing an email address. Populates the `email_*` output columns. | | `ip_address_column` | — | Optional column containing an IP address, used as an additional verification signal. | | `address_street_line_1_column`, `address_city_column`, `address_state_code_column`, `address_postal_code_column`, `address_country_code_column` | — | Optional columns containing postal address parts. Populate the `address_*` output columns. | | `country_hint` | — | Optional ISO 3166-1 alpha-2 region code used to parse numbers written in national format. | **Output columns** | Column | Description | | - | - | | `phone_is_valid` | Whether the number is valid. | | `phone_activity_score` | Activity score for the number. | | `phone_line_type` | Line type of the number. | | `phone_name_match` | Whether the name matches the number's owner. | | `phone_contact_grade` | Contact grade for the phone. | | `email_is_valid` | Whether the email is valid. | | `email_name_match` | Whether the name matches the email's owner. | | `email_contact_grade` | Contact grade for the email. | | `address_is_valid` | Whether the address is valid. | | `address_name_match` | Whether the name matches the address. | | `phone_warnings` | Warnings for the row, such as `Missing Name`. | | `phone_errors` | Error details when the row could not be verified. | ### Monitor `monitor` starts one [monitor](/api-reference/monitors/monitors-overview) over the rows that reach it. It runs once all rows have arrived and returns no rows, so place it last in the workflow. Each workflow run starts its own monitor, which keeps checking on its own schedule after the run ends. **Specs** | Field | Default | Description | | - | - | - | | `key_columns` | — | Columns that identify each row. Required. Rows with no value in any of them are skipped. Rows with the same values are watched once. | | `watched` | — | Watched fields: field name → description, or an object with `type`, `description`, and `subfields`. Required. A column with the same name as a watched field sets that field's starting value. | | `frequency` | `1d` | How often the monitor checks, `1h` to `30d`. Independent of how often the workflow runs. | | `first_check` | `now` | `now` checks right away. `next_tick` waits for the first scheduled fire. | | `tier` | `low` | Depth of the full re-check: `low`, `medium`, or `high`. | | `subject_type` | `company` | `company` or `lead`. | | `research_plan` | — | Instructions for the enrichment that re-derives the fields. | | `name` | watched field names | Name of the monitor the run starts. | A row whose input columns already supply every watched field skips the full first check: it starts from those values, and its checks begin with the lightweight probe. Monitors started by this block have no webhook. Retrieve the monitor a run started with `GET /monitors/for-run/{workflow_run_id}/{block_id}`. See [Monitor Endpoints](/api-reference/monitors/monitors-endpoints#get-the-monitor-a-workflow-run-started). # Workflow Schedules Source: https://docs.sixtyfour.ai/api-reference/workflows/workflow-schedules Create, update, and delete cron-based schedules that trigger workflow runs automatically. ## Use case Trigger a workflow on a recurring cadence without polling or external cron infrastructure. Use these endpoints to create, list, inspect, update, and delete schedules bound to a workflow. See the full request/response schema and parameters in the API Reference. ## Pricing Scheduled runs are billed the same as manual runs. See [Credits & Pricing Guide](/guides/credits-and-pricing) for credit costs. ## Errors For error responses (400, 401, 403, 422, etc.), see [Handling Errors](/api-reference/errors). ## Schedule definition A schedule binds a `workflow_id` to a `cron_expression` and `timezone`: * `workflow_id` — the workflow to run. The workflow must already exist. * `name` — required, up to 120 characters. * `cron_expression` — standard 5-field cron (`minute hour day month weekday`). * `timezone` — IANA timezone, e.g. `America/Los_Angeles`. Defaults to `UTC`. * `description` — optional free-form text. * `is_active` — defaults to `true`. Set to `false` to save a schedule without activating it. Schedules belong to their team. Removing their creator from the team or organization leaves them running unless an administrator selects the pause option. Key-bound schedules pause when their API key is revoked or expires. Archiving the team also pauses its schedules. An automatic pause sets `disabled_reason` on the response. API keys remain valid after their creator leaves unless explicitly revoked. Anyone who still holds a key can continue using it. Selecting key revocation during member removal also pauses schedules bound to those keys. ## Create schedule Create a new schedule for a workflow. ```http theme={null} POST https://api.sixtyfour.ai/schedules ``` Required body fields: `workflow_id`, `name`, and `cron_expression`. ### Example request ```json theme={null} { "workflow_id": "wf_abc123", "name": "Daily lead refresh", "cron_expression": "0 9 * * *", "timezone": "America/New_York", "description": "Re-enrich the prospect list every morning" } ``` ### Example response ```json theme={null} { "id": "a1b2c3d4-5e6f-4789-a123-b4c5d6e7f890", "workflow_id": "wf_abc123", "name": "Daily lead refresh", "description": "Re-enrich the prospect list every morning", "cron_expression": "0 9 * * *", "timezone": "America/New_York", "is_active": true, "disabled_reason": null, "created_by": "f9e8d7c6-b5a4-4321-9876-543210fedcba", "api_key_id": null, "next_run_times": [ "2026-07-17T13:00:00Z", "2026-07-18T13:00:00Z", "2026-07-19T13:00:00Z" ], "created_at": "2026-07-16T18:00:00Z", "updated_at": "2026-07-16T18:00:00Z" } ``` *** ## List schedules Retrieve all schedules for your organization. ```http theme={null} GET https://api.sixtyfour.ai/schedules ``` Optional query parameter: `workflow_id` — filter to schedules for a single workflow. *** ## Get schedule Retrieve a single schedule by ID. ```http theme={null} GET https://api.sixtyfour.ai/schedules/{schedule_id} ``` *** ## Update schedule Update a schedule's name, cadence, timezone, description, or active state. ```http theme={null} PATCH https://api.sixtyfour.ai/schedules/{schedule_id} ``` All body fields (`name`, `cron_expression`, `timezone`, `description`, `is_active`) are optional — only include what you want to change. Reactivating a paused schedule rebinds its execution identity to the current user or API key and clears `disabled_reason`. The schedule remains owned by its team. ### Example request ```json theme={null} { "is_active": false } ``` *** ## Delete schedule Permanently delete a schedule. ```http theme={null} DELETE https://api.sixtyfour.ai/schedules/{schedule_id} ``` Deleting a schedule does not affect the underlying workflow or its run history. *** ## Example usage ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/schedules" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "workflow_id": "WORKFLOW_ID", "name": "Daily lead refresh", "cron_expression": "0 9 * * *", "timezone": "America/New_York" }' ``` ```python Python theme={null} import requests API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.sixtyfour.ai" headers = {"x-api-key": API_KEY, "Content-Type": "application/json"} response = requests.post( f"{BASE_URL}/schedules", headers=headers, json={ "workflow_id": "WORKFLOW_ID", "name": "Daily lead refresh", "cron_expression": "0 9 * * *", "timezone": "America/New_York", }, ) response.raise_for_status() schedule = response.json() next_run = schedule["next_run_times"][0] if schedule["next_run_times"] else "not yet computed" print(f"Created schedule {schedule['id']}, next run at {next_run}") ``` ```javascript JavaScript theme={null} const API_KEY = "YOUR_API_KEY"; const BASE_URL = "https://api.sixtyfour.ai"; const headers = { "x-api-key": API_KEY, "Content-Type": "application/json" }; const response = await fetch(`${BASE_URL}/schedules`, { method: "POST", headers, body: JSON.stringify({ workflow_id: "WORKFLOW_ID", name: "Daily lead refresh", cron_expression: "0 9 * * *", timezone: "America/New_York", }), }); if (!response.ok) { throw new Error(`Failed to create schedule: ${await response.text()}`); } const schedule = await response.json(); const nextRun = schedule.next_run_times[0] ?? "not yet computed"; console.log(`Created schedule ${schedule.id}, next run at ${nextRun}`); ``` # Workflow Execution Source: https://docs.sixtyfour.ai/api-reference/workflows/workflows-execution Run workflows, poll status, cancel runs, and download results. ## Use case Execute batch enrichment pipelines, monitor progress in real time, and retrieve results. Use these endpoints to run workflows with input data, poll for completion, cancel long-running runs, and download enriched output files. See the full request/response schema and parameters in the API Reference. ## Pricing See [Credits & Pricing Guide](/guides/credits-and-pricing) for credit costs. ## Errors For error responses (400, 403, 404, 409, etc.), see [Handling Errors](/api-reference/errors). *** ## Run workflow Execute a workflow and receive a job ID for tracking. ```http theme={null} POST https://api.sixtyfour.ai/workflows/run ``` Pass `workflow_id` as a query parameter. The optional request body accepts `specs_override` (to override the first block's specs for dynamic inputs) and `webhook_payload` (input data for workflows starting with a `webhook` block). | Name | Required | Description | | - | - | - | | workflow\_id | Yes | The workflow to execute | ### Request Body (Optional) | Field | Type | Description | | - | - | - | | specs\_override | object | Override specs of the first block for this run. For workflows starting with `read_csv`, pass `specs_override.resource_handle_id` from `/storage/csv/upload`. | | webhook\_payload | array or object | Input data for workflows starting with a webhook block. API key auth only. Can be a list of records or a single object. See [Incoming Webhooks](/api-reference/webhooks/incoming) for the full spec. | ### Example Request ```json theme={null} { "webhook_payload": [ { "company_name": "Acme Corp", "website": "acme.com", "industry": "Technology" }, { "company_name": "TechStart Inc", "website": "techstart.io", "industry": "Software" } ] } ``` For `read_csv` workflows, use the `handle_id` returned by `/storage/csv/upload`: ```json theme={null} { "specs_override": { "resource_handle_id": "22222222-2222-2222-2222-222222222222" } } ``` Use the `job_id` returned in the response to track workflow execution. Check the workflow editor's "Workflow API Reference" for specific `specs_override` options. To run a workflow on a recurring cadence instead of calling Run Workflow manually, see [Workflow Schedules](/api-reference/workflows/workflow-schedules). *** ## List workflow runs Get execution history for your workflows. ```http theme={null} GET https://api.sixtyfour.ai/workflows/runs ``` Optional query parameters: `status` (`running`, `completed`, `cancelled`, or `active`), `workflow_id`, and `limit` (1–500, default 100). Results are sorted by most recent first (`queued_at` descending). For failed runs, `error_message` and `status_reason` may be populated. *** ## Get live status Monitor real-time progress of a running workflow. ```http theme={null} GET https://api.sixtyfour.ai/workflows/runs/{run_id}/live_status ``` `run_id` is the same as the `job_id` returned by Run Workflow. ### Status values | Status | Description | | - | - | | queued | Waiting to start | | running | Currently executing | | completed | Finished. Some rows may still have failed; see [Failed rows](#failed-rows) | | failed | Encountered an error, or every row failed before the end of the workflow | | cancelled | Manually cancelled | ### Polling recommendations * Poll every 5–10 seconds for running workflows * Stop polling when `overall_status` is `completed`, `failed`, or `cancelled` * Use `overall_progress_percentage` for progress bars *** ## Cancel run Stop an in-progress workflow execution. ```http theme={null} POST https://api.sixtyfour.ai/workflows/cancel ``` Pass `job_id` (from the Run Workflow response) as a query parameter. The request body is optional — send an empty object `{}` or omit. If the run has already completed, failed, or been cancelled, the API returns the current status without changes. Partial results may be available. *** ## Download results Get signed download URLs for workflow results. ```http theme={null} GET https://api.sixtyfour.ai/workflows/runs/{run_id}/results/download-links ``` Returns one entry per result file. Each block may produce separate files; results are typically in CSV format. Signed URLs expire after 15 minutes (900 seconds). Download files immediately or request new links. *** ## Failed rows A row fails at a step when that step still cannot process it after its retries. A failed row: * skips every later step and is not charged for those steps * stays in the output, with `Failed` in the columns the failing step would have filled * is still sent to an outgoing webhook step The final result file ends with a `Status` column set to `Succeeded` or `Failed` for each row. If your data already has a column named `Status`, it is kept as is and the added column is named `Workflow status`. If that name is also taken, the added column uses the first available numbered name, starting with `Workflow status 2`. A run with some failed rows finishes as `completed`. It finishes as `failed` only when no row made it through the workflow. List workflow runs returns the counts in each run's `metrics`: | Field | Description | | - | - | | `failed_row_count` | Total rows that failed, across all steps | | `failed_rows` | Per step: `block_id`, `block_name`, `sequence_number`, and `count` of rows that failed at that step | | `failed_steps` | Block IDs of steps that every row had already failed before reaching | These fields are omitted when no row failed. *** ## Example usage ```python Python theme={null} import requests import time API_KEY = "YOUR_API_KEY" WORKFLOW_ID = "YOUR_WORKFLOW_ID" BASE_URL = "https://api.sixtyfour.ai" headers = {"x-api-key": API_KEY, "Content-Type": "application/json"} run_response = requests.post( f"{BASE_URL}/workflows/run?workflow_id={WORKFLOW_ID}", headers=headers, json={ "webhook_payload": [ {"company_name": "Acme Corp", "website": "acme.com"}, {"company_name": "TechStart", "website": "techstart.io"} ] } ) run_response.raise_for_status() job_id = run_response.json()["job_id"] while True: status = requests.get( f"{BASE_URL}/workflows/runs/{job_id}/live_status", headers=headers ).json() if status["overall_status"] in ["completed", "failed", "cancelled"]: break time.sleep(5) if status["overall_status"] == "completed": links = requests.get( f"{BASE_URL}/workflows/runs/{job_id}/results/download-links", headers=headers ).json() for r in links: print(f"{r['filename']}: {r['download_url']}") ``` ```javascript JavaScript theme={null} const API_KEY = "YOUR_API_KEY"; const WORKFLOW_ID = "YOUR_WORKFLOW_ID"; const BASE_URL = "https://api.sixtyfour.ai"; const headers = { "x-api-key": API_KEY, "Content-Type": "application/json" }; const runResponse = await fetch( `${BASE_URL}/workflows/run?workflow_id=${WORKFLOW_ID}`, { method: "POST", headers, body: JSON.stringify({ webhook_payload: [ { company_name: "Acme Corp", website: "acme.com" }, { company_name: "TechStart", website: "techstart.io" } ] }) } ); if (!runResponse.ok) { const err = await runResponse.json(); throw new Error(`Failed to start workflow: ${JSON.stringify(err)}`); } const { job_id } = await runResponse.json(); let status; while (true) { const statusResponse = await fetch( `${BASE_URL}/workflows/runs/${job_id}/live_status`, { headers } ); status = await statusResponse.json(); if (["completed", "failed", "cancelled"].includes(status.overall_status)) { break; } await new Promise((r) => setTimeout(r, 5000)); } if (status.overall_status === "completed") { const linksResponse = await fetch( `${BASE_URL}/workflows/runs/${job_id}/results/download-links`, { headers } ); const links = await linksResponse.json(); links.forEach((r) => console.log(`${r.filename}: ${r.download_url}`)); } ``` # Workflow Management Source: https://docs.sixtyfour.ai/api-reference/workflows/workflows-management List, create, update, and delete workflows via the Workflows API. ## Use case Create, version, and manage reusable enrichment pipelines programmatically. Use these endpoints to list existing workflows, inspect block definitions, create new pipelines from code, update configurations, or remove workflows you no longer need. See the full request/response schema and parameters in the API Reference. ## Pricing See [Credits & Pricing Guide](/guides/credits-and-pricing) for credit costs. ## Errors For error responses (400, 403, 404, etc.), see [Handling Errors](/api-reference/errors). For a full reference of available block types and their use cases, see [Workflow Blocks](/api-reference/workflows/workflow-blocks). ## Workflow definition A workflow definition is a directed graph of `blocks` connected by `edges`: * **`blocks`** — each block has a `sequence_number`, `block_type` (see [Workflow Blocks](/api-reference/workflows/workflow-blocks)), `block_name`, optional `block_id`, and a `specs` object. * **`edges`** — each edge has a `from_block_id`, a `to_block_id`, and an optional `condition`. This shape is used in both Create Workflow and Update Workflow requests. *** ## List workflows Retrieve all workflows in your organization. ```http theme={null} GET https://api.sixtyfour.ai/workflows ``` Returns a lightweight list of workflows without block definitions. Use `GET /workflows/{workflow_id}` to retrieve a workflow with its full block graph. *** ## Get workflow Retrieve a specific workflow including its complete block graph. ```http theme={null} GET https://api.sixtyfour.ai/workflows/{workflow_id} ``` *** ## Create workflow Create a new workflow with a block graph definition. ```http theme={null} POST https://api.sixtyfour.ai/workflows/create_workflow ``` Required body fields: `workflow_name`, `workflow_description`, and `workflow_definition`. Pass an optional `id` containing a valid UUID to use a custom workflow ID; otherwise one is auto-generated. ### Example request ```json theme={null} { "workflow_name": "Contact Validation", "workflow_description": "Verifies contact email addresses", "workflow_definition": { "blocks": [ { "sequence_number": 1, "block_type": "io", "block_name": "webhook", "block_id": "input", "specs": { "dataframe_type": "LEAD", "input_schema": { "email": { "type": "string" } } } }, { "sequence_number": 2, "block_type": "enrichment", "block_name": "verify_email", "block_id": "validate", "specs": { "email_column": "email" } } ], "edges": [ { "from_block_id": "input", "to_block_id": "validate" } ] } } ``` Definitions are validated before saving. For requests authenticated with an API key, an invalid definition is rejected with `400` and nothing is stored. See [Validation errors](#validation-errors) for the response and for the checks that only happen when a run starts. *** ## Update workflow Update an existing workflow's name, description, or block definition. ```http theme={null} POST https://api.sixtyfour.ai/workflows/update_workflow ``` Pass `workflow_id` as a query parameter. All body fields (`workflow_name`, `workflow_description`, `workflow_definition`) are optional — only include what you want to change. The `workflow_definition` shape is the same as Create Workflow above. Performs upsert: creates the workflow if it doesn't exist. For API-key requests an invalid definition is rejected with `400` and the last valid saved definition is left unchanged — see [Validation errors](#validation-errors). *** ## Validation errors `create_workflow` and `update_workflow` run the full workflow validator before saving: schema and edge checks, block type compatibility, required block fields, and every column a block references against the columns its upstream blocks produce. For API-key requests, any failed check returns `400` with one entry per problem: ```json theme={null} { "detail": { "message": "Workflow validation failed", "validation_errors": [ { "field": "blocks[1].specs.email_column", "code": "COLUMN_NOT_FOUND", "message": "Email column \"work_email\" not found in upstream data", "blocking": true, "context": { "block_id": "verify", "block_name": "verify_email", "missing_columns": ["work_email"], "available_columns": ["name", "email", "company"], "suggestion": "Did you mean \"email\"? Available columns: name, email, company", "suggested_replacement": { "email_column": "email" }, "summary": "Column \"work_email\" not found in the data from the previous block. Did you mean \"email\"?" } } ] } } ``` | Field | Meaning | | - | - | | `field` | Path to the offending value in your definition, e.g. `blocks[1].specs.email_column`. Block indexes follow the order of `blocks` in your request. | | `code` | Stable identifier for the rule, e.g. `COLUMN_NOT_FOUND`, `MISSING_REQUIRED_FIELD`, `INVALID_FIELD_NAME`, `INVALID_SPECS`, `TYPE_INCOMPATIBLE`, `MULTIPLE_SOURCE_BLOCKS`. | | `message` | Precise description of the problem. | | `blocking` | Always `true` on a `400`; findings the executor can tolerate at runtime are returned as `warnings` on successful saves instead. | | `context.block_id` / `context.block_name` | The block the finding belongs to, using the `block_id` you supplied. Absent for graph-level findings such as disconnected blocks. | | `context.missing_columns` / `context.available_columns` | For column findings: what you referenced and what actually exists at that point in the graph. `available_columns` lists at most 50 names; `available_column_count` carries the total when truncated. | | `context.suggestion` | Optional human-readable remediation hint, such as the closest matching column and the columns available. Not machine-applicable. | | `context.suggested_replacement` | Optional. When one available column is a close match, a spec fragment you can merge into that block's `specs` verbatim to fix the finding. | | `context.summary` | A one-line, plain-language version of the finding. | Fix the listed fields and resubmit the same request. Invalid field names (declared output fields must be letters, digits and underscores, starting with a letter) and blocks whose specs fail to parse are rejected the same way. A saved definition has passed these checks, but some conditions can only be judged when a run starts: a source block whose data has not been materialized yet (`MISSING_DATA_SOURCE`) does not block the save, but rejects the run with the same `validation_errors` shape. *** ## Delete workflow Permanently delete a workflow. ```http theme={null} POST https://api.sixtyfour.ai/workflows/delete_workflow ``` Pass `workflow_id` as a query parameter. Permanently removes the workflow definition. Historical workflow runs are preserved. # Workflows Overview Source: https://docs.sixtyfour.ai/api-reference/workflows/workflows-overview Create, manage, and execute automated data enrichment pipelines with the Workflows API. ## Use Case Run batch data enrichment and automated processing pipelines. Workflows are ideal for processing many records through multi-step pipelines (e.g., validate emails, enrich companies, export to CSV) without manual intervention. ## Core Concepts The Workflows API models pipelines as directed acyclic graphs (DAGs): | Term | Description | | - | - | | **Workflow** | A reusable pipeline definition containing blocks and edges | | **Block** | A processing step (e.g., webhook input, enrichment, filtering, export) | | **Edge** | A connection between blocks that defines data flow | | **Run** | An execution instance of a workflow | | **Job ID** | A unique identifier for tracking a workflow run | ## Execution Flow ```mermaid theme={null} flowchart LR A[List Workflows] --> B[Run Workflow] B --> C[Poll Live Status] C --> D[Download Results] ``` 1. **List workflows** — Get available workflows in your organization 2. **Run workflow** — Execute with input data; receive a `job_id` 3. **Poll live status** — Monitor progress until `completed`, `failed`, `cancelled`, or `timeout` 4. **Download results** — Get signed URLs for result files (CSV, NDJSON) For workflows that start with a `webhook` block, see [Incoming Webhooks](/api-reference/webhooks/incoming). For recurring, cron-based execution instead of manual runs, see [Workflow Schedules](/api-reference/workflows/workflow-schedules). ## Quick Start Don't want to write code? Use [Sixtyfour's Workflow Builder](https://app.sixtyfour.ai/templates) to create and manage your own workflows. ```bash cURL theme={null} # 1. List available workflows curl -X GET "https://api.sixtyfour.ai/workflows" \ -H "x-api-key: YOUR_API_KEY" # 2. Run a workflow with data curl -X POST "https://api.sixtyfour.ai/workflows/run?workflow_id=WORKFLOW_ID" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "webhook_payload": [ {"company_name": "Acme Corp", "website": "acme.com"} ] }' # 3. Check status (use job_id from step 2) curl -X GET "https://api.sixtyfour.ai/workflows/runs/JOB_ID/live_status" \ -H "x-api-key: YOUR_API_KEY" # 4. Download results curl -X GET "https://api.sixtyfour.ai/workflows/runs/JOB_ID/results/download-links" \ -H "x-api-key: YOUR_API_KEY" ``` ```python Python theme={null} import requests import time API_KEY = "YOUR_API_KEY" BASE_URL = "https://api.sixtyfour.ai" headers = { "x-api-key": API_KEY, "Content-Type": "application/json" } # 1. Run workflow run_response = requests.post( f"{BASE_URL}/workflows/run?workflow_id=wf_abc123", headers=headers, json={ "webhook_payload": [ {"company_name": "Acme Corp", "website": "acme.com"}, {"company_name": "TechStart", "website": "techstart.io"} ] } ) run_response.raise_for_status() job_id = run_response.json()["job_id"] print(f"Workflow started: {job_id}") # 2. Poll for completion while True: status_response = requests.get( f"{BASE_URL}/workflows/runs/{job_id}/live_status", headers=headers ) status = status_response.json() print(f"Status: {status['overall_status']} - {status['overall_progress_percentage']:.1f}%") if status["overall_status"] in ["completed", "failed", "cancelled", "timeout"]: break time.sleep(5) # 3. Download results if status["overall_status"] == "completed": results_response = requests.get( f"{BASE_URL}/workflows/runs/{job_id}/results/download-links", headers=headers ) for result in results_response.json(): print(f"Download: {result['filename']} ({result['row_count']} rows)") ``` ```javascript JavaScript theme={null} const API_KEY = 'YOUR_API_KEY'; const BASE_URL = 'https://api.sixtyfour.ai'; const headers = { 'x-api-key': API_KEY, 'Content-Type': 'application/json' }; // 1. Run workflow const runResponse = await fetch( `${BASE_URL}/workflows/run?workflow_id=wf_abc123`, { method: 'POST', headers, body: JSON.stringify({ webhook_payload: [ { company_name: 'Acme Corp', website: 'acme.com' }, { company_name: 'TechStart', website: 'techstart.io' } ] }) } ); if (!runResponse.ok) throw new Error(`Failed to start: ${await runResponse.text()}`); const { job_id } = await runResponse.json(); console.log(`Workflow started: ${job_id}`); // 2. Poll for completion let status; while (true) { const statusResponse = await fetch( `${BASE_URL}/workflows/runs/${job_id}/live_status`, { headers } ); status = await statusResponse.json(); console.log(`Status: ${status.overall_status} - ${status.overall_progress_percentage}%`); if (['completed', 'failed', 'cancelled', 'timeout'].includes(status.overall_status)) break; await new Promise((r) => setTimeout(r, 5000)); } // 3. Download results if (status.overall_status === 'completed') { const resultsResponse = await fetch( `${BASE_URL}/workflows/runs/${job_id}/results/download-links`, { headers } ); const results = await resultsResponse.json(); results.forEach((r) => console.log(`Download: ${r.filename} (${r.row_count} rows)`)); } ``` ## Uploading an input file Workflows that start with a `read_csv` block read their rows from a file you upload first. Enriching a list of people or companies? Use [Bulk Intelligence](/api-reference/endpoint/bulk-intelligence) — one call with file and config, no workflow or handle needed. ```http theme={null} POST https://api.sixtyfour.ai/storage/csv/upload ``` Send the file as `multipart/form-data` with a single file part. Accepts `.csv`, `.json`, `.jsonl`, and `.ndjson`. ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/storage/csv/upload" \ -H "x-api-key: YOUR_API_KEY" \ -F "file=@leads.csv" ``` The response returns a `handle_id`: ```json theme={null} { "handle_id": "9b1f9d2e-..." } ``` Pass `handle_id` as `specs_override.resource_handle_id` when you call `/workflows/run`: ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/workflows/run?workflow_id=WORKFLOW_ID" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "specs_override": { "resource_handle_id": "9b1f9d2e-..." } }' ``` ## NDJSON input & output Inputs can be NDJSON or JSONL as well as CSV — upload them the same way (see [Uploading an input file](#uploading-an-input-file)). For NDJSON output, add `result_formats=ndjson` when running. CSV is always produced regardless; NDJSON is added alongside it. Use NDJSON when you need typed values — numbers, booleans, and nested objects survive instead of being flattened to strings. ```bash cURL theme={null} # Request NDJSON output (CSV is always included automatically) curl -X POST "https://api.sixtyfour.ai/workflows/run?workflow_id=WORKFLOW_ID&result_formats=ndjson" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "specs_override": { "resource_handle_id": "9b1f9d2e-..." } }' # Get the NDJSON download link — returns a JSON array of result objects, each with a download_url curl -X GET "https://api.sixtyfour.ai/workflows/runs/JOB_ID/results/download-links?format=ndjson" \ -H "x-api-key: YOUR_API_KEY" # Fetch the file from the returned download_url curl -X GET "DOWNLOAD_URL" \ -H "x-api-key: YOUR_API_KEY" \ -o results.ndjson ``` The `format` query parameter selects which output to link — `csv` (default), `json`, or `ndjson`. It defaults to `csv`, so pass `format=ndjson` to get the NDJSON links. # MCP Server Source: https://docs.sixtyfour.ai/developer-tools/mcp-setup Connect Sixtyfour intelligence tools and docs to your AI assistant using the Model Context Protocol. Sixtyfour provides two MCP servers: | Server | URL | Auth | | - | - | - | | `sixtyfour-docs` | `https://docs.sixtyfour.ai/mcp` | None required | | `sixtyfour-intelligence` | `https://mcp.sixtyfour.ai/mcp` | API key or OAuth | ## Setup The `sixtyfour-docs` server requires no authentication. The `sixtyfour-intelligence` server requires either an API key or OAuth for authentication. ### Cursor Install in Cursor Or add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project) manually. Replace `YOUR_API_KEY` with your key. ```json theme={null} { "mcpServers": { "sixtyfour-docs": { "url": "https://docs.sixtyfour.ai/mcp" }, "sixtyfour-intelligence": { "url": "https://mcp.sixtyfour.ai/mcp?api_key=YOUR_API_KEY" } } } ``` Cursor also supports OAuth for the intelligence server. If you omit the API key, Cursor will authenticate via an OAuth browser flow on first use. Cursor only supports 60 character tool names and descriptions. If you see a warning about tool names being too long in Cursor, the MCP server will still work as Cursor will truncate the tool name and description with a hash suffix. See this [post for more details](https://forum.cursor.com/t/posthog-mcp-tool-names-too-long/155408). ```json theme={null} { "mcpServers": { "sixtyfour-intelligence": { "url": "https://mcp.sixtyfour.ai/mcp" } } } ``` ### VS Code Install in VS Code Or add to `.vscode/mcp.json` in your project root manually. Replace `YOUR_API_KEY` with your key. ```json theme={null} { "servers": { "sixtyfour-docs": { "type": "http", "url": "https://docs.sixtyfour.ai/mcp" }, "sixtyfour-intelligence": { "type": "http", "url": "https://mcp.sixtyfour.ai/mcp?api_key=YOUR_API_KEY" } } } ``` ### Windsurf Add to `~/.codeium/windsurf/mcp_config.json`. Replace `YOUR_API_KEY` with your key. ```json theme={null} { "mcpServers": { "sixtyfour-docs": { "url": "https://docs.sixtyfour.ai/mcp" }, "sixtyfour-intelligence": { "url": "https://mcp.sixtyfour.ai/mcp?api_key=YOUR_API_KEY" } } } ``` After saving the configuration, reload your MCP servers or restart your IDE. ### Claude Code Run these commands in your terminal. Add `--scope user` to make a server available across all projects. **Docs MCP** ```bash theme={null} claude mcp add --transport http sixtyfour-docs "https://docs.sixtyfour.ai/mcp" ``` **Intelligence MCP — OAuth (recommended)** Claude Code will prompt you to authenticate via browser on first use: ```bash theme={null} claude mcp add --transport http sixtyfour-intelligence "https://mcp.sixtyfour.ai/mcp" ``` **Intelligence MCP — API key** ```bash theme={null} claude mcp add --transport http sixtyfour-intelligence "https://mcp.sixtyfour.ai/mcp?api_key=YOUR_API_KEY" ``` ### Claude Desktop In Claude Desktop, add two custom connectors: | Name | Remote MCP server URL | | - | - | | `Sixtyfour Docs` | `https://docs.sixtyfour.ai/mcp` | | `Sixtyfour Intelligence` | `https://mcp.sixtyfour.ai/mcp` | Claude Desktop handles authentication for the intelligence server via OAuth on first connection. ### Codex Install via the [Sixtyfour Codex plugin](https://github.com/sixtyfour-ai/sixtyfour-codex-plugin), or add the servers to `~/.codex/config.toml` manually. #### Plugin install Register the marketplace, then install the plugin. Start a new Codex session after installing — plugins load at session start. **Codex CLI:** ```bash theme={null} codex plugin marketplace add sixtyfour-ai/sixtyfour-codex-plugin codex plugin add sixtyfour@sixtyfour ``` Check installed plugins with `codex plugin list`. To install this plugin in the ChatGPT desktop app instead of the CLI, see [ChatGPT](#chatgpt). Plugins require ChatGPT Work mode or Codex — they aren't available in standard Chat, the IDE extension, or mobile. Work mode currently requires a Pro, Pro Lite, Enterprise, or Edu plan. On Enterprise, Edu, and Business workspaces, an admin must allow installing marketplace plugins under Workspace Settings → Plugins before it appears for members. On first use, authenticate the intelligence server. Run `codex mcp login sixtyfour-intelligence` when prompted. #### Manual config Add the servers to `~/.codex/config.toml`, then restart Codex and sign in when prompted. **Docs MCP:** ```toml theme={null} [mcp_servers.sixtyfour-docs] command = "npx" args = ["-y", "mcp-remote", "https://docs.sixtyfour.ai/mcp"] ``` **Intelligence MCP:** ```toml theme={null} [mcp_servers.sixtyfour-intelligence] command = "npx" args = ["-y", "mcp-remote", "https://mcp.sixtyfour.ai/mcp"] ``` #### Reset a stuck sign-in session The manual `config.toml` + `mcp-remote` method caches its OAuth session in `~/.mcp-auth`, separate from Codex's own session state. If sign-in stops working, clear the cached session for the intelligence server: ```bash theme={null} find ~/.mcp-auth -type f -name "e7e3f47f4a14aca4e636f38d171a9178_*" -delete ``` Restart Codex — it prompts you to sign in again via browser. This only affects the Sixtyfour session; other `mcp-remote`-based connections are untouched. To avoid clearing the cache altogether, switch to the [plugin install](#plugin-install) method, which supports `codex mcp login`/`codex mcp logout` for reconnecting. ### ChatGPT The Sixtyfour plugin is published in the [ChatGPT Plugins Directory](https://chatgpt.com/plugins/plugin_asdk_app_6a514c90130c8191bb9a04df335d5ac6) — the same plugin installed via the CLI marketplace command in [Codex](#codex). View in ChatGPT Plugins 1. Open the listing above, or turn on Work mode in ChatGPT (web or desktop app) and search "Sixtyfour" in Plugins. 2. Select the plugin and install it. 3. Authenticate the intelligence server when prompted. See [Codex](#codex) for plan requirements and workspace admin settings that gate plugin installs. ### Smithery Install the `sixtyfour-intelligence` server through [Smithery](https://smithery.ai/servers/sixtyfour/sixtyfour-intelligence), which handles configuration for Claude Desktop and other supported clients. 1. Open the [Sixtyfour Intelligence listing on Smithery](https://smithery.ai/servers/sixtyfour/sixtyfour-intelligence). 2. Select your client (for example, Claude Desktop) and follow the install command Smithery provides. 3. On first use, authenticate via OAuth or paste your Sixtyfour API key when prompted. *** ## Documentation MCP reference The `sixtyfour-docs` server gives your AI assistant direct access to Sixtyfour API references, guides, and code examples. | Tool | Purpose | | - | - | | `search_sixtyfour_ai_documentation` | Semantic search across Sixtyfour docs | | `query_docs_filesystem_sixtyfour_ai_documentation` | Read specific pages and run `rg`/`cat`/`tree` against the docs filesystem | **Example prompts** * *"How do I use the company-intelligence endpoint?"* * *"What parameters does find-email accept?"* * *"Show me an example of using webhooks with Sixtyfour."* *** ## Intelligence MCP reference The `sixtyfour-intelligence` server exposes the tools below, grouped by category. **Account** | Tool | Purpose | | - | - | | `sixtyfour_check_balance` | Return the organization's current credit balance | **Company search** | Tool | Purpose | | - | - | | `sixtyfour_company_get_searchable_fields` | List all filterable field names for company search | | `sixtyfour_company_explore_field_values` | Get top values for a field (e.g. valid industry names) | | `sixtyfour_company_search` | Search companies with a MongoDB-style filter query | | `sixtyfour_company_next_page` | Fetch the next page of company search results | | `sixtyfour_company_get_company_details` | Expand a result handle into full company data | **People search** | Tool | Purpose | | - | - | | `sixtyfour_people_get_searchable_fields` | List all filterable field names for people search | | `sixtyfour_people_explore_field_values` | Get top values for a field | | `sixtyfour_people_search` | Search people with a MongoDB-style filter query | | `sixtyfour_people_next_page` | Fetch the next page of people search results | | `sixtyfour_people_get_person_details` | Expand a result handle into full person data | Set `count_only` to `true` on `sixtyfour_people_search` to return `total_available` without profiles, result handles, or pagination data. When the backend cannot provide an exact count, `total_available` is `null`: ```json theme={null} { "query": { "locationCountry": "US" }, "count_only": true } ``` **Similar people (lookalike)** | Tool | Purpose | | - | - | | `sixtyfour_find_similar_start` | Find people similar to 1–1000 seed profiles — returns a `task_id` and `search_id` | | `sixtyfour_find_similar_status` | Poll a `sixtyfour_find_similar_start` `task_id` until `completed` | | `sixtyfour_find_similar_results` | Page the ranked matches of a completed search | Pass seeds as `linkedin_urls` (1–1000 LinkedIn profile URLs) or `csv_text` (the full text of a CSV with a LinkedIn URL column, up to 1000 rows), not both. The server infers the traits the seeds share. Use `guidance` only for hard requirements, such as `"only people in the US"`. Seed profiles never appear in the results. A search typically takes 1–3 minutes. Poll with `sixtyfour_find_similar_status`, not `sixtyfour_enrichment_job_status`; set `wait_seconds` (up to 25) to wait server-side between polls. Page the results by `search_id`, then by the returned `next_cursor`. Each row has a 0–100 similarity score, its rank, and the signals that matched. `sixtyfour_find_similar_results` is billed per returned row. `sixtyfour_find_similar_start` starts a billable search; if a call times out, do not retry blindly — check status first. **Async enrichment and contact lookup** | Tool | Purpose | | - | - | | `sixtyfour_company_intelligence_start` | Start a company intelligence job — returns a `task_id` | | `sixtyfour_people_intelligence_start` | Start a people intelligence job — returns a `task_id` | | `sixtyfour_find_email_start` | Find email addresses for 1–100 leads — returns a `task_id` | | `sixtyfour_find_phone_start` | Find phone numbers for 1–100 leads — returns a `task_id` | | `sixtyfour_reverse_email_start` | Resolve the person behind one or more email addresses — returns a `task_id` | | `sixtyfour_reverse_phone_start` | Resolve the person behind one or more phone numbers — returns a `task_id` | | `sixtyfour_enrichment_job_status` | Poll any `_start` tool's `task_id` until `completed`, `failed`, `cancelled`, `terminated`, or `timed_out` | | `sixtyfour_enrich_linkedin` | Enrich a single LinkedIn profile or company URL — returns the result directly | Tools ending in `_start` submit a job and return a `task_id`. Poll `sixtyfour_enrichment_job_status` with that `task_id` until the response shows `completed`. The completed response includes `result`, `charge_amount`, and `task_type`. `sixtyfour_enrich_linkedin` is synchronous — it returns the result directly. Enrichment start tools create billable jobs. If a call times out, do not retry blindly — the job may still have been created. Check status with `sixtyfour_enrichment_job_status` first. **Bulk intelligence** | Tool | Purpose | | - | - | | `sixtyfour_people_bulk_intelligence_start` | Research the same `struct` for many people in one job — returns a `task_id` | | `sixtyfour_company_bulk_intelligence_start` | Research the same `struct` for many companies in one job — returns a `task_id` | Pass the entities as `rows` (a list of identifier objects, such as name, company, and LinkedIn URL) or `csv_text` (CSV text with a header row), not both. `struct` is required: each key becomes an output field, described in plain English or as `{"description": "...", "type": "str"}`. The start response includes `estimated_cost_credits`, a floor estimate. Poll with `sixtyfour_enrichment_job_status`. The completed response includes signed download URLs for the result files under `results`, and `charge_credits`. Inline rows travel in the model's context, so these tools suit up to a few hundred rows assembled from searches. For larger files, use the [web app](https://app.sixtyfour.ai) or the [bulk intelligence API](/api-reference/endpoint/bulk-intelligence) directly. Bulk intelligence start tools create billable jobs. If a call times out, do not retry blindly — check status with `sixtyfour_enrichment_job_status` first. **Age verification** | Tool | Purpose | | - | - | | `sixtyfour_verify_age_start` | Start an age verification job for a person — returns a `task_id` | | `sixtyfour_verify_age_status` | Poll a `sixtyfour_verify_age_start` `task_id` until `completed`, `failed`, `cancelled`, `terminated`, or `timed_out` | `sixtyfour_verify_age_start` and `sixtyfour_verify_age_status` are a dedicated pair — poll with `sixtyfour_verify_age_status`, not `sixtyfour_enrichment_job_status`. The completed response includes `date_of_birth`, `age`, `evidence`, and `sources`. See [Age Verification](/api-reference/endpoint/age-verification) for the full response shape. `sixtyfour_verify_age_start` creates a billable job. If a call times out, do not retry blindly — the job may still have been created. Check status with `sixtyfour_verify_age_status` first. **Feedback** | Tool | Purpose | | - | - | | `send_feedback()` | Report an issue or provide feedback to Sixtyfour | **Example prompts** * *"Search for US SaaS companies with 100–5000 employees."* * *"Research Sixtyfour — return headquarters, headcount, funding stage, and key executives."* * *"Enrich this person: Saarth Shah, CEO & Co-Founder at Sixtyfour."* * *"Find the CTO and VP of Engineering at Sixtyfour."* * *"Find the work email for Saarth Shah at Sixtyfour."* * *"Find a phone number for Jane Doe, VP of Sales at Acme Corp."* * *"Who owns the email [jane@acme.com](mailto:jane@acme.com)?"* * *"Verify the age of Jane Doe, who works at Acme Corp."* * *"Find 50 more people like the ones in this CSV."* * *"Find the work email and GitHub profile for each of these 20 people."* * *"How many Sixtyfour credits do I have left?"* # Skills for AI Agents Source: https://docs.sixtyfour.ai/developer-tools/skills Agent Skills that teach AI coding assistants (Claude Code, Cursor, etc.) how to use Sixtyfour — people and company intelligence at scale. ## What are Agent Skills? The [sixtyfour-ai/skills](https://github.com/sixtyfour-ai/skills) repo packages Sixtyfour as an Agent Skill — a single directory of instructions that teaches an AI coding assistant how to use the Sixtyfour API correctly. ## Prerequisites 1. Have an AI coding assistant installed (Claude Code, Cursor, etc.). 2. Sign up at [app.sixtyfour.ai](https://app.sixtyfour.ai). 3. Generate a key by following [Get an API Key](/get-api-key). 4. Set `SIXTYFOUR_API_KEY` in your agent's shell. ```bash theme={null} export SIXTYFOUR_API_KEY=YOUR_API_KEY ``` ## Install You can install the Sixtyfour skills from [Smithery](https://smithery.ai/skills/sixtyfour/sixtyfour-intelligence), from the [Cursor Plugin Directory](https://cursor.directory/plugins/sixtyfour), or manually from the [sixtyfour-ai/skills](https://github.com/sixtyfour-ai/skills) repo. ### Smithery Install via the [Sixtyfour Intelligence skill on Smithery](https://smithery.ai/skills/sixtyfour/sixtyfour-intelligence). Pick your client on the Smithery page and follow the one-line install command it generates — Smithery handles registering the skill with Claude Code, Cursor, and other supported clients. ### Cursor Plugin Directory The skill is also listed in the [Cursor Plugin Directory](https://cursor.directory/plugins/sixtyfour). Open the listing and use the install button to add it to Cursor directly. ### Manual install The Sixtyfour Agent Skills are not yet available in the Claude Code marketplace. For now, follow the instructions below to install manually. ```bash Claude Code theme={null} npx skills add sixtyfour-ai/skills --skill "sixtyfour" ``` ```bash Cursor theme={null} mkdir -p ~/.cursor/plugins/local git clone https://github.com/sixtyfour-ai/skills.git \ ~/.cursor/plugins/local/sixtyfour ``` ```bash Manual theme={null} git clone https://github.com/sixtyfour-ai/skills.git cp -r skills/skills/sixtyfour /sixtyfour ``` After cloning into Cursor, open the Command Palette (`Cmd+Shift+P` on macOS, `Ctrl+Shift+P` on Windows/Linux) and run `Developer: Reload Window`. Then open **Cursor Settings → Plugins** and confirm Sixtyfour appears with a **Local** badge and 1 skill. ## Usage Once installed, your agent can: * **Investigate people** — "Tell me everything about Jane Doe at Acme Corp — title, background, LinkedIn, career trajectory" * **Research companies** — "Research Ramp — revenue, headcount, funding, engineering leadership" * **Search at scale** — "Find 500 VPs of Sales at Series B SaaS startups in NYC" * **Build pipelines** — "Search for AI startups, find their CTOs, get contact info, and export to CSV" * **Due diligence** — "Deep investigation on this person for compliance review" (medium/high tier) ## Troubleshooting ### API key not set Check that the variable is exported in the same shell your agent runs in: ```bash theme={null} [ -n "$SIXTYFOUR_API_KEY" ] && echo "set" || echo "not set" ``` If it's not set, generate a key at [app.sixtyfour.ai/keys](https://app.sixtyfour.ai/keys) and export it in the terminal that your agent runs in: ```bash theme={null} export SIXTYFOUR_API_KEY=YOUR_API_KEY ``` Setting the variable in a different terminal window does not carry over. ### Environment variables don't persist in Cursor or VS Code Cursor and VS Code don't always inherit shell environment variables — especially on macOS when launched from the Dock or Spotlight. Fix it one of three ways: * Launch from a terminal that already has the variable set: `cursor .` * Add the export to `~/.zshrc` or `~/.bashrc` so it's always available * Add it to `.claude/settings.json` under `env` ### Locating `poll_job.py` The polling script ships inside the skill's plugin cache. The agent knows where to find it, but if you need to run it manually: ```bash theme={null} ~/.claude/plugins/cache/sixtyfour-skills/sixtyfour//skills/sixtyfour/scripts/poll_job.py ``` Run it from that directory or pass the full path. It requires Python 3 and uses only the standard library. ### 429: too many concurrent searches Deep search allows a maximum of 5 concurrent searches per org. If you hit a 429, wait for running searches to finish before starting new ones. Check the status of previous tasks with `GET /search/status/{task_id}`. ### 403 on micro, high, or xhigh tier requests The `high` and `xhigh` research tiers require enterprise access. If you get a 403, book a call at [cal.com/team/sixtyfour/discovery](https://cal.com/team/sixtyfour/discovery). Access to the `micro` tier is granted case-by-case by our team. The `low` and `medium` tiers work on all plans. ### Results are empty or incomplete * Bump `tier` from `"low"` to `"medium"`. The default single-pass lookup misses people with limited online presence or common names. * For people intelligence, pass as many identifiers as possible in `lead_info`: `full_name`, `company`, `linkedin_url`, and `email` all improve matching. ### Deep search appears stuck Searches take 30–120+ seconds while the agent runs. The `poll_job.py` script prints progress messages like `Analyzing your query...` and `Found X profiles` as it goes. Don't kill it early. If it hangs past 10 minutes, re-run with a fresh `start-deep-search` call. ### Output file is CSV, not JSON Deep search results download as CSV. Parse them with `csv.DictReader`, not `json.load`. Columns include `fullName`, `currentPosition`, `currentCompany`, `linkedinUrl`, `skills`, `locationName`, and others. ### Signed download URLs expire After a search completes, the download URL expires in 15 minutes. If you wait too long, re-poll the status endpoint — it issues a fresh `resource_handle_id` you can use to get a new signed URL. # Get an API Key Source: https://docs.sixtyfour.ai/get-api-key Create a Sixtyfour account and generate your API key. ## Create an Account 1. Go to [app.sixtyfour.ai](https://app.sixtyfour.ai) and sign up 2. Create an organization ## Get Your API Key 1. Open **Settings → API Keys** from the dashboard 2. Click **Create new key**, name it, choose an expiration, then click **Create key** 3. Copy the key value — you'll need it for API requests **Your API key is shown only once.** Store it in a secure secret manager (e.g. 1Password, AWS Secrets Manager, Vercel/GitHub environment variables) immediately after creation. * **Never** commit keys to source control or share them in plaintext (Slack, email, screenshots). * Treat the key like a password — anyone with it can call the API and incur usage on your organization. * If a key is lost or leaked, revoke it from the **API Keys** page and create a new one. ## Set Key Expiration Every key requires an expiration setting at creation time. Choose one of: | Option | Behavior | | - | - | | Never | The key stays active until you manually revoke it. | | Duration | The key expires automatically after the specified time. Maximum 730 days (2 years), in days or months. | Set expiration options in the Create new key modal Expired keys stop authenticating immediately and return `401 Unauthorized`. Rotate before expiry to avoid downtime — create a new key, update your secret manager, then revoke the old one. Short-lived keys reduce blast radius if a key is leaked. Pick the shortest duration that fits your rotation schedule. ## Review the API Reference Review the [API Quick Start](/api-quick-start) to see how to use the API. # Automated Workflows Source: https://docs.sixtyfour.ai/guides/automated-workflows Run any workflow on a recurring schedule using the built-in workflow scheduler. Only Enterprise plan customers can create workflows with schedules in the Sixtyfour Platform. Schedules turn any workflow into a recurring job that runs without manual intervention. Use them to keep enrichment lists fresh, send daily prospect digests, refresh CRM data on a cadence, or trigger any pipeline on a fixed interval. Schedules are configured per-workflow in the [Workflow Editor](https://app.sixtyfour.ai/workflows/new). Each run is tracked with a status, job ID, and next-run timestamp. You can also programmatically trigger workflows using the Workflows API. See [Programmatic Workflows](/guides/programmatic-workflows) for more details. To create and manage schedules from code instead of the UI, see the [Workflow Schedules API](/api-reference/workflows/workflow-schedules). ## Why automate workflows | Use case | Example schedule | | - | - | | Keep a prospect list current | Daily at 9:00 AM — search for new accounts matching your ICP and append to a notebook | | Refresh CRM enrichment | Weekly Monday 9:00 AM — pull HubSpot contacts, re-enrich, push back | | Monitor a market | Every hour — scrape a competitor's careers page and send new roles to Slack | | Backfill data overnight | Custom `0 2 * * *` — run heavier enrichment when credits and latency don't compete with daytime work | Schedules pair naturally with the **Deduplicate with Notebook** block, which prevents recurring runs from saving the same row twice. See [Building Workflows](/guides/building-workflows) for the full block reference. ## Prerequisites * A completed workflow. Schedules attach to a `workflow_id`, so a draft workflow with no ID cannot be scheduled. ## Open the schedules panel From the [Workflow Editor](https://app.sixtyfour.ai/workflows/new), click the overflow menu (`···`) next to the **Run** button and select **Schedules**. Editor overflow menu with Schedules option highlighted If the workflow has no schedules yet, the panel shows an empty state. Click **Create Your First Schedule** to open the dialog. Empty schedules panel with Create Your First Schedule button ## Create a schedule The Create Schedule dialog has two ways to define when the workflow runs: common presets or a custom cron expression. ### Use a common preset The **Common Schedules** tab covers the most frequent cadences. Pick one from the dropdown and the dialog shows a preview of the next three run times in your selected timezone. Create Schedule dialog with Common Schedules tab selected, showing Daily at 9:00 AM preview Available presets: * Every 15 minutes * Every 30 minutes * Every hour * Daily at 9:00 AM * Every weekday at 9:00 AM * Every Monday at 9:00 AM * Weekly (Monday 9:00 AM) * Monthly (1st at 9:00 AM) ### Use a custom cron expression Switch to the **Custom Cron** tab for anything outside the presets. Sixtyfour uses standard 5-field cron syntax: `minute hour day month weekday`. Create Schedule dialog with Custom Cron tab selected | Field | Range | | - | - | | minute | 0–59 | | hour | 0–23 | | day of month | 1–31 | | month | 1–12 | | day of week | 0–6 (Sunday = 0) | Common patterns: * `*/30 * * * *` — every 30 minutes * `0 9 * * 1-5` — every weekday at 9:00 AM * `0 */4 * * *` — every 4 hours on the hour * `0 2 1 * *` — 2:00 AM on the 1st of every month The dialog validates the expression as you type and shows a human-readable summary plus the next three run times once it's valid. ### Required fields | Field | Notes | | - | - | | Schedule Name | Required. Shown in the schedules list and run history. | | Description | Optional. Helps distinguish multiple schedules on the same workflow. | | Schedule | Preset or custom cron expression. | | Timezone | Defaults to UTC. Cron expressions evaluate against this timezone. | | Active | When off, the schedule is saved but won't run until re-enabled. | Click **Create Schedule** to save. The schedule appears in the schedules list with its next run time. ## Manage existing schedules Each schedule in the list shows its name, status badge (`Active` or `Paused`), human-readable cadence, next run time, and last run status. Per-schedule controls: * Active toggle — pause or resume the schedule without deleting it. * Edit — change the name, cadence, timezone, or active state. * Delete — permanently remove the schedule. The workflow itself is not affected. A workflow can have multiple schedules — for example, an hourly run on weekdays and a single weekly run on Sundays. Workflows that begin with an Incoming Webhooks block expect external payloads and aren't a fit for time-based schedules. For those, trigger runs via the [Workflows API](/api-reference/workflows/workflows-execution) instead. ## Billing Scheduled runs are billed against your organization the same way manual runs are. See [Credits & Pricing](/guides/credits-and-pricing) for cost details. ## Tips * Stagger heavy workflows that share an hourly cadence. Schedules that all fire at `:00` create a load spike — offset them (`5 * * * *`, `15 * * * *`) to smooth credit usage. * Add a Deduplicate with Notebook block to any recurring workflow that writes to a notebook. It prevents the same row from being re-saved on every run. * Set the timezone to your team's timezone. A schedule named "Daily at 9 AM" only matches your team's morning if the timezone is set correctly. UTC is the default. * Pause schedules instead of deleting them while iterating on a workflow — you keep the history and configuration. # Batch Processing Source: https://docs.sixtyfour.ai/guides/batch-processing Run enrichment pipelines across hundreds of records at once using the Workflow Editor or the Workflows API. Sixtyfour workflows process data in batch — upload a list or send records via API, and every row flows through your enrichment pipeline automatically. Use the [Workflow Editor](https://app.sixtyfour.ai/workflows/new) for a no-code approach, or trigger workflows programmatically with the [Workflows API](/api-reference/workflows/workflows-overview). For a full reference of available blocks, see [Building Workflows](/guides/building-workflows). ## Batch Processing in the Workflow Editor The Workflow Editor lets you build and run batch pipelines without writing code. See [Building Workflows](/guides/building-workflows) for a full walkthrough of how to import, find, enrich, clean, and send data. ### Example A typical batch pipeline to find and verify emails for a list of people: ```mermaid theme={null} flowchart LR Search --> Enrich_People[Enrich People] --> Find_Email[Email address] --> Filter --> Append_to_Notebook[Notebook] ``` ## Batch Processing via API Trigger workflows programmatically by sending records to a workflow that starts with a **Webhook** block. Build the workflow in the editor, then call it from your code. ```mermaid theme={null} flowchart LR SendData["Send Data"] --> PollStatus["Poll Status"] PollStatus --> DownloadResults["Download Results"] ``` ### Step 1: Trigger the workflow Send your batch of records to `POST /workflows/run` with a `webhook_payload` array. Each object in the array is one row. ```http theme={null} POST https://api.sixtyfour.ai/workflows/run?workflow_id=WORKFLOW_ID ``` ```json theme={null} { "webhook_payload": [ {"name": "Sarah Chen", "company": "Pacific View Studios", "title": "Creative Director"}, {"name": "James Park", "company": "Northgate Labs", "title": "CTO"}, {"name": "Maria Lopez", "company": "Suncrest Health", "title": "VP Engineering"} ] } ``` The response includes a `job_id` for tracking: ```json theme={null} { "status": "queued", "workflow_id": "wf_abc123", "job_id": "run_xyz789" } ``` ### Step 2: Poll for progress Check workflow status with `GET /workflows/runs/{run_id}/live_status`. Poll every 5-10 seconds until `overall_status` reaches `completed`, `failed`, or `cancelled`. ```http theme={null} GET https://api.sixtyfour.ai/workflows/runs/run_xyz789/live_status ``` Use `overall_progress_percentage` to track progress. ### Step 3: Download results Once completed, retrieve signed download URLs for the result CSV files. ```http theme={null} GET https://api.sixtyfour.ai/workflows/runs/run_xyz789/results/download-links ``` Signed URLs expire after 15 minutes. Download files immediately or request new links. For full code examples covering this trigger/poll/download flow in cURL, Python, and JavaScript, see the [Workflow Execution reference](/api-reference/workflows/workflows-execution#example-usage). ## Tips * **Filter before enrichment.** Reducing rows early saves time and credits. * **Deduplicate across runs.** Use the Deduplicate with Notebook block on recurring batch workflows to avoid processing the same records twice. * **Request only what you need.** Define only the return fields you need in enrichment blocks to keep results focused and costs efficient. * **Use Webhook for API triggers.** Any workflow you want to trigger programmatically must start with a Webhook source block. # Building Workflows Source: https://docs.sixtyfour.ai/guides/building-workflows How to import, find, enrich, clean, and send data using the Sixtyfour Workflow Editor. Workflows in Sixtyfour are linear pipelines that process data through a sequence of blocks. Each block performs a specific function — importing data, finding and enriching it, cleaning and organizing it, or sending results. Every workflow follows the same pattern: ```mermaid theme={null} flowchart LR Import --> Find --> Enrich --> Clean --> Send ``` Only Enterprise plan customers can use the Workflow Editor and Templates to create workflows in the Sixtyfour Platform. You can still create workflows programmatically using the [Workflows API](/api-reference/workflows/workflows-overview). * Open the [Workflow Editor](https://app.sixtyfour.ai/workflows/new) to start building. * For API-driven workflow management, see the [Workflows API reference](/api-reference/workflows/workflows-overview). * For a pre-built starting point, browse the [Workflow Templates](/guides/workflow-templates) gallery. ## Source Blocks For a complete list of source blocks, see the [Workflow Blocks Reference](/api-reference/workflows/workflow-blocks). Every workflow starts with a source block. Pick one based on where your data lives. | Block | Description | | - | - | | Search | Find people or companies from Sixtyfour's dataset using a natural language query. The most common starting point. | | Read Notebook | Pull data from a previously saved Sixtyfour notebook. | | Google Maps | Search for local businesses and places. | | GitHub Find People | Discover developers from GitHub repositories (contributors, stargazers, watchers). | | GitHub Search | Search GitHub for repositories, users, or code. | | Kaggle | Pull competitors from Kaggle competition leaderboards. | | Y Combinator | Fetch YC company data by batch, industry, or tags. | | HubSpot Import | Import contacts or companies directly from HubSpot CRM. | | X | Search X/Twitter for profiles (e.g., "AI researchers", "crypto founders in NYC"). | | TikTok | Get the following list from TikTok profile URLs or handles. | | Incoming Webhooks | Receive data from external systems via API call. | Workflows starting with an Incoming Webhooks block can be [triggered via API](/api-reference/workflows/workflows-execution). Other source blocks run from the Workflow Editor. ## Enrichment Blocks Enrichment blocks research and append new information to each row. The block you choose depends on whether you're working with **Lead** (people) or **Company** (organization) data. ### Core Enrichment | Block | Input Type | Description | | - | - | - | | Enrich People | Lead | AI web research on people. Define custom return fields (job title, LinkedIn URL, skills, etc.) and the system researches each person. | | Enrich Companies | Company | AI web research on companies. Define custom return fields (revenue, employee count, industry, etc.). Optionally discover people at each company. | ### Contact Finding | Block | Input Type | Description | | - | - | - | | Email address | Lead | Find professional or personal email addresses. Returns a verification status (`OK`, `UNKNOWN`, `RISKY`, `NOT_FOUND`). | | Phone | Lead | Find phone numbers. | | Verify Email | Lead | Check if existing email addresses are valid and deliverable. | | Verify Phone | Lead | Check if existing phone numbers are valid and match the lead. Requires a phone column and a full-name column; returns validity, activity score, line type, name match, and a contact grade. | ### Configure Verify Phone Select the phone and full-name columns from the preceding blocks. If the editor suggests a replacement, apply it only if it matches your data. Save the mapping and rerun the workflow. ### Reverse Lookup | Block | Input Type | Description | | - | - | - | | Reverse Email | Lead | Look up person and company info from an email address. | | Reverse Phone | Lead | Look up person and company info from a phone number. | ### Specialized | Block | Input Type | Description | | - | - | - | | Web Research Agent | Any | General-purpose AI agent that browses the internet to answer custom questions per row. Best for highly specialized queries. | | Scrape Website | Any | Scrape web content from URLs in a column. Output as markdown or HTML; supports geo, JS rendering, and proxies. | | Find Subdomains | Company | Find subdomains for a domain; supports time filters. | | Backlinks | Company | Get referring domains for a target domain or URL; supports pagination. | For the underlying APIs, see [People Intelligence](/api-reference/endpoint/people-intelligence), [Company Intelligence](/api-reference/endpoint/company-intelligence), [Find Email](/api-reference/endpoint/find-email), and [Find Phone](/api-reference/endpoint/find-phone). ## Transform Blocks Transform blocks filter, deduplicate, reshape, and prepare data between enrichment and output. | Block | Description | | - | - | | Filter | Keep or remove rows based on column conditions (e.g., only verified emails, companies with 100+ employees). | | Deduplicate | Remove duplicate rows based on one or more columns. | | Deduplicate with Notebook | Remove rows that already exist in a specified notebook. Prevents duplicates across recurring runs. | | Generate Text | Generate custom text per row using a prompt template with column references. Use `{column_name}` for substitution. Useful for personalized messaging. | | LLM Output | Generate structured or unstructured text per row using configurable LLM models. Use when you need typed output via a schema. | | Remove Columns | Remove selected columns from the output, keeping all rows intact. | | Group By | Group rows by column values with aggregation functions (sum, count, list, etc.). | | Extract Leads | Extract people discovered during Company Enrichment into individual lead rows. Required before using any lead-only block on company-enriched data. | | Leads to Company | Convert lead rows into company rows with nested lead arrays. | ### Data Type Conversion Sixtyfour workflows operate on two data types: **Lead** (people) and **Company** (organizations). Some blocks require a specific type. If you run Enrich Companies with people discovery enabled, the output is still Company data. You must use the **Extract Leads** block to convert it to Lead data before using lead-only blocks like Find Email or Find Phone. ## Output Blocks Output blocks deliver your results. They are typically the final step in a workflow. | Block | Description | | - | - | | Notebook | Save results to a Sixtyfour notebook. Create a new notebook or append to an existing one. | | Slack | Send messages to a Slack channel. Use `{column}` for substitution and `{@column}` to tag users by email. | | Outgoing Webhooks | POST workflow results to an external URL when the run completes. See [Outgoing Webhooks](/api-reference/webhooks/outgoing). | ## Common Workflow Patterns ### Find people and get their emails ```mermaid theme={null} flowchart LR Search --> Find_Email[Email address] --> Append_to_Notebook[Notebook] ``` ### Research companies, find decision-makers, get contact info ```mermaid theme={null} flowchart LR Search[Search\nCompanies] --> Enrich_Companies[Enrich Companies] --> Extract_Leads[Extract Leads] --> Find_Email[Email address] --> Append_to_Notebook[Notebook] ``` ### Enrich an existing list and save clean results ```mermaid theme={null} flowchart LR Read_Notebook[Read Notebook] --> Enrich_People[Enrich People] --> Filter --> Deduplicate[Deduplicate] --> Append_to_Notebook[Notebook] ``` ### Import from CRM, enrich, and notify your team ```mermaid theme={null} flowchart LR HubSpot_Import[HubSpot Import] --> Enrich_People[Enrich People] --> Find_Email[Email address] --> Filter --> Slack ``` ## Tips * **Filter early.** Filtering before enrichment reduces rows processed, saving time and credits. * **Deduplicate across runs.** Use Deduplicate with Notebook on recurring workflows to avoid saving the same results twice. * **Request only what you need.** Define only the return fields you need in enrichment blocks to keep results focused and costs efficient. * **Bridge company to lead data.** Use Extract Leads any time you need to go from company data to people data — it is the only way to cross that boundary. * To skip the blank canvas, start from a [Workflow Template](/guides/workflow-templates). * To build programmatic workflows, see [Programmatic Workflows](/guides/programmatic-workflows). # Credits & Pricing Source: https://docs.sixtyfour.ai/guides/credits-and-pricing Understand how Sixtyfour credits work, what each intelligence enrichment costs, and how to manage your balance. Sixtyfour uses a credit-based system for all API calls and workflow block executions. Every enrichment, discovery, and AI operation deducts credits from your account balance. ## API Pricing [API Usage](/api-quick-start) pricing and [Workflow](/api-reference/workflows/workflows-overview) pricing are the same — you pay the same credit cost whether you call the REST API directly or run the equivalent block inside a workflow on the Sixtyfour platform. ## MCP Server Pricing [MCP server](/developer-tools/mcp-setup) pricing is charged per API call and consumes credits from your account balance when a workflow or an enrichment is executed. ## Plans Each plan includes a monthly credit allowance. Certain plans include rollover credits. | Plan | Credits / month | Rollover Credits | Best for | | - | -: | -: | - | | **Free** | Limited to 64 | No | For exploring the Sixtyfour platform. | | **Individual** | 100 | No | For solo users getting started with intelligence enrichment who use Claude Code, OpenClaw or other AI tools. | | **Starter** | 1,200 | Rollover with 2x monthly cap | For small teams experimenting with intelligence enrichment workflows at scale. | | **Enterprise** | Custom | Custom | For large organizations running high-scale intelligence enrichment workflows. | You can upgrade your plan at any time under the [billing section](https://app.sixtyfour.ai/billing) of the Sixtyfour dashboard. ## Intelligence Pricing ### People & Company Intelligence Tiered Intelligence Enrichment offers multiple tiers of data depth and quality. The tier defaults to `low` if not specified. People and Company Intelligence have separate pricing structures. ### People Intelligence Tiers | Tier | Credits | Description | | - | -: | - | | Micro | 0.3 | Lightweight enrichment — returns up to 10 fields. Best for very high-volume lookups where only minimal data is needed. | | Low | 1 | Basic enrichment (default) | | Medium | 20 | Extended enrichment | | High | 40 | Full enrichment including [OSINT](https://en.wikipedia.org/wiki/Open-source_intelligence) data | | X-High | 250 | Maximum-depth enrichment including [OSINT](https://en.wikipedia.org/wiki/Open-source_intelligence) data | ### Company Intelligence Tiers | Tier | Credits | Description | | - | -: | - | | Micro | 0.3 | Lightweight enrichment — returns up to 10 fields. Best for very high-volume lookups where only minimal firmographic data is needed. | | Low | 1 | Basic enrichment (default) | | Medium | 5 | Extended enrichment | | High | 15 | Full enrichment including [OSINT](https://en.wikipedia.org/wiki/Open-source_intelligence) data | Micro tier access is granted case-by-case by our team. High and X-High tier access is currently available to enterprise customers only. If you are interested in high or X-High tier access, please contact us at [support@sixtyfour.ai](mailto:support@sixtyfour.ai). ### Email & Phone Discovery | Operation | Credit Cost | Notes | | - | -: | -: | | Find Email (professional) | 0.5 | Default mode | | Find Email (personal) | 2 | Personal email lookup | | Find Phone | 3 | Per phone number found | ### Reverse Lookup | Operation | Credit Cost | | - | -: | | Reverse Email | 0.5 | | Reverse Phone | 3 | ### Search Both Deep Search and Filter Search charge based on the number of results returned. Each paginated page of results loads 10 records by default, so loading one page costs 1 credit. Enterprise plans can run free searches from the Platform UI without consuming credits. If you are interested in Enterprise access, please contact us at [support@sixtyfour.ai](mailto:support@sixtyfour.ai). | Operation | Credit Cost | Unit | | - | -: | -: | | Deep Search | 0.1 | Per result (company or person) | | Filter Search | 0.1 | Per result (company or person) | ### AI Agents | Operation | Credit Cost | | - | -: | | QA Agent | 0.5 | | Research Agent | 0.3 | ### Other | Operation | Credit Cost | | - | -: | | Get LinkedIn Profile | 0.1 | | LinkedIn Enrichment | 0.3 | | Email Generation (AI) | 0.1 | | Email Verification | 0.07 | | Phone Verification | 0.15 | | Breached Database Search | 0.1 | Verify Phone rows skipped because the phone is blank or the name is missing incur no phone-verification charge. ### Monitors [Monitors](/api-reference/monitors/monitors-overview) bill per row, per check. | Operation | Credit Cost | Notes | | - | -: | - | | Probe | 0.2 | Every check after a row's first, for each active row. | | First check | Intelligence tier price | A row's first check, at the monitor's tier. Priced as [People Intelligence](#people-intelligence-tiers) for `lead` monitors and [Company Intelligence](#company-intelligence-tiers) for `company` monitors. Not charged for rows started by the workflow `monitor` block whose input already supplies every watched field; those rows start with the probe. | | Re-check | Intelligence tier price | Only when the probe finds a likely change, at the monitor's tier. Same pricing as the first check. | Switched-off rows and cancelled monitors incur no charge. Each probe, first check, and re-check is charged only if it runs to completion; a completed probe is still charged when the re-check after it fails. Monitor charges appear in usage logs as `monitor_recheck`. ## Data Source & Web Scraping Pricing ### Data Sources | Operation | Credit Cost | Unit | | - | -: | -: | | Google Maps Search | 0.1 | Per page (\~20 results) | | X (Twitter) Search | 3 | Per query | | TikTok Get Following | 0.1 | \~50 API calls per account (auto-paginated) | | News Search | 0.2 | Per article | | News Extraction | 20 | Per job | | Job Search | 0.5 | Per job | | Find Subdomains | 0.005 | Per subdomain | | Backlinks / Referring Domains | 0.01 | Per result | ### Web Scraping Web scraping has a base cost plus optional add-ons, all charged per page scraped: | Configuration | Credit Cost | | - | -: | | Base (standard proxy) | 0.01 | | + Residential / mobile proxy | +0.06 | | + JavaScript rendering | +0.06 | | All options enabled | 0.13 | ### AI Model Pricing Certain AI models used in Workflow Blocks may have different credit costs depending on the model and whether reasoning is enabled. | Model | Credit Cost | | - | -: | | gpt-4.1-mini (default) | 0.1 | | gpt-5-nano | 0.15 | | gpt-5-nano with reasoning | 0.2 | ### Transform Data | Tier | Credit Cost | | - | -: | | Low (default) | 0.1 | | Medium | 0.3 | ## Search Results Export limits Exporting search results is allowed and limited by plan type. | Plan | Results limits | | - | -: | | Free | 100 | | Individual | 1,000 | | Starter | 10,000 | | Enterprise | 50,000 | ## What's Free The following platform operations do not consume credits: * **Agent Queries** — Do not consume credits. Credits are only consumed when a workflow or search is run. * **Importing data** — CSV uploads, CRM imports, webhook imports * **Filtering and deduplication** — all filter and deduplication operations * **Data transformation** — column removal, lead-to-company mapping * **Exporting to CSV** — downloading results * **Slack messages** — sending notifications via Slack * **Reading from storage** — accessing stored datasets * **Failed workflow rows** — a row that fails at a workflow step is not charged for that step or any later step. See [Failed rows](/api-reference/workflows/workflows-execution#failed-rows). ## Credit Top-Ups If you run out of credits before your billing cycle renews, you can purchase a one-time top-up in the dashboard. Top-up credits can cost **3x the standard plan rate**. For ongoing needs, upgrading your plan is more cost-effective. Top-up credits expire **1 year from the date of purchase**. Set up auto-recharge in the dashboard to automatically top up when your balance falls below a threshold — no manual intervention needed. You can purchase top-ups at any time under the [billing section](https://app.sixtyfour.ai/billing) of the Sixtyfour dashboard. ## Cost Optimization * **Filter before enriching.** Narrow your dataset to relevant records before running enrichment to avoid spending credits on data you won't use. * **Use professional email mode.** Personal email discovery costs 4x more than professional. Only use personal mode when you need it. * **Test on small batches first.** Run new workflows on 10–20 records to validate results before scaling. * **Deduplicate across runs.** Use the Deduplicate block on recurring workflows to avoid re-processing the same records. * **Track spend in the dashboard.** The [Usage & API Logs](/guides/usage-logs) page shows credit consumption by endpoint and over time. # Getting Support Source: https://docs.sixtyfour.ai/guides/getting-support How to get support using Sixtyfour. Sixtyfour provides multiple support channels to help you get the most out of the platform. Whether you need a quick answer from our AI assistants or hands-on help from the team, choose the option that fits your needs. ## Docs AI Assistant Support Every account has access to our **Docs AI Assistant**. The AI assistant built into the Docs site can answer questions about Sixtyfour documentation. Open the AI Assistant from any page by clicking the **Ask AI** button in the top right corner to get instant answers without waiting for a response from the team. Docs AI Assistant ## Sixtyfour Agent Support Within the [Sixtyfour Platform](https://app.sixtyfour.ai/chat), you can use the **Sixtyfour Agent** to create and build workflows, run searches, and analyze data. Ask it any question about the Sixtyfour platform and it helps you complete your task. Sixtyfour Agent Sixtyfour offers support based on your current plan. All paid plans include email support, and enterprise customers get dedicated support. ## Support by Plan | Plan | Support | | - | - | | Free | None | | Individual | Email | | Starter | Email | | Growth | Email | | Enterprise | Dedicated Support | ## Contact Us For email support, reach us at [support@sixtyfour.ai](mailto:support@sixtyfour.ai). ## Dedicated Support Dedicated support is available on the Enterprise plan. If you need priority assistance, custom SLAs, or a dedicated point of contact, [contact us](mailto:support@sixtyfour.ai) to discuss an Enterprise plan. # Using OAuth Clients Source: https://docs.sixtyfour.ai/guides/oauth-clients Set up an OAuth client in the Sixtyfour dashboard for server-to-server authentication without sharing long-lived API keys. Set up an OAuth client to authenticate server-to-server requests with a short-lived, signed JWT assertion instead of a long-lived API key. Sixtyfour verifies your JWT against a public key you register directly or via a JWKS URL — there is no client secret. | Property | Value | | - | - | | Grant type | `client_credentials` | | Client auth | `private_key_jwt` | | Signing algorithm | `RS256` | | Scope | `full_access` | | Token URL | `https://api.sixtyfour.ai/oauth/token` | | Discovery URL | `https://api.sixtyfour.ai/.well-known/oauth-authorization-server` | ## Step 1: Go to OAuth Clients Go to [Settings → OAuth Clients](https://app.sixtyfour.ai/settings/oauth-clients). OAuth Clients tab in the API Keys settings ## Step 2: Create a client 1. Click **Create client** 2. Enter a **Client Name** (for example, `my-production-app`) 3. Optionally enter a **JWKS URL** — a public HTTPS endpoint that serves your JSON Web Key Set The JWKS choice is permanent. Provide a JWKS URL for automatic key discovery and rotation, or leave it blank to upload public keys manually. You cannot switch modes after creation. 4. Click **Create and continue** Sixtyfour generates a `client_id` and drops you into the client detail view. ## Step 3: Configure your key Pick the flow that matches how you created the client. ### Option A — JWKS URL If you configured a JWKS URL, Sixtyfour fetches your public keys from that endpoint automatically. Validate it at any time: 1. On the client detail page, click **Validate JWKS** 2. Confirm the health indicator shows a green check and a recent validation timestamp ### Option B — Upload a public key If you did not set a JWKS URL, generate an RSA key pair and upload the public half: ```bash theme={null} openssl genrsa -out private.pem 2048 openssl rsa -in private.pem -pubout -out public.pem ``` 1. Click **Upload key** 2. Paste the contents of `public.pem` (including the `-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----` lines) 3. Click **Upload key** Copy the generated **Key ID** (`kid`) — you'll include it in the JWT header. Keep `private.pem` on the server that signs assertions. Never commit it or send it to a browser. ## Step 4: Request an access token Your server signs a short-lived JWT assertion with the private key, then POSTs it to the token endpoint. ```python Python theme={null} import jwt, time, uuid, requests # pip install PyJWT cryptography requests CLIENT_ID = "YOUR_CLIENT_ID" KEY_ID = "YOUR_KEY_ID" TOKEN_URL = "https://api.sixtyfour.ai/oauth/token" ISSUER = TOKEN_URL.replace("/oauth/token", "") # or fetch from discovery # Load your private key (keep this secret, never commit it) with open("private.pem") as f: private_key = f.read() # Build the signed JWT assertion now = int(time.time()) assertion = jwt.encode( { "iss": CLIENT_ID, "sub": CLIENT_ID, "aud": ISSUER, "iat": now, "exp": now + 60, "jti": str(uuid.uuid4()), }, private_key, algorithm="RS256", headers={"kid": KEY_ID}, ) # Exchange for an access token resp = requests.post(TOKEN_URL, data={ "grant_type": "client_credentials", "client_id": CLIENT_ID, "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", "client_assertion": assertion, "scope": "full_access", }) access_token = resp.json()["access_token"] print("Access token:", access_token) ``` ```javascript JavaScript theme={null} import jwt from "jsonwebtoken"; import { randomUUID } from "node:crypto"; import { readFileSync } from "node:fs"; const CLIENT_ID = "YOUR_CLIENT_ID"; const KEY_ID = "YOUR_KEY_ID"; const TOKEN_URL = "https://api.sixtyfour.ai/oauth/token"; const ISSUER = "https://api.sixtyfour.ai"; const privateKey = readFileSync("private.pem", "utf8"); const now = Math.floor(Date.now() / 1000); const assertion = jwt.sign( { iss: CLIENT_ID, sub: CLIENT_ID, aud: ISSUER, iat: now, exp: now + 60, jti: randomUUID(), }, privateKey, { algorithm: "RS256", header: { kid: KEY_ID } }, ); const body = new URLSearchParams({ grant_type: "client_credentials", client_id: CLIENT_ID, client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer", client_assertion: assertion, scope: "full_access", }); const resp = await fetch(TOKEN_URL, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body, }); const { access_token } = await resp.json(); console.log("Access token:", access_token); ``` The response contains a bearer token: ```json theme={null} { "access_token": "eyJhbGciOi...", "token_type": "Bearer", "expires_in": 3600, "scope": "full_access" } ``` ## Step 5: Call the API Send the token in the `Authorization` header on any Sixtyfour API request: ```http theme={null} Authorization: Bearer YOUR_ACCESS_TOKEN Content-Type: application/json ``` OAuth access tokens work on all endpoints that accept `x-api-key`. Do not send both headers on the same request. Run the token exchange server-side only — never from a browser. ## Managing a client The client detail page exposes everything you need after setup. ### Adjust token TTL Change **Token TTL** to control how long issued access tokens stay valid. Options are 1 minute, 5 minutes, 1 hour, or 24 hours. Shorter TTLs reduce blast radius if a token is leaked. ### Rotate keys If you configured a JWKS URL, publish the new key in your JWKS document and click **Validate JWKS**. Sixtyfour picks up the new `kid` automatically on the next fetch. If you uploaded a public key directly, upload the new key, switch your signer to the new `kid`, then revoke the old key from the Public Keys table. ### Permanently disable a client Clicking **Disable** is irreversible: it immediately invalidates all outstanding tokens and the client cannot be re-enabled. If you only need a temporary pause, rotate keys instead. To restore access after disabling, create a new client. ### Delete a client Use the trash icon on the detail page header. Deleting revokes all tokens and keys for that client and cannot be undone. # Programmatic Workflows Source: https://docs.sixtyfour.ai/guides/programmatic-workflows Operate Sixtyfour workflows entirely outside the Workflow Editor — define and run with the Workflow Definition JSON. The Workflow Editor can provide the JSON definition behind every workflow. That same JSON is what the Workflows API consumes for `create_workflow`, `update_workflow`, and `run`. Pair it with the Workflow ID and you can operate a workflow entirely from your own code — no UI required. To build workflows in the UI, see [Building Workflows](/guides/building-workflows). ## The Workflow Definition JSON The JSON shown in the editor's **Workflow API Reference** modal is the canonical, server-side representation of a workflow. A workflow definition is a directed graph of blocks and edges: ```json theme={null} { "workflow_name": "Enrich inbound leads", "workflow_description": "Webhook → enrich people → outgoing webhook", "workflow_definition": { "blocks": [ { "block_id": "1f2c3a4d-...-source", "sequence_number": 1, "block_type": "io", "block_name": "webhook", "specs": { "input_schema": { "email": { "type": "string" }, "company": { "type": "string" } }, "dataframe_type": "LEAD" } }, { "block_id": "2a3b4c5d-...-enrich", "sequence_number": 2, "block_type": "enrichment", "block_name": "lead_enrichment", "specs": { "return_fields": { "title": "string", "linkedin_url": "string" } } }, { "block_id": "3a4b5c6d-...-deliver", "sequence_number": 3, "block_type": "output", "block_name": "outgoing_webhook", "specs": { "url": "https://example.com/hooks/sixtyfour", "timeout_seconds": 10 } } ], "edges": [ { "from_block_id": "1f2c3a4d-...-source", "to_block_id": "2a3b4c5d-...-enrich" }, { "from_block_id": "2a3b4c5d-...-enrich", "to_block_id": "3a4b5c6d-...-deliver" } ] } } ``` ### Field reference | Field | Type | Description | | - | - | - | | `workflow_name` | string | Human-readable name shown in the editor and `GET /workflows`. | | `workflow_description` | string | Free-form description. | | `workflow_definition.blocks` | array | All processing steps in the graph. Order is irrelevant — execution follows `edges`. | | `workflow_definition.edges` | array | Directed connections (`from_block_id` → `to_block_id`). Defines data flow. | | `id` | string (optional) | Pre-generated UUID for the workflow. Omit to let the server assign one. | Each block has the following shape: | Field | Type | Description | | - | - | - | | `block_id` | string (optional) | UUID. The server normalizes missing or non-UUID IDs and rewrites edges to match. | | `sequence_number` | integer | UI ordering hint. Does not affect runtime execution. | | `block_type` | string | Free-form category (`io`, `transform`, `enrichment`, `signal`, `output`). The runtime ignores this for dispatch. | | `block_name` | string | The executor identifier. This is what the runtime uses to resolve a block. See [Workflow Blocks Reference](/api-reference/workflows/workflow-blocks) for the full list. | | `specs` | object | Block-specific configuration. Schema varies per `block_name`. | `block_name` selects the executor — renaming it will break execution with `Unknown block type: ...`. `block_type` is a free-form label; renaming it is harmless. Treat `block_name` as immutable when hand-editing JSON. ## Get the JSON for an existing workflow Two ways to get the JSON: * From the editor: open a workflow in [app.sixtyfour.ai](https://app.sixtyfour.ai), click the menu next to **Run**, choose **View Workflow Definition**, and copy the JSON. The Workflow ID appears at the top of the modal. * From the API: `GET /workflows/{workflow_id}` returns the same definition under `workflow_definition`. ```bash cURL theme={null} curl -X GET "https://api.sixtyfour.ai/workflows/WORKFLOW_ID" \ -H "x-api-key: YOUR_API_KEY" ``` The response includes `id`, `name`, `description`, `status`, and the full `workflow_definition` graph. The `workflow_definition` field can be passed directly back into `create_workflow` or `update_workflow` without modification. ## Programmatic flows The flows below all use the same JSON body shape (`CreateWorkflowRequest`). ### Run an existing workflow Pair a stored Workflow ID with `POST /workflows/run` to start a job from any client. The workflow lives server-side; your code triggers it and consumes results. ```python Python theme={null} import os, requests BASE = "https://api.sixtyfour.ai" HEADERS = {"x-api-key": os.environ["SIXTYFOUR_API_KEY"], "Content-Type": "application/json"} resp = requests.post( f"{BASE}/workflows/run", params={"workflow_id": "WORKFLOW_ID"}, headers=HEADERS, json={"webhook_payload": [{"email": "ada@example.com", "company": "Acme"}]}, ) resp.raise_for_status() print("job_id:", resp.json()["job_id"]) ``` ```javascript JavaScript theme={null} const BASE = "https://api.sixtyfour.ai"; const headers = { "x-api-key": process.env.SIXTYFOUR_API_KEY, "Content-Type": "application/json", }; const resp = await fetch(`${BASE}/workflows/run?workflow_id=WORKFLOW_ID`, { method: "POST", headers, body: JSON.stringify({ webhook_payload: [{ email: "ada@example.com", company: "Acme" }], }), }); const { job_id } = await resp.json(); console.log("job_id:", job_id); ``` Two optional body fields parameterize each run: | Field | Purpose | | - | - | | `webhook_payload` | Input rows for workflows whose source block is `webhook`. List or single object. | | `specs_override` | Overrides the first block's `specs` for this run only. Use when one workflow serves many parameterized inputs. | The full request/response shape and polling pattern live in [Workflow Execution](/api-reference/workflows/workflows-execution). For workflows whose source block is `read_csv`, upload the CSV to `/storage/csv/upload` and pass the returned `handle_id` as `specs_override.resource_handle_id`. See [Workflow Execution](/api-reference/workflows/workflows-execution). ### Create a workflow from JSON POST the definition to `create_workflow` to register a new workflow server-side. The response includes the assigned `id`, which you then pass to `run`. ```bash cURL theme={null} curl -X POST "https://api.sixtyfour.ai/workflows/create_workflow" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d @workflow.json ``` ```python Python theme={null} import os, json, requests BASE = "https://api.sixtyfour.ai" HEADERS = {"x-api-key": os.environ["SIXTYFOUR_API_KEY"], "Content-Type": "application/json"} with open("workflow.json") as f: body = json.load(f) resp = requests.post(f"{BASE}/workflows/create_workflow", headers=HEADERS, json=body) resp.raise_for_status() workflow_id = resp.json()["id"] print("created:", workflow_id) ``` ```javascript JavaScript theme={null} import fs from "node:fs/promises"; const BASE = "https://api.sixtyfour.ai"; const headers = { "x-api-key": process.env.SIXTYFOUR_API_KEY, "Content-Type": "application/json", }; const body = JSON.parse(await fs.readFile("workflow.json", "utf8")); const resp = await fetch(`${BASE}/workflows/create_workflow`, { method: "POST", headers, body: JSON.stringify(body), }); const { id } = await resp.json(); console.log("created:", id); ``` ## Choose your own Workflow ID You can pass an optional `id` (a valid UUID) in the request body to choose your own Workflow ID instead of letting the server assign one. The endpoint returns `409` if that ID is already taken. ## Deploy + Run from CI A typical deploy script saves the workflow definition with `update_workflow`, then triggers a run. `update_workflow` creates the workflow if `workflow_id` doesn't exist and updates it otherwise, so the same script is safe to run on every deploy. ```python Python theme={null} import os, json, requests BASE = "https://api.sixtyfour.ai" HEADERS = {"x-api-key": os.environ["SIXTYFOUR_API_KEY"], "Content-Type": "application/json"} WORKFLOW_ID = os.environ["WORKFLOW_ID"] with open("workflow.json") as f: body = json.load(f) u = requests.post( f"{BASE}/workflows/update_workflow", params={"workflow_id": WORKFLOW_ID}, headers=HEADERS, json=body, ) u.raise_for_status() r = requests.post( f"{BASE}/workflows/run", params={"workflow_id": WORKFLOW_ID}, headers=HEADERS, json={"webhook_payload": [{"email": "ada@example.com", "company": "Acme"}]}, ) r.raise_for_status() print("running:", r.json()["job_id"]) ``` With an API key, `create_workflow` and `update_workflow` reject an invalid definition with `400` and a `validation_errors` list that names the field, the rule, and where possible a `suggested_replacement` — see [Validation errors](/api-reference/workflows/workflows-management#validation-errors). A successful save means the definition passed those checks; an update that fails leaves the last valid saved definition in place. A run can still be rejected at start for conditions that depend on data, such as a source block whose file has not been uploaded yet. To deliver completed run results to your own URL, add an `outgoing_webhook` block to the workflow — see [Outgoing Webhooks](/api-reference/webhooks/outgoing). Polling, result download, and cancellation are documented in [Workflow Execution](/api-reference/workflows/workflows-execution). ## Authentication All programmatic endpoints accept either an API key or an OAuth client credential. `webhook_payload` and `specs_override` require machine authentication (API key or OAuth) — they are rejected for JWT (browser session) requests. The credential also determines ownership: the JSON definition has no `org_id` field, so a workflow created with a given API key belongs to that key's organization. To deploy the same JSON into a different organization, POST it using that organization's API key. | Auth method | When to use | Header | | - | - | - | | API key | Server-to-server scripts, CI, single-tenant deploys | `x-api-key: YOUR_API_KEY` | | OAuth client credentials | Multi-tenant integrations, third-party apps acting on behalf of a user | `Authorization: Bearer ACCESS_TOKEN` | See [Get API Key](/get-api-key) for API keys and [OAuth Clients](/guides/oauth-clients) for OAuth setup. # Roles & Permissions Source: https://docs.sixtyfour.ai/guides/roles-and-permissions Understand the organizational roles and permissions for Sixtyfour. Sixtyfour uses role-based access control (RBAC) to manage what organization members can see and do. Every member is assigned one of three roles: Member, Admin, or Owner. | Role | Inherits from | Typical use | | - | - | - | | **Member** | — | Developers and end users running API calls, or using the platform's Agent | | **Admin** | Member | Team leads managing keys, billing, and webhooks | | **Owner** | Admin | Billing and IT administrators with full org control | Each role is additive — Admins have everything Members have, and Owners have everything Admins have. ## Permission reference The table below lists every permission and which roles hold it. | Permission | Member | Admin | Owner | | - | :-: | :-: | :-: | | `org:read` | ✓ | ✓ | ✓ | | `org:write:rename` | | | ✓ | | `org:write:support` | | | ✓ | | `members:read` | ✓ | ✓ | ✓ | | `members:write:invite` | | ✓ | ✓ | | `members:write:role` | | | ✓ | | `members:write:remove` | | | ✓ | | `oauth:read` | ✓ | ✓ | ✓ | | `oauth:write` | | ✓ | ✓ | | `webhooks:read` | ✓ | ✓ | ✓ | | `webhooks:write` | | ✓ | ✓ | | `keys:read` | ✓ | ✓ | ✓ | | `keys:write` | | ✓ | ✓ | | `billing:read` | ✓ | ✓ | ✓ | | `billing:write` | | ✓ | ✓ | | `usage:read` | ✓ | ✓ | ✓ | | `logs:read` | | ✓ | ✓ | | `logs:export` | | ✓ | ✓ | | `agent:read` | ✓ | ✓ | ✓ | | `agent:write` | | ✓ | ✓ | | `data:read` | ✓ | ✓ | ✓ | | `data:write` | ✓ | ✓ | ✓ | | `integrations:write` | ✓ | ✓ | ✓ | ## Member The default role for anyone invited to an organization. | Permission | Description | | - | - | | `org:read` | View organization details | | `members:read` | View the member list | | `oauth:read` | View OAuth client configurations | | `webhooks:read` | View webhook configurations | | `keys:read` | View that API keys exist (not the key value) | | `billing:read` | View billing information and invoices | | `usage:read` | View usage dashboards and credit balance | | `agent:read` | View agent configurations | | `data:read` | Read enrichment and search data | | `data:write` | Run enrichment and search requests | | `integrations:write` | Manage third-party integrations | API key values are only visible at generation time. `keys:read` confirms a key exists — it does not expose the key value. ## Admin Admins have all Member permissions, plus the ability to manage team access and account configuration. | Permission | Description | | - | - | | `members:write:invite` | Invite new members to the organization | | `keys:write` | Create and revoke API keys | | `oauth:write` | Create and manage OAuth clients | | `webhooks:write` | Create and manage webhooks | | `billing:write` | Update billing details and payment methods | | `logs:read` | View detailed API call logs | | `logs:export` | Export API logs as CSV | | `agent:write` | Create and update agent configurations | ## Owner Owners have all Admin permissions, plus organization-level controls that affect membership structure and account identity. | Permission | Description | | - | - | | `members:write:role` | Change a member's role | | `members:write:remove` | Remove members from the organization | | `org:write:rename` | Rename the organization | | `org:write:support` | Manage support access and settings | Only an existing Owner can promote another member to Owner or remove an Owner from the organization. Assign this role carefully. ## Manage role assignments Role assignments are managed from the organization settings page in the Sixtyfour app. 1. Go to [Organization settings](https://app.sixtyfour.ai/settings/organization). 2. Open the **Team Members** section. 3. Change a member's role or remove them from the organization. You need `members:write:role` to change roles and `members:write:remove` to remove members — both held by **Owners** only. **Admins** can invite new members but cannot change existing roles. # Struct & Type Casting Source: https://docs.sixtyfour.ai/guides/struct-and-type-casting How the struct field shapes API responses and how Sixtyfour casts values to the types you expect. [People Intelligence](/api-reference/endpoint/people-intelligence) and [Company Intelligence](/api-reference/endpoint/company-intelligence) accept a `struct` field that defines exactly what data you want returned in `structured_data`. This guide explains how `struct` works and how the API resolves and casts types. ## How `struct` works Each key in `struct` becomes a field in the response's `structured_data`, and its value tells the agent what to find. You can pass either a plain-English description or an object with `description` and `type`: ```json theme={null} { "struct": { "email": "The individual's email address", "num_employees": {"description": "Approximate employee count", "type": "int"} } } ``` The agent uses these descriptions to guide its research. Be specific — `"company's primary Instagram handle"` returns better results than `"social"`. [QA Agent](/api-reference/endpoint/qa-agent) accepts string descriptions only and returns those extra fields as strings; the typed objects and casting rules below do not apply to it. ## Type casting The API automatically handles type casting with intelligent type preservation. ### Resolution priority Type resolution follows this priority: 1. **Explicit type definitions** in `struct` (highest) 2. **Original input types** from the request body (e.g., `target_company`, `lead_info`, `data`) 3. **Inferred types** from example values in `struct` 4. **String** (default) ### Supported types | Type | Example values | | - | - | | `"str"` or `"string"` | `"Pacific View Studios"` | | `"int"` or `"integer"` | `50`, `1000` | | `"float"` | `95.5`, `3.14` | | `"bool"` or `"boolean"` | `true`, `false` | | `"list"` | `["item1", "item2"]` | | `"list[str]"` | `["item1", "item2"]` | | `"list[int]"` | `[1, 2, 3]` | | `"list[float]"` | `[1.5, 2.7, 3.14]` | | `"list[bool]"` or `"list[boolean]"` | `[true, false]` | | `"dict"` | `"{\"key\": \"value\"}"` (stringified JSON) | The `"dict"` type returns a stringified JSON value, not a nested typed object. Parse it client-side with `json.loads()` (Python) or `JSON.parse()` (JavaScript). There is no image type. A field whose description asks for an image (a headshot, a logo, a screenshot) is an ordinary string field whose value is a permanent image URL hosted by Sixtyfour — see [Image fields](/api-reference/endpoint/people-intelligence#image-fields). ### Examples **Type override** — explicit `type` in `struct` overrides the original input type: ```json theme={null} { "lead_info": {"age": 30, "is_active": true}, "struct": { "age": {"type": "str"}, "location": "Current location" } } // Output: {"age": "30", "is_active": true, "location": "New York"} ``` **Explicit type definition** — useful when you want predictable shapes: ```json theme={null} { "struct": { "age": {"description": "Person's age", "type": "int"}, "is_verified": {"description": "Status", "type": "bool"} } } // Output: {"age": 30, "is_verified": true} ``` **Type inference from values** — types are inferred from the example values you pass: ```json theme={null} { "struct": { "score": 95.5, "active": false, "count": 42 } } ``` # Usage & API Logs Source: https://docs.sixtyfour.ai/guides/usage-logs Monitor credit balances, track API usage over time, inspect individual request logs, and export history as CSV. The Sixtyfour dashboard provides a usage dashboard and detailed API call logs so you can track spend, monitor request volume, and debug individual API calls. ## Usage Dashboard The dashboard displays three summary metrics and a time-series chart. | Metric | Description | | - | - | | **Current Balance** | Remaining prepaid credits on your account | | **Requests** | Total API requests made in the selected date range | | **Spend** | Total credit cost for the selected date range | The **API Usage Over Time** chart breaks down daily request volume by endpoint type — People Intelligence, Company Intelligence, Find email, Find phone, QA, Research agent, Search, and others. Use the date picker in the top right to adjust the time range. Usage Dashboard * Programmatically check your balance with the [`/check-balance`](/api-reference/account/check-balance) endpoint. * AI assistants can call the same data via the `sixtyfour_check_balance` tool on the [Intelligence MCP server](/developer-tools/mcp-setup). ## API Call Logs The **Detailed API Call Logs** table shows a complete history of every API request. Each row includes: | Column | Description | | - | - | | **Status** | Request outcome — `Success`, `Pending`, or `Error` | | **Endpoint** | The API endpoint called | | **Duration** | Total processing time | | **Cost** | Credits consumed by the request | | **HTTP** | HTTP status code returned | | **Timestamp** | Date the request was made | | **API Key** | Truncated key used for the request | | **Details** | Link to view the full request and response | ### Filtering Logs Use the dropdown filters above the table to narrow results: * **Status** — filter by `Success`, `Pending`, or `Error` * **Endpoint** — filter by specific API endpoint API Call Logs ## Exporting Logs You can export up to 10k rows of API logs as a CSV. Click the **Export** button above the logs table to download your API call history as a CSV file. The export dialog provides the following options: ### Time Range Select a preset range — **7 Days**, **30 Days**, **90 Days** — or choose **Custom** to define a specific date window. ### Optional Fields By default, exports include: Request ID, Date, Endpoint, Status, Duration, Cost, HTTP Status, API Key, and Error Message. You can add two optional fields: * **Include Request Input** — the request body sent to the API * **Include Response Output** — the response body returned from the API Click **Export CSV** to download. Export API Logs # Using the Agent Source: https://docs.sixtyfour.ai/guides/using-agents Use the Sixtyfour Agent to search, build workflows, and run intelligence queries in natural language. The Sixtyfour Agent is an AI assistant built into the platform that translates natural language queries into fully functional workflows. Instead of manually configuring blocks and connections, describe what you need and the Agent builds it — search, enrichment, filtering, output, and everything in between. The Agent has access to the entire Sixtyfour platform including: all [workflow blocks](/api-reference/workflows/workflow-blocks), the [Search API](/api-reference/search/search-overview), [intelligence endpoints](/api-reference/endpoint/company-intelligence), notebooks, integrations, and your account settings. Only Enterprise plan customers can use the Agent in the Sixtyfour Platform. ## Getting Started Open the Agent from the sidebar in the [Sixtyfour dashboard](https://app.sixtyfour.ai). Enter a query describing what you want to accomplish, and the Agent handles the rest — creating workflows, running searches, and returning results. You can then chat with the Agent to refine your results. Ask follow-up questions, adjust filters, add enrichment steps, or change the output format until the pipeline matches your needs. Getting Started ## What the Agent Can Do ### Search for People and Companies The Agent searches across Sixtyfour's dataset using natural language. Find leads by job title, company, location, seniority, or any combination — or search for companies by industry, size, funding stage, and other criteria. Example queries: * `"Find CTOs at Series B AI startups in San Francisco"` * `"Search for marketing directors at companies with 50–200 employees"` * `"Find competitors to Snowflake in the data infrastructure space"` Search for People and Companies ### Build and Manage Workflows The Agent creates end-to-end [workflows](/guides/building-workflows) from natural language. It selects the right blocks, configures them, connects them in the correct order, and handles data type conversions automatically. The Agent can also read, edit, run, and cancel existing workflows. It inspects a workflow's current state before making changes to prevent conflicts. Example queries: * `"Build a workflow that finds VPs of Sales at fintech companies, gets their emails, and saves verified results to a notebook"` * `"Add a filter to my workflow that removes leads without a LinkedIn URL"` * `"Run my lead enrichment workflow and notify me in Slack when it finishes"` ### Construct Intelligence Pipelines The Agent assembles multi-step pipelines using blocks across every category: | Category | Examples | | - | - | | Discovery | People search, company search, GitHub contributors, Google Maps businesses, job postings, Y Combinator companies | | Enrichment | AI-powered people and company research, email finding, phone finding, email verification, reverse lookups | | Extraction | Web scraping, subdomain enumeration, backlink analysis | | Transform | Filtering, deduplication, text generation, data type conversion, grouping | | AI | LLM-powered classification, extraction, or structured output per row | | Output | Save to notebooks, send Slack messages, fire webhooks | ### Handle CSV Data Upload a CSV file and the Agent incorporates it into a workflow automatically. The Agent generates inline data on the fly and provides real-time feedback directly in chat. ### Manage Notebooks The Agent lists, inspects, and creates [notebooks](/guides/building-workflows#output-blocks) — Sixtyfour's managed spreadsheets for storing enriched data. Workflows output directly to notebooks, and the Agent can set that up as part of any pipeline. ### Optimize for Cost The Agent proactively suggests ways to reduce credit usage. It places filters before expensive enrichment steps, warns before running high-cost operations, and requests only the fields you need. ### Access Account Information The Agent shows your credit balance, usage statistics, team members, and organization details. It can generate invite links for new members. ### Review Run History The Agent retrieves execution logs for any workflow, showing run status, timestamps, rows processed, and credit costs. ### Research the Web The Agent searches the web for up-to-date information when needed — identifying companies in a specific market, confirming current data, or informing workflow design with real-time context. ## How the Agent Works ```mermaid theme={null} flowchart LR Query["Your query"] --> Agent["Agent builds workflow"] --> Run["Workflow executes"] --> Results["Results in notebook"] ``` The Agent is built around a few core principles: * **Type-aware.** Sixtyfour workflows operate on two data types — **Lead** (people) and **Company** (organizations). The Agent ensures blocks connect in valid sequences and inserts conversion blocks (like Extract Leads) automatically when a type change is required. * **Safety first.** The Agent reads a workflow's current state before making edits, preventing conflicts with in-progress runs or existing block configurations. * **Proactive.** The Agent suggests optimizations and flags potential issues — like placing filters before expensive enrichment steps — rather than executing blindly. * **Honest.** If the Agent lacks enough information to answer a question or build a workflow, it says so rather than guessing. ## Agent Preferences You can provide instructions and context for the agent when responding to conversations. This is useful when you want to define the agent's behavior or provide it with context for your tasks. To configure agent preferences, go to your account settings and select Agent Preferences. Agent Preferences ### YOLO Mode With YOLO Mode enabled, the Agent can spend credits, modify notebooks, and run workflows without asking first. Review your workflow configuration and credit balance before enabling this setting. YOLO Mode gives the Agent more autonomy by auto-approving tool actions without confirmation. When enabled, the Agent executes actions — running workflows, creating notebooks, applying filters — without prompting you for permission at each step. This is useful when you trust the Agent to handle a multi-step pipeline end to end and don't want to approve every individual action. Enable YOLO Mode from the Agent Preferences page in your account settings. YOLO Mode ## Example Use Cases | Goal | Example query | | - | - | | Build a prospecting list | `"Find 50 engineering managers at healthcare companies in Boston and get their emails"` | | Research a market | `"Search for AI infrastructure companies founded after 2020 with more than 50 employees"` | | Enrich existing data | `"Take my leads notebook, enrich each person, filter for verified emails, and deduplicate"` | | Monitor competitors | `"Find all developers who starred Competitor's GitHub repo and get their LinkedIn profiles"` | | Automate outreach prep | `"Build a workflow that finds decision-makers at target accounts, researches each person, generates a personalized intro, and saves everything to a notebook"` | ## Agent Tips * **Be specific.** The more detail you give — job titles, locations, company criteria, desired output — the better the Agent's first result. * **Iterate.** The Agent is conversational. Refine your query, ask it to adjust filters, or add steps to an existing workflow. * **Let the Agent optimize.** It knows which block ordering minimizes credit usage. Describe the outcome you want and let it choose the pipeline structure. * **Use [Agent Preferences](/guides/using-agents#agent-preferences)** to provide instructions and context for the agent when responding to conversations. * **Enable [YOLO Mode](/guides/using-agents#yolo-mode)** when you want the Agent to execute without confirmation prompts — ideal for trusted, repeatable pipelines. # Using Webhooks Source: https://docs.sixtyfour.ai/guides/using-webhooks Use webhooks to trigger Sixtyfour workflows and to receive enrichment results in real time. Sixtyfour webhooks come in two directions: * **Incoming**: your system POSTs into Sixtyfour to trigger a workflow run. * **Outgoing**: Sixtyfour POSTs to your URL when an async job or workflow finishes. Both are optional — you can always trigger work via the standard sync APIs and poll for results — but together they let you wire Sixtyfour into event-driven systems with no glue scripts. ## Which direction do I need? | You want to... | Use | | - | - | | Kick off a workflow when something happens in your CRM, scheduler, or app | **Incoming** — POST to `/workflows/run` with a `webhook_payload` | | Get notified when an async enrichment job completes | **Outgoing** — set `webhook_url` on the async endpoint | | Get the full result of a workflow delivered to your URL when it completes | **Outgoing** — add an `outgoing_webhook` block to the workflow | | Both — trigger a workflow externally and have its results delivered back | **Incoming + outgoing** in the same workflow | ## Incoming webhooks (trigger workflows) Build a workflow whose **first block is a `webhook` source block**, then POST your payload to `/workflows/run`. Each row in the payload becomes one row in the workflow's input dataset. ```bash theme={null} curl -X POST "https://api.sixtyfour.ai/workflows/run?workflow_id=wf_abc123" \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "webhook_payload": [ { "company_name": "Acme Corp", "website": "acme.com" }, { "company_name": "TechStart Inc", "website": "techstart.io" } ] }' ``` Sixtyfour responds with a `job_id` you can use to track the run. A few rules to know: * API key authentication only — JWT (dashboard) sessions are rejected. * The source block must be `webhook` — the API returns 400 otherwise. * `read_csv` source blocks are API-triggerable but use a different mechanism — pass `specs_override.resource_handle_id` from `/storage/csv/upload` instead of `webhook_payload`. See [Workflow Execution](/api-reference/workflows/workflows-execution). See the [Incoming Webhooks reference](/api-reference/webhooks/incoming) for the full spec, validation rules, and configuration options. ## Outgoing webhooks (receive results) Sixtyfour POSTs to your URL through one signed, retried HTTP pipeline regardless of source. The two sources are: 1. **Async-job webhooks** — pass `webhook_url` in the body of any async endpoint (`/find-email-async`, `/people-intelligence-async`, etc.). Payload is a single job-result envelope. 2. **Workflow `outgoing_webhook` block** — add the block to a workflow. Payload is a richer envelope with `event_id`, `run_id`, inline `results`, and an optional signed `results_download_url`. Keep the `task_id` and poll `/job-status/{task_id}` if a callback does not arrive within your application's deadline. See [failure callback coverage](/api-reference/webhooks/outgoing). ### End-to-end flow (async-job example) ```mermaid theme={null} sequenceDiagram participant App as Your App participant SF as Sixtyfour App->>SF: POST /find-email-async { lead, webhook_url } SF-->>App: 200 { task_id } Note over SF: Enrichment runs... SF->>App: POST webhook_url { task_id, status, result } App-->>SF: 200 OK ``` ### Step 1: Send an async request with `webhook_url` ```bash cURL theme={null} curl -X POST https://api.sixtyfour.ai/find-email-async \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "lead": { "name": "Jane Smith", "company": "Northgate Labs", "linkedin_url": "https://linkedin.com/in/janesmith" }, "webhook_url": "https://your-server.com/webhooks/sixtyfour" }' ``` ```python Python theme={null} import requests response = requests.post( "https://api.sixtyfour.ai/find-email-async", headers={ "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, json={ "lead": { "name": "Jane Smith", "company": "Northgate Labs", "linkedin_url": "https://linkedin.com/in/janesmith" }, "webhook_url": "https://your-server.com/webhooks/sixtyfour" } ) task_id = response.json()["task_id"] ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.sixtyfour.ai/find-email-async", { method: "POST", headers: { "x-api-key": "YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ lead: { name: "Jane Smith", company: "Northgate Labs", linkedin_url: "https://linkedin.com/in/janesmith" }, webhook_url: "https://your-server.com/webhooks/sixtyfour" }) }); const { task_id } = await response.json(); ``` The response returns a `task_id`. Store it — you'll match it against the incoming webhook payload. ### Step 2: Handle the webhook When the job completes, Sixtyfour POSTs the result to your `webhook_url`. Your handler must return a 2xx status code within 10 seconds. Return 2xx even for unknown `task_id` values. Returning a non-2xx triggers retries, which wastes delivery attempts. For the workflow `outgoing_webhook` block, the request shape and headers are identical — only the JSON envelope is different (richer, with workflow metadata and an optional `results_download_url`). See the [Outgoing Webhooks reference](/api-reference/webhooks/outgoing) for both payload shapes side by side, plus signing/verification code samples. ### Signing secrets and verification Once you generate a signing secret in [Settings → Webhooks](https://app.sixtyfour.ai/settings/webhooks), every outgoing delivery includes a `Sixtyfour-Signature` header (HMAC-SHA256 over the raw body). The same secret signs both async-job webhooks and `outgoing_webhook` block deliveries. See [Signing Secrets & Verification](/api-reference/webhooks/signing-secrets) for the full algorithm, working Python (Flask) and Node.js (Express) code samples, and rotation behavior. Verify against the raw request body before JSON parsing. Re-serializing a parsed dict changes the bytes and verification will fail. ## Testing locally Your local development server isn't reachable from the internet. Use a tunneling tool to expose it: ```bash theme={null} # ngrok ngrok http 3000 # Then use the generated URL as your webhook_url: # https://abc123.ngrok-free.app/webhooks/sixtyfour ``` Other options include [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) and [localtunnel](https://github.com/localtunnel/localtunnel). Use the HTTPS URL from your tunnel as the `webhook_url` value (or the `outgoing_webhook` block's `url`). ## Production checklist * Track your tasks and runs by storing `task_id` (async jobs) or `run_id` (workflows) from each request, then validate incoming webhooks against your stored set. * Dedupe with `Sixtyfour-Event-Id` — retries can deliver the same event more than once, so treat the event ID as your idempotency key. * Once signing is enabled, reject deliveries with a missing or invalid `Sixtyfour-Signature` header. * Monitor delivery. If all 5 retry attempts fail, the webhook is marked as undelivered. Results remain available via `GET /job-status/{task_id}` (async jobs) or the workflow result download endpoints — see [Outgoing Webhooks → Retry behavior](/api-reference/webhooks/outgoing#retry-behavior) for the full retry schedule. # Workflow Templates Source: https://docs.sixtyfour.ai/guides/workflow-templates Browse, run, and create reusable workflow templates in the Sixtyfour platform. Workflow templates are pre-built workflows for common Sixtyfour use cases — lead enrichment, due diligence, recruiting research, email campaigns, and more. Use a template to skip the blank canvas and ship a working workflow in minutes. Save your own templates to share with teammates in your organization. Only Enterprise plan customers can use Workflow Templates to create workflows in the Sixtyfour Platform. Click [Build a Workflow](https://app.sixtyfour.ai/workflows/new) in the sidebar of the Sixtyfour dashboard to open the Templates gallery. Workflow templates gallery showing categories, search, and template cards ## Browse the gallery The Templates page lists every template available to you, grouped by section. Filter the gallery using the search bar and category chips at the top of the page. * All Templates — every template in your org, plus all global templates * Intelligence — research workflows for people and companies * Data Enrichment — append missing fields to existing lists * Recruitment — sourcing and candidate research * Email Campaigns — find verified emails and route them to outbound * Marketing — campaign analysis, audience research, competitor monitoring * Trust & Safety — entity diligence, KYC/KYB, deanonymization Additional categories — including Deanonymization, Finance, Background Checks, OSINT, and AML/KYC — appear in the **Category** dropdown next to the chip filters when relevant templates are available to your organization. Templates marked Locked require an Enterprise plan with OSINT-grade intelligence enabled — click one to see upgrade options. ## Use a template Click any template card to open its preview. The preview shows the template's category, complexity, use case, and every step in the workflow. Template preview modal showing the steps in an Entity Due Diligence workflow 1. Click a template card to open the preview modal. 2. Review the steps and confirm the template fits your use case. 3. Click **Use Template**. Sixtyfour creates a new workflow in the Workflow Editor, pre-filled with the template's defined blocks. From there you can run it as-is or customize any block — change inputs, adjust return fields, add filters, swap output destinations — before you run it. Edits to your copy do not affect the original template or anyone else using it. For details on what each block does, see [Building Workflows](/guides/building-workflows). ## Create a template You can convert any of your existing workflows into a reusable template. There are three entry points. ### From the Templates page 1. Open the [Templates page](https://app.sixtyfour.ai/templates). 2. Click **Create** in the top-right corner. 3. Select the workflow you want to convert from the list. 4. Fill in the template details (see [Template fields](#template-fields) below). 5. Click **Create Template**. ### From the Workflow Editor 1. Open any workflow with at least one block. 2. Click the menu in the workflow header and select **Save as Template**. 3. Fill in the template details and click **Create Template**. ### From Workflow History 1. Open your [Workflow History](https://app.sixtyfour.ai/workflows). 2. Find the workflow you want to convert and click the kebab menu (⋮) in its row. 3. Select **Save as Template**. 4. Fill in the template details and click **Create Template**. ### Template fields | Field | Required | Description | | - | - | - | | Name | Yes | Short, descriptive name (e.g., "LinkedIn Lead Enrichment"). | | Description | Yes | One- or two-sentence overview of what the template does. | | Use Case | Yes | When someone should pick this template. Surfaces in the preview modal. | | Category | Yes | One of the [template categories](#browse-the-gallery). | | Complexity | Yes | `Quick`, `Standard`, or `Advanced`. Sets reader expectations. | | Estimated Time | Yes | How long a typical run takes (e.g., "10 minutes"). | | Tags | No | Searchable keywords like `linkedin`, `enrichment`, `kyc`. | | Feature this template | No | Pins this template to the top of its section. | The template captures your workflow's blocks and connections, not its data. Any sample inputs or results from past runs are not included. ## Tips * Start from a template, then customize the blocks to match your exact return fields, filters, and output destination. * Convert your team's best-performing workflows into org templates so new teammates start from a known-good baseline. * Write the Use Case field like advice to a new hire, not a marketing tagline — it's what teammates see in the preview modal. * Tag for findability. Tags power the search bar, so add the platforms, data types, and industries the template applies to. # GitHub Integration Source: https://docs.sixtyfour.ai/integrations/github-integration Find and enrich developers from GitHub repositories — contributors, stargazers, watchers, and more — using Sixtyfour workflows. Connect GitHub to Sixtyfour and turn repositories into lead lists. Pull contributors, stargazers, and watchers from any repo, or search GitHub for users, repositories, and code. Only Enterprise plan customers can use the GitHub integration in the Sixtyfour Platform. ## What You Can Do | Capability | Description | | - | - | | **Find people from repos** | Extract contributors, stargazers, or watchers from GitHub repositories | | **Search GitHub** | Search for repositories, users, or code by keyword | | **Enrich developers** | Pipe GitHub profiles into enrichment blocks to find emails, companies, and contact details | ## Quickstart In the [Sixtyfour dashboard](https://app.sixtyfour.ai), go to **Integrations** and select **GitHub**. Authorize access to your GitHub account. Open the [Workflow Editor](https://app.sixtyfour.ai/workflows/new) and create a new workflow. Choose one of the GitHub source blocks: | Block | Use case | | - | - | | **GitHub Find People** | Get contributors, stargazers, or watchers from a specific repository | | **GitHub Search** | Search GitHub for repositories, users, or code matching a query | **GitHub Find People** — Select the role type (contributors, stargazers, or watchers) and provide a repository column or value (e.g., `sixtyfour-ai/docs`). **GitHub Search** — Choose a search type (repositories, users, or code) and enter your query. Chain enrichment blocks after the GitHub source to build out lead profiles: ```mermaid theme={null} flowchart LR GFP[GitHub Find People] --> EP[Enrich People] --> FE[Email address] --> Append[Notebook] ``` Run the workflow from the editor. Sixtyfour pulls profiles from GitHub and processes each one through your pipeline. ## Example Workflows ### Recruit contributors from an open-source project ```mermaid theme={null} flowchart LR GFP[GitHub Find People] --> Enrich_People[Enrich People] --> Find_Email[Email address] --> Filter --> Append[Notebook] ``` ### Find developers interested in a technology ```mermaid theme={null} flowchart LR GS[GitHub Search] --> Enrich_People[Enrich People] --> Find_Email[Email address] --> Slack ``` ## Block Reference | Field | GitHub Find People | GitHub Search | | - | - | - | | Block type | `github_find_people` | `github_search` | | Pipeline position | Source (first block) | Source (first block) | | Requires integration | Yes | Yes | | Key config | Role type, repository column | Search type, query | GitHub source blocks run from the Workflow Editor. To trigger workflows programmatically via API, use a [Webhook source block](/guides/building-workflows#source-blocks) instead and pass your GitHub data as the payload. # Google Sheets Integration Source: https://docs.sixtyfour.ai/integrations/google-sheets-integration Export enrichment results and workflow output directly to Google Sheets from Sixtyfour. Connect Google Sheets to Sixtyfour and push workflow results into a spreadsheet in your Google account — no CSV downloads or manual copy-paste. Only Enterprise plan customers can use the Google Sheets integration in the Sixtyfour Platform. ## What You Can Do | Capability | Description | | - | - | | **Export to Sheets** | Send workflow results to a Google Sheet in your connected account | | **Per-account auth** | Authorize the specific Google account you want to export to | | **Automatic columns** | Sixtyfour writes each result column to a matching sheet column | | **Reusable connection** | Connect once and reuse the integration across workflows | ## Quickstart In the [Sixtyfour Platform](https://app.sixtyfour.ai), open the download results you want to export and select **Export to Google Sheets**. Sign in with the Google account you want to export to and grant Sixtyfour permission to create and edit spreadsheets on your behalf. If you have multiple Google accounts, make sure to pick the one that owns the destination Drive. Confirm the export. Sixtyfour creates a new Google Sheet in your Drive and writes your workflow results into it. A link to the sheet appears in the Platform when the export completes. ## What You Can Export | Result Type | Exportable to Google Sheets | | - | - | | Search results | Yes | | Workflow results | Yes | | Agent Search results | Yes | | Notebooks results | Yes | # HubSpot Integration Source: https://docs.sixtyfour.ai/integrations/hubspot-integration Import contacts and companies from HubSpot CRM into Sixtyfour workflows for enrichment, email discovery, and more. Connect HubSpot to Sixtyfour and enrich your CRM data automatically. Import contacts or companies from HubSpot, run them through enrichment and contact-finding blocks, and push clean results to your team. Only Enterprise plan customers can use the HubSpot integration in the Sixtyfour Platform. ## What You Can Do | Capability | Description | | - | - | | **Import contacts** | Pull contact records from HubSpot into a Sixtyfour workflow | | **Import companies** | Pull company records from HubSpot into a Sixtyfour workflow | | **Enrich CRM data** | Run imported records through AI enrichment, email finding, and phone finding | | **Keep data clean** | Deduplicate against existing notebooks to avoid processing the same records twice | ## Quickstart In the [Sixtyfour dashboard](https://app.sixtyfour.ai), go to **Integrations** and select **HubSpot**. Authorize access to your HubSpot CRM. Open the [Workflow Editor](https://app.sixtyfour.ai/workflows/new) and create a new workflow. Select **HubSpot Import** as the source block. Choose the object type to import: | Object type | What it imports | | - | - | | **Contacts** | People records from your HubSpot CRM | | **Companies** | Company records from your HubSpot CRM | The object type determines the data type for downstream blocks. Contacts map to **Lead** data, companies map to **Company** data. Build out your pipeline after the HubSpot source. A common pattern for CRM enrichment: ```mermaid theme={null} flowchart LR HubSpot[HubSpot Import] --> Enrich_People[Enrich People] --> Find_Email[Email address] --> Filter --> Append[Notebook] ``` Run the workflow from the editor. Sixtyfour imports your HubSpot records and processes each one through the pipeline. ## Example Workflows ### Research companies and discover decision-makers ```mermaid theme={null} flowchart LR HubSpot[HubSpot Import] --> Enrich_Companies[Enrich Companies] Enrich_Companies --> Extract_Leads[Extract Leads] Extract_Leads --> Find_Email[Email address] Find_Email --> Slack ``` ### Deduplicate before enriching recurring imports ```mermaid theme={null} flowchart LR HubSpot[HubSpot Import] --> Dedup[Deduplicate with Notebook] --> Enrich_People[Enrich People] --> Find_Email[Email address] --> Append[Notebook] ``` Use the **Deduplicate with Notebook** block on recurring imports to skip records you have already processed. ## Block Reference | Field | Value | | - | - | | Block type | `hubspot_import` | | Pipeline position | Source (first block) | | Requires integration | Yes — HubSpot account must be connected | | Key config | Object type (contacts or companies) | HubSpot Import runs from the Workflow Editor. To trigger workflows programmatically via API, use a [Webhook source block](/guides/building-workflows#source-blocks) instead and pass your CRM data as the payload. # Slack Integration Source: https://docs.sixtyfour.ai/integrations/slack-integration Send enrichment results and workflow notifications directly to Slack channels and DMs from Sixtyfour. Connect Slack to Sixtyfour and deliver workflow results to your team in real time — no CSV exports or manual handoffs. Only Enterprise plan customers can use the Slack integration in the Sixtyfour Platform. ## What You Can Do | Capability | Description | | - | - | | **Send to channels** | Post workflow results to any public or private Slack channel | | **Send to DMs** | Deliver results directly to individual team members | | **Dynamic messages** | Use `{column}` substitution to build messages from your data | | **Mention by email** | Use `{@column}` to @mention Slack users by matching their email address | ## Quickstart In the [Sixtyfour dashboard](https://app.sixtyfour.ai), go to **Integrations** and select **Slack**. Authorize access to your Slack workspace. This grants Sixtyfour permission to post messages on your behalf. Open the [Workflow Editor](https://app.sixtyfour.ai/workflows/new) and build your pipeline. Add a **Slack** block as the final output step. Select a channel or DM recipient, then write your message template. Reference columns from your data with `{column_name}` syntax. Only channels where the Sixtyfour Slack app has been invited appear in the channel selector. Invite the app to a channel first with `/invite @Sixtyfour` in Slack. ```text theme={null} New lead found: {name} at {company} Title: {title} Email: {email} ``` To @mention someone in your workspace, use `{@email}` where `email` is a column containing the person's Slack-linked email address. Run the workflow from the editor. Each row in your results triggers a Slack message with the column values filled in. ## Example Workflow Enrich a list of leads, find their emails, filter to verified addresses, and notify your sales team in Slack: ```mermaid theme={null} flowchart LR Search --> Enrich_People[Enrich People] --> Find_Email[Email address] --> Filter --> Slack ``` ## Block Reference | Field | Value | | - | - | | Block type | `send_slack_message` | | Pipeline position | Output (final block) | | Requires integration | Yes — Slack workspace must be connected | # Welcome to Sixtyfour Source: https://docs.sixtyfour.ai/introduction Intelligence infrastructure for people and entities. Deep research on any person or company at scale.
Deploy AI agents that investigate people and companies, resolve identities, map relationships, and surface risk signals — all through a single API.
## Developer Tools Give your AI coding tools built-in Sixtyfour knowledge Structured APIs and Docs references for your AI tools ## APIs Deploy AI research agents for people and companies. * [People Intelligence](/api-reference/endpoint/people-intelligence) * [Company Intelligence](/api-reference/endpoint/company-intelligence) * [Find Email](/api-reference/endpoint/find-email) * [Find Phone](/api-reference/endpoint/find-phone) * [Confidence Scoring](/api-reference/endpoint/field-confidence) * [Age Verification](/api-reference/endpoint/age-verification) Query millions of people and company records with natural language. * [Deep Search Overview](/api-reference/search/search-overview) * [Deep Search](/api-reference/search/search-endpoints) * [Filter Search](/api-reference/search/filter-search) * [Search Exclusions](/api-reference/search/search-exclusions) Build, run, and automate multi-step intelligence pipelines. * [Workflow Overview](/api-reference/workflows/workflows-overview) * [Workflow Blocks](/api-reference/workflows/workflow-blocks) * [Workflow Scheduling](/api-reference/workflows/workflow-schedules) * [Programmatic Workflows](/guides/programmatic-workflows) ## Developer Resources Make your first API call in minutes. * [Get your API key](/get-api-key) * [First API call](/api-quick-start) * [API Playground](https://app.sixtyfour.ai/playground/) Go deeper with our technical guides. * [Using OAuth Clients](/guides/oauth-clients) * [Batch Processing](/guides/batch-processing) * [Struct & Type Casting](/guides/struct-and-type-casting) * [Using Webhooks](/guides/using-webhooks) Understand how credits work and plan your API usage. * [Credit costs by endpoint](/guides/credits-and-pricing) * [Usage & API Logs](/guides/usage-logs)