Skip to main content

How to set up Clay

Authentication

Clay runs on your own Clay workspace, so you need a Clay account and an API key:
  1. In Clay, open Settings, then Account, then API keys
  2. Create a key and copy it
  3. Paste the API key in Cargo when connecting
The key is tied to one Clay workspace. Every call Cargo makes is billed to that workspace’s Clay credits, and its plan sets the search limits below.
Code slugs — integration slug: clay · actions: runRoutine, searchPeople, searchCompanies. In a workflow, declare the connector in uses and call uses.<key>.runRoutine(...).

Clay actions

Run Routine

Run a Clay routine over one or more rows and return its results. Clay runs routines asynchronously, so Cargo submits the run and then polls until it finishes, up to roughly 26 minutes. Required fields:
  • Routine: The routine ID, written as type:id
  • Items: JSON for the rows to run. Either an object of input name/value pairs for one row, or an array of up to 100 such objects

Getting the routine ID right

A routine ID names the kind of object as well as the object itself: The ID you copy out of a Clay URL is only the second half. Passing that bare — wf_abc123 rather than workflow:wf_abc123 — fails with Invalid routine id. Getting the prefix right is not enough on its own. The function or Workflow also has to be registered as a routine in Clay, and a routine that does not exist fails with Routine not found even when the ID is well formed. Registering is a Clay-side step: the Clay CLI does it with clay routines create function <tableId> or clay routines create workflow <workflowId>, and clay routines list shows what is already registered. Then open the routine in Clay, go to Details, and enable API access. Enabling MCP or Claygent access does not enable the API. Clay does not publish a routine’s input names through the API, so the keys in Items have to match what the routine expects exactly. A typo surfaces as a Clay error on the row rather than a validation error in Cargo. The action returns one entry per row: A completed run can contain failed rows. The node still succeeds, and the title reports how many rows failed. Filter on status == "complete" downstream before using result. Use case: Score or enrich a batch of accounts with the waterfall your GTM team already built in Clay, then route on the result.

Search People

Find people in Clay’s GTM database with a Clay search query. Required fields:
  • Query: A Clay advanced search query
Optional fields:
  • Limit: Maximum records to retrieve across all pages. Defaults to 50, up to 1,000
  • Records per request: How many records to pull per call. Defaults to 50
Use case: Build a target list of decision makers at accounts that match your ICP, then push them into a sequence.

Search Companies

Find companies in Clay’s GTM database. Same fields as Search People. Use case: Source accounts matching a headcount band, industry, and technology profile before enrichment. Clay works out whether a query targets people or companies from the query itself. If you run a company query through Search People, the action tells you to use the other one rather than returning the wrong shape. Each action returns the fields Clay publishes for its own source, so they are available in a mapper without running the node first: Only the identifier is guaranteed. A search matches on your predicate, not on the record being complete, so every other field can come back empty. matched_experiences holds only the roles your query matched on, not the person’s full history. When Clay stops a search at your plan’s per-search cap rather than because the query ran out of matches, the node title says so — otherwise a capped search reads as a complete answer.

Writing a Clay query

Queries follow select from <source> where <predicate>. The word after select from decides which action the query belongs to — a select from people query only runs in Search People, and a select from companies query only in Search Companies. Clay reads that word literally rather than inferring intent, so a query about people at French companies is still a people query.

Search People

A person’s role, employer, and tenure all live on their experiences, so most people queries are an experiences.any(...) with is_current = true inside it. Drop is_current only when you want people who ever held the role. The buying committee at accounts matching your ICP. Filter on the role and on the employer in one predicate:
Someone newly in the job. A new leader with a budget is the classic trigger — start_date is when they began that role:
People whose employer runs a tool you integrate with:
One exact title, no fuzzy expansion. is_similar_to pulls in variants like Chief Technology Officer and VP Engineering, which is usually what you want. Use = when it isn’t:

Search Companies

Firmographic ICP:
Headquartered in a country. Without is_headquarters = true this matches any office, which is usually not what “companies in France” means:
Hiring for a role. jobs is job postings, so this is a hiring signal, not a headcount fact:
Running a given technology:
Has a role in seat. people is current and former employees; swap exists for count(...) >= N to size a team, and negate it with not to find the whitespace where the role does not exist yet:

Traps

  • is_similar_to for job titles, not contains. contains matches whole words only, so it misses every title variant
  • Company location lives in the locations array, not a headquarters field
  • There is no between. Use two comparisons, or a bucketed field
  • company_size and annual_revenue are bucketed enums and reject < and >. Match their exact values with = or in (...). Prefer them over estimated_employee_count, which is for an exact-headcount ask
  • Growth fields are ratios, not percentages. “+20% in a year” is employee_growth_12mo > 1.2
  • Dates are months. Use today() - interval 3 month; day and week intervals are rejected
  • Profile fields such as location_country are not valid inside experiences.any(...). Put them at the top level
  • Aggregates do not nest. Write people.exists(is_current = true and job_title is_similar_to ("Engineer")), never people.exists(experiences.any(...))
Count-mode queries and job queries are not supported through the API. The full grammar and field catalog live in Clay’s search reference.

Best practices

  • Keep Items small and explicit. One row per record you want enriched, with only the inputs the routine declares
  • Filter routine results on status before mapping result into a model, so failed rows do not write empty columns
  • Leave Records per request empty unless your Clay workspace is on a paid plan. Free and Trial workspaces reject anything above 50
  • Set Limit to what you actually need. Search results count against a rolling 30-day allowance on your Clay plan, and a generous limit spends it quickly
  • Treat a long routine as a risk in time-sensitive workflows. A run that has not finished in about 26 minutes fails the node

Credits

Clay does not consume Cargo credits. Every call is billed to the Clay workspace that issued the API key, in Clay’s own credits. Cargo caching still applies and will avoid repeating an identical call.

Rate limits

Clay meters its public API per workspace rather than per endpoint, so all three actions share one budget of 300 requests per minute. Cargo spreads calls to stay under it. Clay also limits how many searches one workspace can have running at once, and rejects a search that starts while another is still going. Cargo therefore runs Search People and Search Companies one call at a time, so a search node over a large batch works through the batch rather than failing partway. A single search call can take up to a minute, so expect list building to be slower than the other actions. Search results are capped separately, by Clay plan: Exceeding one of these returns an error from Clay naming the limit you hit, which Cargo surfaces on the node.