Cookbook
View as Markdown

Push leads from Clay into a campaign

A Clay HTTP API column that adds each row to a campaign as it's enriched, carrying the enrichment columns as custom fields, with no code between.

Last updated

Clay is where many teams build and enrich a list; Victoria AI is where it gets worked. The join is one column. Clay's HTTP API enrichment calls POST /v1/leads for each row, mapping the table's columns into the lead and the enrichment columns into custom_fields, and writes the lead's id back into the row. Rows flow into the campaign as they're enriched, a scoring column can gate them, and nothing runs outside Clay.

Before you start

  • An API key with leads:write and leads:read. See Create an API key.
  • The campaign's id, from its URL in the app or from GET /v1/campaigns.
  • A Clay table with at least a first name, a last name, and a work email or a LinkedIn profile URL per row, plus whatever enrichment columns you want in the messages.

1. Save the key as an HTTP API account

In Clay, open Settings → Connections (or the account picker when you add the column) and create an HTTP API connection of the Headers kind with one header:

HeaderValue
AuthorizationBearer vk_…

Clay stores the header encrypted at the workspace level, so the key never sits in a column or a formula, and every HTTP API column in the workspace can reuse it.

2. Add a formula column for a stable request key

Add a Formula column named Request key that produces the campaign id, a colon, and the row's email in lowercase (for a LinkedIn-only row, the LinkedIn URL). Clay's formula editor writes the formula from a plain description such as "the text 550e8400-e29b-41d4-a716-446655440000: followed by the Email column in lowercase".

The same row then always produces the same key, so re-running the column, which Clay does when an input changes, can't enrol the person twice: it's the request's Idempotency-Key, and the API accepts any unique string of up to 255 characters there. See Idempotency.

3. Add the HTTP API column

Add an enrichment, choose HTTP API, and switch to the Configure tab:

FieldValue
MethodPOST
Endpointhttps://api.versionseven.ai/v1/leads
Authenticationthe HTTP API account from step 1
HeadersContent-Type → application/json; Idempotency-Key → the Request key column
Bodythe JSON below, with each /Column inserted from the table
{
  "campaign_id": "550e8400-e29b-41d4-a716-446655440000",
  "lead": {
    "first_name": "/First Name",
    "last_name": "/Last Name",
    "email": "/Work Email",
    "linkedin_url": "/LinkedIn Profile",
    "company": "/Company Name",
    "title": "/Job Title",
    "company_website": "/Company Domain",
    "industry": "/Industry",
    "employees": "/Employee Count",
    "custom_fields": {
      "funding_stage": "/Funding Stage",
      "tech_stack": "/Tech Stack Summary",
      "recent_hire": "/Recent Sales Hire"
    }
  }
}

Each /Column is a reference inserted with Clay's / menu inside the body field; type the column's name to find it. Clay substitutes the row's value when the column runs, and an empty cell becomes an empty value, which the API ignores for optional fields. employees has to be a number: insert an integer column there, or leave it out.

Under Run settings, set the column to run only if the row has a work email or a LinkedIn profile, and, if you score rows, only if the score column passes your bar. That's the gate: Clay enriches everyone, Victoria AI gets the ones worth contacting.

4. Map the response into columns

Run the column on one row and open its output. Clay shows the JSON the API answered and lets you pick values into new columns with a JSON path:

New columnPathWhat it holds
Victoria lead id$.lead_idThe lead's id in Victoria AI (on 201 and 200).
Victoria created$.lead_createdtrue for a new lead, false when an existing lead was enrolled.
Victoria error$.errorThe error code when the row was refused (below); empty on success.

The first run is also the test: the lead appears in the campaign in the app, and GET /v1/leads with campaign_id and search set to the row's email finds it with its custom_fields:

curl "https://api.versionseven.ai/v1/leads?campaign_id=550e8400-e29b-41d4-a716-446655440000&search=sarah.johnson%40acmecorp.com" \
  -H "Authorization: Bearer $VICTORIA_API_KEY"

Then run the column for the table.

What each row's result means

Status and errorMeaning
201A new lead was created and enrolled.
200The person was already a lead in your workspace (same email, or same LinkedIn URL) and was enrolled. Their stored name and company are kept; the custom fields travel with the enrolment.
409 LEAD_ALREADY_IN_CAMPAIGNAlready there. Nothing changed.
409 LEAD_SUPPRESSEDThe person or their domain is on your Do Not Contact list. Nothing was created.
400 VALIDATION_ERRORA required field is missing or malformed; the response's details.errors names each. A lead needs a first name, a last name, and an email or LinkedIn URL.
403 TRIAL_LEAD_CAP_REACHEDThe workspace is on a free trial and has used its lead quota.
429 RATE_LIMITEDMore than 100 requests a minute to the endpoint. Clay runs enrichment columns in parallel, so a large table can hit this; Clay retries, and the idempotency key makes a retry safe. Running the column in batches of a few hundred rows keeps it quiet.

Clay treats a 4xx as a failed enrichment for that row and leaves the other rows alone. The Victoria error column shows which rows to look at.

Using the enrichment in messages

Every key under custom_fields becomes a variable in the campaign's messages: {funding_stage}, {tech_stack}, {recent_hire}. A step that reads Saw you brought on {recent_hire}; congratulations works for every row with that cell filled, and the campaign's readiness check counts the rows that lack it before activation. Clay's AI columns are good at writing one clean sentence per row for exactly this; keep each value short and free of line breaks.

Lead fields (first_name, company, title) are the lead's own and are reused across campaigns; custom_fields sent with a campaign_id belong to that enrolment, so the same person can carry different fields in different campaigns.

What to expect

  • Clay reruns a column when its inputs change. With the idempotency key derived from the campaign and the email, a rerun within 24 hours returns the first answer again; after that, the lead is already in the campaign and the row shows LEAD_ALREADY_IN_CAMPAIGN. Neither enrols twice.
  • Matching is by email, then LinkedIn URL. Emails are compared without regard to case; LinkedIn URLs exactly, so send them in one form (Clay's LinkedIn columns are consistent).
  • Clay's own export action to sequencers doesn't list Victoria AI; the HTTP API column is the general mechanism and does the same thing.
  • A campaign needs to be active to send. Rows added to a draft wait in the backlog until it's activated.

Next steps