Reporting API
Account Metrics
Six metrics for one account over a date range, in total and by lead source and by channel. leads and appointments are the account's own OnePath Dashboard counts.
curl -H "Authorization: Bearer YOUR_KEY" \
"https://app.onepath.ai/api/reporting/v1/accounts/42/metrics?from=2026-08-01&to=2026-08-31"
{
"account": { "id": 42, "name": "Wahoo Plumbing", "time_zone": "America/Chicago" },
"period": { "from": "2026-08-01", "to": "2026-08-31" },
"metrics": {
"leads": 120,
"appointments": 48,
"booking_rate": 40.0,
"won_revenue": 96000.0,
"average_ticket": 2000.0,
"pipeline_revenue": 150000.0
},
"by_source": [
{
"source": { "id": 913, "handle": "yelp", "name": "Yelp", "kind": "platform", "category": "marketplace", "platform": "yelp", "archived": false },
"metrics": { "leads": 40, "appointments": 18, "booking_rate": 45.0, "won_revenue": 36000.0, "average_ticket": 2000.0, "pipeline_revenue": 52000.0 }
}
],
"by_channel": [
{
"channel": { "type": "web_scheduler", "name": "Web Scheduler" },
"metrics": { "leads": 75, "appointments": 30, "booking_rate": 40.0, "won_revenue": 60000.0, "average_ticket": 2000.0, "pipeline_revenue": 94000.0 }
}
],
"generated_at": "2026-09-03T10:00:00Z"
}
Take the id from /accounts. Numbers are numbers: rates are percentages as floats, money is US dollars as decimals. Nothing comes back as a formatted string. by_source and by_channel carry the same six metrics per row; see Breakdowns.
Dates
from and to are both required, in YYYY-MM-DD, and inclusive. A range may cover at most 366 days. Anything else is a 422 invalid_date_range.
Days start and end in the account's own time zone (the time_zone in the response), not yours and not UTC. A booking at 23:30 local time on to is included; one at 00:30 the next local morning is not. For a monthly report, ask for the first to the last day of the month and you get exactly that month as the contractor experienced it.
What each number means
| Key | Counts | Notes |
|---|---|---|
| leads | job requests | each service a customer asked for, so a conversation asking for two services counts twice; the Dashboard's Job requests |
| appointments | job requests that were won | won means the job was booked; the Dashboard's Booked jobs |
| booking_rate | appointments ÷ leads × 100 | one decimal; 0.0 when there are no leads |
| won_revenue | the value of won opportunities | US dollars |
| average_ticket | won_revenue ÷ appointments | two decimals; 0.0 when there are no appointments |
| pipeline_revenue | the value of opportunities that are open, being followed up, handed to a person, or won | an estimate; the Dashboard doesn't show it |
leads, appointments and booking_rate match what the account owner sees on their Dashboard for the same days. The three revenue metrics are opportunity estimates the Dashboard doesn't show. Its Realized Revenue comes from the account's field service software, so it won't match them.
Reporting on several accounts
There is no rollup endpoint. Call /accounts once, then this endpoint once per account. Add the totals yourself, and recompute the two rates from your sums rather than averaging them:
const key = "YOUR_KEY";
const base = "https://app.onepath.ai/api/reporting/v1";
const range = "from=2026-08-01&to=2026-08-31";
const headers = { Authorization: `Bearer ${key}` };
const { accounts } = await fetch(`${base}/accounts`, { headers }).then((r) => r.json());
const rows = await Promise.all(
accounts.map((a) => fetch(`${base}/accounts/${a.id}/metrics?${range}`, { headers }).then((r) => r.json()))
);
const leads = rows.reduce((sum, r) => sum + r.metrics.leads, 0);
const appointments = rows.reduce((sum, r) => sum + r.metrics.appointments, 0);
const bookingRate = leads ? Math.round((appointments / leads) * 1000) / 10 : 0;
Results are cached for six hours per account and range, so a nightly job that walks every account is well within the rate limits.