Conversation insights
Developer·7 minutes to read
What it does
Reads your organisation's conversation insights as counts: how many bot conversations it had in a date range, by type of work and by level of automation. You can also ask for the same counts for some members or groups, to compare them. The numbers are the ones Organisation → Usage → Insights shows for the same range. The answer holds ids and counts only, never a conversation, a title, a message, an attachment or anyone's name.
Authentication
You need a key of scope admin, made by a person who is still an owner or admin of a team organisation. These all get 401 unauthenticated:
- a
readorwritekey, or a key whose person is no longer an admin; - any key while an admin has turned insights off in Organisation → Usage → Insights;
- a key of a personal organisation, which has no insights.
Request
GET /usage/insights
<!-- generated:begin GET /usage/insights --> <!-- generated:end GET /usage/insights -->from and to are UTC days (YYYY-MM-DD), both included. Leave them out for the month to date. A range may be at most 366 days long.
members is a comma-separated list of up to 50 members, each given by id or by email address in any letter case; you can mix the two. Each must be a member of your organisation. A member named by email address is answered under their id, exactly as if you had named the id, and a member you name twice is answered once. groups is a comma-separated list of up to 50 group ids, each a live group of your organisation. A group is counted by who is in it now.
To read usage itself — per day, or per metered call filtered by member, bot, routine or way of paying — see Usage by day and by call below.
curl -sS "$BOTSEON_API_URL/api/v1/usage/insights?from=2026-10-01&to=2026-10-17" \
-H "authorization: Bearer $BOTSEON_API_KEY" \
-H "accept: application/json"
Response
The first entry is the whole organisation. Then comes one entry per member you named, then one per group, in the order you named them.
{
"from": "2026-10-01",
"to": "2026-10-17",
"data": [
{
"subject": { "type": "organisation", "id": "6f1e2d3c-4b5a-4968-8776-655443322110" },
"total": 42,
"byWorkType": {
"inbox": 12, "calendar": 6, "sales": 4, "support": 5, "marketing": 1, "finance": 3,
"people": 0, "engineering": 2, "research": 4, "projects": 1, "general": 4
},
"byAutomation": { "answered": 20, "drafted": 9, "completed": 13 }
}
]
}
| Key | Level of automation |
|---|---|
answered | Not automated: the bot answered, and a person did the work. |
drafted | Semi-automated: the bot prepared the work, and a person reviewed it. |
completed | Fully automated: the bot did the work. |
Errors
| Status | Code | When | What to do |
|---|---|---|---|
| 400 | invalid_request | A day that is not YYYY-MM-DD, a start after its end, a range over 366 days, or a member (by id or email address) or group that is not your organisation's. details names the field. An address nobody holds and one that belongs to another organisation get the same answer. | Fix the field details names. |
| 401 | unauthenticated | The key is not an admin's admin key, insights are off, or the organisation is personal. | Use an admin key from a current admin, or turn insights on in the app. |
| 429 | rate_limited | More than 20 requests in a minute for your organisation. | Wait for Retry-After. |
Rate limits
This route allows 20 requests a minute per organisation. The 21st in the same minute gets 429 with Retry-After: 60. This limit is the route's own, so your other API calls carry on. The shared limits still apply too: 300 requests a minute per key and 1 000 per organisation. See Envelopes, errors, idempotency and paging.
Usage by day and by call
GET /usage/daily and GET /usage/events read your organisation's usage for a reporting or cost tool. They take the same admin key as this page, answer ids and numbers only, and are listed with the other administrator routes in Audit log, usage, spend limits and groups. The shared rate limits apply.
GET /usage/daily
One row per member per UTC day with runs, calls, credits, spendMicroCents and costMicroCents. from and to are required UTC days (YYYY-MM-DD), both included; the range may be at most 366 days long. limit is 1 to 200 rows a page (50 when you leave it out), and cursor is the nextCursor of the page before.
GET /usage/events
One row per metered model call, oldest first. It takes these query parameters:
| Parameter | What it means |
|---|---|
from, to | A day (YYYY-MM-DD) or an instant (ISO 8601 with an offset). to is now when you leave it out, and from is 366 days before to. from must be before to: for one day, give to as an instant, such as from=2026-10-01&to=2026-10-01T23:59:59Z. |
members | Members, by id or by email address in any letter case; you can mix the two. A member's calls are the calls of the runs they started. |
bots | Bot ids. A deleted bot's calls can still be read. |
routines | Routine ids: the calls of the runs those routines started. |
keys | API key ids, as GET /keys lists them: the calls of the runs those keys started with POST /runs, POST /conversations/{id}/messages or a run.create in POST /batch. A revoked key's calls can still be read. Runs started before keys were recorded (October 2026) match no key. |
serviceAccounts | Service-account ids, as GET /service-accounts lists them: the calls of the runs any of those accounts' keys started with POST /runs, POST /conversations/{id}/messages or a run.create in POST /batch. An archived account's calls can still be read. A person's own key is not a service account: narrow to it with members or keys. |
credentialKind | How the call was paid for: platform (your plan and credit), byok (your own provider key) or local (a model on your own machine). |
limit | Rows per page, 1 to 1 000. 50 when you leave it out. |
cursor | The nextCursor of the page before; send it with the same filters. |
Each filter is a comma-separated list of at most 100 values. A call matches a filter when it matches any of its values, and it is on the page only when it matches every filter you give.
curl -sS "$BOTSEON_API_URL/api/v1/usage/events?from=2026-10-01&credentialKind=platform&limit=1000" \
-H "authorization: Bearer $BOTSEON_API_KEY" \
-H "accept: application/json"
The answer is a page: data and nextCursor, null on the last page. Each row has id, occurredAt, userId, botId, runId, model, credentialKind, inputTokens, outputTokens, credits, costMicroCents (the list price), spendMicroCents (what paid for the call; one credit is 1 000 000 micro-cents) and serviceAccountId (the service account whose key started the run, or null when a person, the screen or a routine started it). Add up spendMicroCents over a month filtered to one member or one bot and you get that member's or bot's figure on Organisation → Usage. Add up costMicroCents over a calendar month (UTC) filtered to one service account and you get the spend that account's monthly spend limit is checked against, the monthSpendMicroCents of GET /service-accounts/{id}.
A value the route cannot read is 400 invalid_request naming the parameter: a value that is not an id, an email address or one of the listed kinds; a member, bot, routine, key or service account that is not your organisation's (the path names the position too: members.1 is the second value of members); a limit over 1 000; a parameter the route does not take; a start not before its end or a window over 366 days; or a cursor this list did not issue. You never get an empty page for a value that names nothing. An address nobody holds and one that belongs to another organisation get the same answer.
Last verified against build b2955f0.