# Clay

<!-- BEGIN GENERATED: guide-map -->
## Guide map

Read only the section needed for the next decision. Line ranges are exact
for this generated runtime target.

| Section | What it covers | Lines |
| --- | --- | --- |
| [Purpose](#purpose) | Scope, ownership, and the central operating question. | 27-37 |
| [Use This When](#use-this-when) | Tasks and conditions that belong in this guide. | 38-46 |
| [Do Not Use This For](#do-not-use-this-for) | Boundaries and the correct routes for excluded work. | 47-56 |
| [Choose A Path](#choose-a-path) | Decision hierarchy for selecting the right capability or provider. | 57-68 |
| [Core SDK Surfaces](#core-sdk-surfaces) | Exact source-backed callable contracts owned by this guide. | 69-88 |
| [Authentication](#authentication) | Verbiflow owns the server-side Clay credential for both Local SDK and Hosted execution. | 89-100 |
| [Input Contracts](#input-contracts) | `source_type` is exactly `"people"` or `"companies"`. | 101-120 |
| [Output And Handle Contracts](#output-and-handle-contracts) | Immediate search returns a dictionary with: | 121-142 |
| [Operating Pattern](#operating-pattern) | The normal end-to-end execution sequence. | 143-192 |
| [Rate Limits And Concurrency](#rate-limits-and-concurrency) | Clay applies its Public API rate limit per workspace, not per Verbiflow job. | 193-218 |
| [Usage And Cancellation](#usage-and-cancellation) | Clay uses `free_observed` accounting. | 219-234 |
| [Verification](#verification) | Evidence required before reporting success. | 235-246 |
| [Troubleshooting](#troubleshooting) | Failure diagnosis and corrective routes. | 247-257 |
| [Related References](#related-references) | Adjacent boundaries and when to cross them. | 258-266 |
<!-- END GENERATED: guide-map -->

## Purpose

Use this provider guide after `company-search.md` or `contact-search.md` chooses
Clay. `vf.clay` exposes Clay Public API people and company search in both Local
and Hosted runtimes. Verbiflow owns the server credential, durable artifact,
workspace pacing, progress, and observed usage record.

Clay job search, tables, workbooks, views, enrichment columns, and arbitrary
Clay CLI commands are not supported by this Public API surface. Use
`job-search.md` and `vf.sources.jobs.find_jobs(...)` for openings.

## Use This When

- The company-search outcome needs Clay-native company filters.
- The contact-search outcome needs Clay-native role, person, company, or
  lookalike filters.
- The result may be larger than one immediate page and must survive as an
  artifact.
- You need the live Clay filter catalog instead of guessing a request shape.

## Do Not Use This For

| Outcome | Owning Guide |
| --- | --- |
| Find job openings or companies hiring for a role | `job-search.md` |
| Enrich already-known people or companies | `contact-enrichment.md` or `company-enrichment.md` |
| Operate Clay tables, workbooks, columns, webhooks, or views | Unsupported by `vf.clay` |
| Read or extract local Clay browser authentication state | Unsupported. Clay uses the server-side Public API only. |
| Search paid Crunchbase data | `company-search.md`, then `crunchbase.md` |

## Choose A Path

1. Start from `company-search.md` or `contact-search.md` and define the output
   entity, qualification rule, exclusions, evidence fields, and maximum rows.
2. Call `vf.clay.filter_fields(...)` for the matching source type unless the
   exact live field contract was already inspected in this run.
3. Use an immediate search only for one page of at most 500 rows.
4. Use a durable search for pagination, cancellation, artifact export, or any
   request above 500 rows.
5. Qualify the returned sample against the commercial outcome before treating
   provider membership as final ICP truth.

## Core SDK Surfaces

<!-- BEGIN GENERATED: core-sdk-surfaces -->
| Function | Use When | Inputs | Output And Handles To Preserve | Side Effect | Source |
| --- | --- | --- | --- | --- | --- |
| `vf.clay.filter_fields` | List live Clay Public API filter fields and allowed values. | <strong>Required</strong>: `source_type` (people \| companies; required): Allowed values: people, companies.<br><strong>Optional</strong>: none | `clay_filter_fields` payload returned by the SDK operation. Inspect the documented function reference and `raw`/dictionary fields when debugging provider-specific data. | read | installed SDK: `verbiflow/clay.py:17-39` |
| `vf.clay.search_companies` | Search companies through Clay's Public API using the backend-owned API key. | <strong>Required</strong>: `filters` (Mapping[str, object]; required): Clay company-search filters using exact field names and value shapes returned by clay.filter_fields('companies').<br><strong>Optional</strong>: `limit` (int; optional; default `100`): Maximum number of records, preview rows, or results to request. | `clay_search_results` payload returned by the SDK operation. Inspect the documented function reference and `raw`/dictionary fields when debugging provider-specific data. | provider_call | installed SDK: `verbiflow/clay.py:161-198` |
| `vf.clay.search_people` | Search people through Clay's Public API using the backend-owned API key. | <strong>Required</strong>: `filters` (Mapping[str, object]; required): Clay people-search filters using exact field names and value shapes returned by clay.filter_fields('people').<br><strong>Optional</strong>: `limit` (int; optional; default `100`): Maximum number of records, preview rows, or results to request. | `clay_search_results` payload returned by the SDK operation. Inspect the documented function reference and `raw`/dictionary fields when debugging provider-specific data. | provider_call | installed SDK: `verbiflow/clay.py:41-78` |
| `vf.clay.start_companies_search` | Start a durable Clay company search with paginated artifact results. | <strong>Required</strong>: `filters` (Mapping[str, object]; required): Clay company-search filters using exact field names and value shapes returned by clay.filter_fields('companies').<br><strong>Optional</strong>: `limit` (int; optional; default `100`): Maximum number of records, preview rows, or results to request. | `PendingJob`: `job_id`, optional `artifact_id`, `status`, `finished`, `failed`, `error_message`, progress, preview rows, and summary. Prefer `vf.jobs.result(pending)` for wait/page/report/stage follow-up.<br><strong>Common next calls</strong>: `vf.jobs.get`, `vf.jobs.stream_rows`, `vf.jobs.cancel`, `vf.jobs.export_csv` | artifact_job | installed SDK: `verbiflow/clay.py:114-146` |
| `vf.clay.start_people_search` | Start a durable Clay people search with paginated artifact results. | <strong>Required</strong>: `filters` (Mapping[str, object]; required): Clay people-search filters using exact field names and value shapes returned by clay.filter_fields('people').<br><strong>Optional</strong>: `limit` (int; optional; default `100`): Maximum number of records, preview rows, or results to request. | `PendingJob`: `job_id`, optional `artifact_id`, `status`, `finished`, `failed`, `error_message`, progress, preview rows, and summary. Prefer `vf.jobs.result(pending)` for wait/page/report/stage follow-up.<br><strong>Common next calls</strong>: `vf.jobs.get`, `vf.jobs.stream_rows`, `vf.jobs.cancel`, `vf.jobs.export_csv` | artifact_job | installed SDK: `verbiflow/clay.py:80-112` |
<!-- END GENERATED: core-sdk-surfaces -->

The supported namespace is exactly:

- `vf.clay.filter_fields(...)`
- `vf.clay.search_people(...)`
- `vf.clay.search_companies(...)`
- `vf.clay.start_people_search(...)`
- `vf.clay.start_companies_search(...)`

## Authentication

Verbiflow owns the server-side Clay credential for both Local SDK and Hosted
execution. Agents and SDK users do not create, supply, request, print, or store
a Clay API key. Never place one in SDK code, Play files, task prompts, request
payloads, browser storage, or client-side configuration.

If `vf.clay.*` reports that Clay is not configured, treat it as platform
readiness—not an instruction to collect a credential from the user. Preserve
the SDK receipt and report the blocked operation. Operator provisioning of the
server secret belongs to deployment configuration, outside this SDK Skill.

## Input Contracts

`source_type` is exactly `"people"` or `"companies"`. `filters` must be a
non-empty JSON object whose field names, value types, and allowed values match
the live catalog returned for that source type. Do not translate natural
language into invented keys such as `query`, `titles`, or `domains` unless the
catalog actually exposes those exact names.

`limit` controls returned rows, not provider page count:

| Operation | Limit | Behavior |
| --- | --- | --- |
| `search_people`, `search_companies` | 1 to 500, default 100 | Creates a search and advances its stateful iterator once. |
| `start_people_search`, `start_companies_search` | 1 to 10,000, default 100 | Pages sequentially into a durable Growth data artifact. |

Filter discovery returns `source_type`, `fields`, and `guidance`. Each field
contains its `name`, provider type, optional description, and optional allowed
values. Guidance contains provider behavior, field-specific advice, and
create-request examples when Clay publishes them.

## Output And Handle Contracts

Immediate search returns a dictionary with:

| Field | Meaning |
| --- | --- |
| `source_type` | The exact Clay source used. |
| `search_id` | Provider search identity. Preserve it in diagnostics. |
| `data` | Returned row dictionaries, clamped to the requested limit. |
| `returned` | Exact number of rows in `data`. |
| `has_more` | Whether Clay reported more iterator data after this page. |

Durable start returns `PendingJob`. Preserve `job_id` immediately and
`artifact_id` when present. Normalize the handle with `vf.jobs.result(job)`.
The resulting `JobResult` owns `wait`, `get`, `rows`, `iter_rows`, export,
report, and sequence-staging follow-up. Preview rows are not the full artifact.

Each durable artifact retains the capability, organization scope, actor,
authentication method, Play identity when present, requested limit, provider
request counts, observed rate-limit metadata, row count, schema, and terminal
outcome.

## Operating Pattern

### Discover Filters

```python
import verbiflow as vf

people_fields = vf.clay.filter_fields("people")
company_fields = vf.clay.filter_fields("companies")

print([field["name"] for field in people_fields["fields"][:20]])
print([field["name"] for field in company_fields["fields"][:20]])
```

### Immediate Search

```python
people = vf.clay.search_people(
    filters={"job_title_keywords": ["VP Engineering"]},
    limit=100,
)

companies = vf.clay.search_companies(
    filters={"industries": ["Computer Software"]},
    limit=100,
)

print(people["search_id"], people["returned"], people["has_more"])
print(companies["search_id"], companies["returned"], companies["has_more"])
```

### Durable Search

```python
import verbiflow as vf

pending = vf.clay.start_people_search(
    filters={"job_title_keywords": ["Founder"]},
    limit=2_000,
)

print(pending.job_id, pending.artifact_id)
result = vf.jobs.result(pending).wait(require_success=True, timeout=900)
rows = result.rows(limit=2_000)
print(result.job_id, result.artifact_id, len(rows))
```

For large downstream processing, prefer `result.iter_rows(...)` or artifact
export over loading every row into memory.

## Rate Limits And Concurrency

Clay applies its Public API rate limit per workspace, not per Verbiflow job.
Bridge requests and durable workers therefore share one Postgres pacing state
keyed by a non-secret fingerprint of the backend Clay key.

The provider contract is Clay's official
[rate-limit guide](https://developers.clay.com/public-api/rate-limits). The
stateful page loop follows Clay's official
[pagination guide](https://developers.clay.com/public-api/pagination).

Every physical request claims a short atomic slot. No database connection or
lock is held while sleeping or during the Clay HTTP call. When Clay provides
`X-RateLimit-Remaining` and `X-RateLimit-Reset`, requests are spread across the
remaining window. A received 429 extends the shared cooldown from
`Retry-After`; bounded exponential backoff and jitter remain the fallback.

Durable progress may enter `rate_limited` with the wait reason and duration,
then return to `running`. Stateful iterator pages stay sequential. Do not add
client-side page concurrency or start many duplicate Clay jobs to bypass the
workspace limit.

Only a conclusive 429 is retried. Iterator timeouts, disconnects, ambiguous
server failures, and invalid response bodies are not replayed because the
stateful provider iterator may already have advanced.

## Usage And Cancellation

Clay uses `free_observed` accounting. This is post-execution observation, not a
credit reservation or hard-limit admission check. Immediate calls record the
actual returned rows. Durable jobs capture the capability pricing snapshot at
enqueue and monotonically settle the canonical artifact row count on success,
partial failure, or cancellation.

Artifact state, observed usage, and terminal progress commit in one
first-writer-wins database transaction. Completion, failure, and cancellation
cannot overwrite one another after a terminal outcome wins. A repeated
settlement cannot double-count or reduce the observed row count. If completion
wins while cancellation is arriving, the completed result remains canonical.
If a durable job must be stopped, use `vf.jobs.cancel(...)`; do not abandon a
live job and assume provider work or accounting stopped.

## Verification

- Confirm the field catalog source type matches the intended outcome.
- Print `search_id`, requested limit, returned count, and `has_more` for an
  immediate result.
- For durable work, preserve `job_id` and `artifact_id`, wait with
  `require_success=True`, and inspect the full artifact row count.
- Sample rows against the outcome's inclusion and exclusion rules. Provider
  membership alone is not qualification proof.
- Confirm people requests route from `contact-search.md`, company requests
  route from `company-search.md`, and job openings route from `job-search.md`.

## Troubleshooting

| Symptom | Meaning | Action |
| --- | --- | --- |
| `clay_not_configured` | The server has no usable `CLAY_API_KEY`. | Configure the backend secret. Hosted agents must not request or store the key themselves. |
| `clay_invalid_request` | Clay rejected the filter object. | Re-read `filter_fields` for the exact source and rebuild only from published names and shapes. |
| `clay_rate_limited` or `rate_limited` progress | The shared Clay workspace window is exhausted or the wait exceeds the bounded caller deadline. | Preserve the job or invocation ID, wait for the shared reset, and retry the logical operation once. Do not fan out more jobs. |
| `clay_ambiguous_iterator` | A stateful POST may have advanced before a timeout, disconnect, or server failure. | Do not replay the same page automatically. Preserve the partial artifact and report the ambiguous provider boundary. |
| `has_more=True` after an immediate search | More Clay rows exist beyond the one returned page. | Start a durable search with an explicit maximum. |
| A `PendingJob` appears to have no rows | The start response contains only a preview or no preview. | Use `vf.jobs.result(pending).wait(...)`, then `rows` or `iter_rows`. |

## Related References

| Next Decision | Read |
| --- | --- |
| Choose whether the output entity is a company | `company-search.md` |
| Choose whether the output entity is a person | `contact-search.md` |
| Find job openings or hiring evidence | `job-search.md` |
| Qualify, transform, export, or stage returned rows | `row_operations.md`, `artifacts_jobs.md`, `artifacts.md` |
| Compare another structured company provider | `crunchbase.md` |
