Skip to Content
Browse the Docs▾

List leads

GET/api/v1/leads

Leads newest first with status, score, primary contact and your own columns, paged by an opaque cursor. Draft bodies are only on GET /leads/{id}.

curl -H "Authorization: Bearer $REVNU_API_TOKEN" "https://revnu.com/api/v1/leads?status=replied&limit=50"

Query

Param Meaning
status Filter by lead status: qualified, drafted, sent, partially_sent, replied, declined or bounced. Anything else is a 400.
limit 1 to 100. Default 50.
cursor The nextCursor from the previous page.
workflowRunId Only the leads this workflow run created or returned people on (a run id from GET /workflows/{id}/runs). Each lead then also has runContactIds, the contacts this run created, runPeople, the people this run returned on the company's research list without saving them as contacts ({ name, title, email, linkedinUrl, profileUrl, photoUrl, role, alreadyKnown }; role is economic for the buyer, champion, technical, user or null, and alreadyKnown is true when the company had the person before this run), and runCreatedLead, true when the run created the lead itself.

Response

{ "leads": [lead, …], "nextCursor": "…" | null }. A page is bounded in rows and in bytes, and holds at most 30 leads that are in a LinkedIn sequence, so pass nextCursor back until it is null.

A lead is { id, domain, companyName, status, replyKind, declineReason, score, scoreReasons, vertical, archetype, tags, fields, contact, linkedin, createdAt, workflowRunId }, plus runContactIds, runPeople and runCreatedLead when you pass workflowRunId. workflowRunId is the workflow run that created the lead, or null. fields is your account's configured lead columns, exactly the dashboard table, as [{ key, value }] entries with each value cut at 1,000 characters, for example [{ "key": "account_case", "value": "…" }, { "key": "slack_thread", "value": "1790064680.268899" }]. The list never carries the draft or the research behind a value; read GET /leads/{id} for those.

Code Meaning
404 workflowRunId is not one of your workflow runs.

LinkedIn sequence progress

status follows the email pipeline and the first LinkedIn message: a connection request on its own leaves a lead qualified, a LinkedIn message moves it to sent, and a reply to one moves it to replied. Where a lead stands in a sequence with LinkedIn steps is in linkedin, the same state as the Status column in the dashboard's Leads table. It is null for a lead outside such a sequence.

linkedin is { sequence, status, reason, invitedAt, acceptedAt, lastMessageAt, seenAt, repliedAt, nextStep }, for the lead's primary contact, with times in epoch milliseconds or null:

  • status is not_started, cannot_start (reason says why, such as no LinkedIn profile), checking (a send is being confirmed with LinkedIn), invite_sent, connected, messaged, seen, replied, finished or stopped.

  • acceptedAt is when Revnu first saw the request accepted. LinkedIn does not give the exact time.

  • seenAt is when they read a message, from accounts that report reads, so null does not mean unread.

  • repliedAt is when they first replied on LinkedIn.

  • nextStep is { kind, dueAt } for the step still to come, where kind is linkedin_invite, linkedin_message or email, and dueAt is null while it waits for an accept. It is null when nothing is left.

This comes from Revnu's own send records, so reading it spends none of your LinkedIn account's daily profile lookups. To keep a CRM in step with a LinkedIn sequence, poll GET /leads and compare linkedin with what you stored, rather than calling GET /linkedin/person for each person.

The Why Now field

The Why Now is the recent reason a company is worth contacting, such as a funding round or a job post. When your account's lead table has a Why Now column, it comes back in fields with the key why_now. The reasoning and source links behind it are on GET /leads/{id}, in the research entry with the same key. What it means and how to correct a wrong one is in What's on a Lead.

There is no route that creates leads

The API reads leads and sets their labels. It can't create or import them. To hand Revnu a list from your own system:

  1. Block first. Put your customers and anyone you are already talking to on the block list, up to 500 entries a call. Revnu checks it before researching or contacting anyone, so they can't end up in outreach.

  2. Send the list. Send it as a message. One message holds up to 4,000 characters, so split a long CSV into chunks and send each with queue: true and its own clientRequestId. Or POST it as the JSON body of an inbound webhook, up to 64,000 bytes.

  3. Read the results. Revnu researches each company and saves the ones that fit as leads. Read them back here, or filter by workflowRunId when a workflow did the import.

The API takes text only. It can't attach a CSV or spreadsheet file. For a big file, uploading it in the dashboard chat is easier: Revnu reads the whole file in one go.

Send a big list in chunks

How many rows fit depends on how wide they are. A row like acme.com,Acme,Dana Lee,dana@acme.io is about 40 characters, so one 4,000-character message holds roughly 80 to 90 of those once you allow for a short instruction at the top. A 2,000-row export is about 25 messages. Wider rows mean fewer per message.

const rows: string[] = csv.trim().split("\n").slice(1); // drop the header row
const intro = "Research these companies and save the ones that fit as leads. Columns: domain,company,contact,email.\n";
const chunks: string[][] = [[]];
for (const row of rows) {
  const current = chunks[chunks.length - 1];
  if ((intro + [...current, row].join("\n")).length > 3900) chunks.push([row]);
  else current.push(row);
}
for (const [i, chunk] of chunks.entries()) {
  if (chunk.length === 0) continue;
  await fetch("https://revnu.com/api/v1/agent/messages", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.REVNU_API_TOKEN}`, "content-type": "application/json" },
    body: JSON.stringify({ text: intro + chunk.join("\n"), queue: true, clientRequestId: `import-2026-10-15-${i}` }),
  });
}

Each queued message runs after the one before, and a message that waits more than six hours behind earlier work fails. Send a long list across the day, or as fewer, larger webhook bodies of up to 64,000 bytes. The agent-message limit is 30 calls a minute per token; see Rate Limits.

Your own columns

Columns from your source system, such as an owner or a CRM id, can't be written through the API. PATCH /leads/{id} sets only vertical, archetype and tags. To fill one of your account's lead columns, ask Revnu in chat, for example: "Put the CRM id from my list in the CRM ID column."

Frequently Asked Questions

Can I create leads through the API?

Through Revnu. Block your existing customers first, then send the list to Revnu as messages of up to 4,000 characters each, or as the JSON body of an inbound webhook of up to 64,000 bytes. Revnu researches each company and saves the ones that fit as leads. In the dashboard, upload a CSV or spreadsheet in chat instead. The API itself reads leads and sets their labels.

Can I upload a CSV file through the API?

Paste the rows into messages in chunks (see Send a big list in chunks), or upload the file in the dashboard chat, which is easier for a big list. The API takes text rather than files.

Can I set my own lead columns from code?

Ask Revnu in chat to fill a custom column. From code, the API sets vertical, archetype and tags.

How do I see new replies?

Poll GET /leads?status=replied. Leads come newest first by when they were created, not by when they replied, so a reply on an old lead can be several pages down. Page through to the end and keep the ids you have already seen. Polling is how replies reach your code. A worked recipe is in Inbound Webhooks.

How do I see who accepted a LinkedIn invite?

Read linkedin.acceptedAt on each lead. It is set once Revnu sees the connection request accepted, and linkedin.status moves to connected. See LinkedIn sequence progress.

Which scope does this need?

leads:read. A token without it gets 403 with requiredScope in the body. GET /me shows a token's scopes.

Are responses cached?

No. Every response is Cache-Control: no-store, so what you read is current.

Last updated

Agents: this page is also markdown at /docs/leads.md, answers Accept: text/markdown, and is listed in /docs/llms.txt.