# Pull Results Into Reporting

> Read the scoreboard Revnu reads, plus workflows and run history, into your own dashboards, and post a weekly Slack summary of sends, replies, reply rate and who replied. Or ask Revnu to post it for you, with no code.

Canonical: https://revnu.com/docs/pull-results-into-reporting
Last updated: 2026-10-18

`GET /api/v1/results` returns the scoreboard Revnu wakes up with: per-channel sections for email, outbound, LinkedIn, ads, SEO, AI answers and social, over a window ending now. A token with `results:read` reads it; add `leads:read` for lead-level detail. Neither can cause any work.

Don't write code? Skip to [No code: ask Revnu](#no-code-ask-revnu). Revnu can post the same weekly numbers to Slack for you.

## No code: ask Revnu

If you want the numbers in a Slack channel every week and nothing else, you don't need the API. Ask Revnu in chat:

> Every Monday at 9am, post last week's sends, replies, reply rate and who replied to #sales.

Revnu sets it up as a workflow, confirms the schedule back to you, and posts at that time every week whether or not anything moved. It can post in your Revnu channel or another Slack channel you name. You'll find it under **Dashboard > Workflows** and change it by asking in chat. More in [Recurring Work With Workflows](https://revnu.com/docs/recurring-work.md), [Daily Updates and the Monday Report](https://revnu.com/docs/reports.md) and, for code, [Create a workflow](https://revnu.com/docs/workflow-create.md).

Build the API version below when you want the numbers next to your other metrics, in a warehouse, or in a format you control.

## Available or not, never a fake zero

Each section is either `{ "available": true, "data": {…} }` or `{ "available": false, "reason": "…" }`. Zero and unknown are different numbers and the API never turns one into the other. Render an unavailable section as "not connected" or "not tracked", not as 0. A section can also carry `caveats`, such as a count that hit a read cap.

## Windows

`since` is epoch milliseconds and is clamped to between 31 days and one hour ago; the default is 24 hours. Every section also has an equal-length `baseline` window before it, so `email.data.sentBaseline` is last period's sends. Pull once an hour or once a day into your warehouse and you have everything Revnu sees.

## A Monday Slack post of sends, replies and reply rate

The email section has what a weekly report needs:

| Number | Where |
|---|---|
| Emails sent | `email.data.sent` (last period: `email.data.sentBaseline`) |
| People who replied | `email.data.replies.data.replies` |
| Bounces | `email.data.replies.data.bounces` |
| Who replied | `email.data.replies.data.repliedContacts`: the newest five, each `{ who, company, agoDays, intent? }` |
| Drafts waiting for you | `email.data.draftsAwaiting` |

Reply rate is yours to compute: replies over sends. Replies are counted by when they arrived, so a reply in this window can answer an email sent last week. When `replies.available` is `false`, the reply count is unknown, not zero, so report it as unknown.

```ts
// Run every Monday at 09:00, for example from a cron job or a scheduled function.
const WEEK = 7 * 24 * 60 * 60 * 1000;
const res = await fetch(`https://revnu.com/api/v1/results?since=${Date.now() - WEEK}`, {
  headers: { Authorization: `Bearer ${process.env.REVNU_API_TOKEN}` },
});
const { results } = await res.json();

let text: string;
if (!results.email.available) {
  text = `Email: not available (${results.email.reason})`;
} else {
  const { sent } = results.email.data;
  const r = results.email.data.replies;
  const replies = r.available ? r.data.replies : null;
  const rate = replies !== null && sent > 0 ? `${((replies / sent) * 100).toFixed(1)}%` : "n/a";
  const who = r.available
    ? (r.data.repliedContacts ?? []).map((c) => `${c.who} (${c.company})`).join(", ")
    : "";
  text = `Last 7 days: ${sent} sent, ${replies ?? "unknown"} replied, reply rate ${rate}.${who ? `\nReplied: ${who}` : ""}`;
}

await fetch(process.env.SLACK_WEBHOOK_URL!, {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ text }),
});
```

`SLACK_WEBHOOK_URL` is an incoming webhook you create in your own Slack workspace.

With daily updates on, Revnu's own [Monday check-in](https://revnu.com/docs/reports.md) is already a weekly roll-up, sent wherever you talk to Revnu. Build this when you want the numbers in a channel of your choosing or next to your other metrics.

## Everyone who replied, not just the newest five

`repliedContacts` stops at five. For the full list, page through [`GET /leads?status=replied`](https://revnu.com/docs/leads.md) with a `leads:read` token. Leads come newest first by when Revnu created them, not by when they replied, so a reply on a lead from weeks ago can sit several pages down. Page to the end every time and keep the ids you've already reported.

```ts
const seen = new Set<string>(await loadReportedIds()); // ids you reported last time
const fresh: Array<{ company: string; who: string | null }> = [];
let cursor: string | null = null;
do {
  const url = new URL("https://revnu.com/api/v1/leads");
  url.searchParams.set("status", "replied");
  url.searchParams.set("limit", "100");
  if (cursor) url.searchParams.set("cursor", cursor);
  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${process.env.REVNU_API_TOKEN}` },
  });
  const page = await res.json();
  for (const lead of page.leads) {
    if (!seen.has(lead.id)) fresh.push({ company: lead.companyName, who: lead.contact?.name ?? null });
  }
  cursor = page.nextCursor;
} while (cursor);
```

Each lead carries its primary `contact` and a `replyKind`. The reply text itself isn't in the API; read it in **Inbox**.

## Workflows and runs

`GET /api/v1/workflows` lists recurring workflows with their schedule, next run and steps. `GET /api/v1/workflows/{id}/runs` is the run history with per-step outcomes and `found: { leads, contacts, people }`, newest first, so a failed step shows up in your monitoring the same hour it happens. List one run's leads with [`GET /leads?workflowRunId=`](https://revnu.com/docs/leads.md).

## Frequently asked questions

### Is this the same data as the dashboard?

Yes. Both read the same results window, so the number in your report matches the number Revnu reports to you.

### How do I calculate reply rate from the API?

Divide `email.data.replies.data.replies` by `email.data.sent` for the same window. Check both sections are available first; an unavailable section is unknown, not zero.

### Can I get more than 31 days?

Each call covers up to 31 days. Pull daily and keep your own history for longer trends.

### Can Revnu post a weekly report to Slack without code?

Yes. Ask in chat: > Every Monday at 9am, post last week's sends, replies, reply rate and who replied to #sales. Revnu sets it up as a workflow. See [Recurring Work With Workflows](https://revnu.com/docs/recurring-work.md).

### What does replies.available false mean?

That Revnu couldn't count replies for that window, so the number is unknown. Report it as unknown, not 0.

### Which scopes does a reporting token need?

`results:read`, plus `leads:read` for lead and reply detail. Neither can send, publish, run or spend.
