curl --request POST \
--url https://pria.praxislxp.com/api/admin/histories/charts/tools \
--header 'Content-Type: application/json' \
--header 'x-access-token: <api-key>' \
--data '
{
"view": "tools",
"lane": "all",
"model": "<string>",
"splitByTier": false,
"institution": "<string>",
"account": "<string>",
"daterange": [
"2023-12-25"
],
"top": 200
}
'import requests
url = "https://pria.praxislxp.com/api/admin/histories/charts/tools"
payload = {
"view": "tools",
"lane": "all",
"model": "<string>",
"splitByTier": False,
"institution": "<string>",
"account": "<string>",
"daterange": ["2023-12-25"],
"top": 200
}
headers = {
"x-access-token": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-access-token': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
view: 'tools',
lane: 'all',
model: '<string>',
splitByTier: false,
institution: '<string>',
account: '<string>',
daterange: ['2023-12-25'],
top: 200
})
};
fetch('https://pria.praxislxp.com/api/admin/histories/charts/tools', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://pria.praxislxp.com/api/admin/histories/charts/tools",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'view' => 'tools',
'lane' => 'all',
'model' => '<string>',
'splitByTier' => false,
'institution' => '<string>',
'account' => '<string>',
'daterange' => [
'2023-12-25'
],
'top' => 200
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-access-token: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://pria.praxislxp.com/api/admin/histories/charts/tools"
payload := strings.NewReader("{\n \"view\": \"tools\",\n \"lane\": \"all\",\n \"model\": \"<string>\",\n \"splitByTier\": false,\n \"institution\": \"<string>\",\n \"account\": \"<string>\",\n \"daterange\": [\n \"2023-12-25\"\n ],\n \"top\": 200\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-access-token", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://pria.praxislxp.com/api/admin/histories/charts/tools")
.header("x-access-token", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"view\": \"tools\",\n \"lane\": \"all\",\n \"model\": \"<string>\",\n \"splitByTier\": false,\n \"institution\": \"<string>\",\n \"account\": \"<string>\",\n \"daterange\": [\n \"2023-12-25\"\n ],\n \"top\": 200\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://pria.praxislxp.com/api/admin/histories/charts/tools")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-access-token"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"view\": \"tools\",\n \"lane\": \"all\",\n \"model\": \"<string>\",\n \"splitByTier\": false,\n \"institution\": \"<string>\",\n \"account\": \"<string>\",\n \"daterange\": [\n \"2023-12-25\"\n ],\n \"top\": 200\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"view": "<string>",
"lane": "<string>",
"model": "<string>",
"tier": "<string>",
"modeSource": "<string>",
"splitByTier": true,
"window": {
"start": "2023-11-07T05:31:56Z",
"end": "2023-11-07T05:31:56Z",
"days": 123
},
"turnMarkerSince": "2023-12-25",
"truncated": true,
"warnings": [
"<string>"
],
"rows": [
{}
]
}
}Get tool usage and prompt cache census
Aggregate-only census of tool usage and prompt-cache behaviour over conversation history. The response carries tool names, model names, lane names, bucket labels and counts. It never carries a user id, a Digital Twin id, a conversation id, a question or an answer. Distinct users and twins are returned as counts. One request runs one bounded view. The window is at most 31 days (a wider window is rejected, never clamped) and defaults to the last 7 days. Results are cached. An administrator of one Digital Twin only ever sees that twin. Only real turns are counted - rows stamped by the turn save path (every saved turn since 2026-09-10) plus avatar embed sessions. Uploads, sign-in events and other non-turn history rows are left out. A window that starts before 2026-09-10 is answered with a warning, because older turns carry no marker. Lanes - qanda is a single-shot text turn, convo is one Convo Mode (realtime) or avatar embed session per row, agentic is a turn that ran a conductor, background workers or a plan execution (its tools are the conductor’s own), and workers (tools view only) counts the tools that the background workers of a turn ran. A tool that appears only under workers is never called by a single-shot turn. The qanda lane means “no background worker and not Convo”. It holds classic questions and also the Basic mode of the new interface, where one model chats and runs tools itself. The tier and modeSource filters tell them apart - every turn saved since 2026-09-12 records the mode it ran in (auto, basic, advanced, expert or mythical) and where that mode came from (request - the send named a mode, from either interface, membership - the send named none and the server applied the member’s saved mode, default - nothing named a mode). The tier is the mode that was DECLARED - a caller that asks for the single-model lane without naming a tier is recorded as auto. modeSource does not tell the classic interface from the new one. With splitByTier the tools and turns views return one row per tier, and a row that carries no mode at all (an avatar embed session) is labelled none. Avatar embed sessions are therefore left out by any tier or modeSource filter. Labels are vetted - a tool label is a platform tool name, mcp_bridged or other, and a model label that does not look like a model name is returned as other. Tools reached through an MCP connector are counted together as mcp_bridged, because their stored name carries the connector label chosen by the customer. The twins count treats the personal scope (no twin) as one twin. Approximations - sameSlotTurns counts turns that share a clock-aligned five-minute slot with an earlier turn of the same conversation, which is a lower bound on follow-ups that land inside a five-minute prompt cache. A conversation is a user, a twin and a course_id, and a row without course_id counts as its own conversation.
curl --request POST \
--url https://pria.praxislxp.com/api/admin/histories/charts/tools \
--header 'Content-Type: application/json' \
--header 'x-access-token: <api-key>' \
--data '
{
"view": "tools",
"lane": "all",
"model": "<string>",
"splitByTier": false,
"institution": "<string>",
"account": "<string>",
"daterange": [
"2023-12-25"
],
"top": 200
}
'import requests
url = "https://pria.praxislxp.com/api/admin/histories/charts/tools"
payload = {
"view": "tools",
"lane": "all",
"model": "<string>",
"splitByTier": False,
"institution": "<string>",
"account": "<string>",
"daterange": ["2023-12-25"],
"top": 200
}
headers = {
"x-access-token": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-access-token': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
view: 'tools',
lane: 'all',
model: '<string>',
splitByTier: false,
institution: '<string>',
account: '<string>',
daterange: ['2023-12-25'],
top: 200
})
};
fetch('https://pria.praxislxp.com/api/admin/histories/charts/tools', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://pria.praxislxp.com/api/admin/histories/charts/tools",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'view' => 'tools',
'lane' => 'all',
'model' => '<string>',
'splitByTier' => false,
'institution' => '<string>',
'account' => '<string>',
'daterange' => [
'2023-12-25'
],
'top' => 200
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-access-token: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://pria.praxislxp.com/api/admin/histories/charts/tools"
payload := strings.NewReader("{\n \"view\": \"tools\",\n \"lane\": \"all\",\n \"model\": \"<string>\",\n \"splitByTier\": false,\n \"institution\": \"<string>\",\n \"account\": \"<string>\",\n \"daterange\": [\n \"2023-12-25\"\n ],\n \"top\": 200\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-access-token", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://pria.praxislxp.com/api/admin/histories/charts/tools")
.header("x-access-token", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"view\": \"tools\",\n \"lane\": \"all\",\n \"model\": \"<string>\",\n \"splitByTier\": false,\n \"institution\": \"<string>\",\n \"account\": \"<string>\",\n \"daterange\": [\n \"2023-12-25\"\n ],\n \"top\": 200\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://pria.praxislxp.com/api/admin/histories/charts/tools")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-access-token"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"view\": \"tools\",\n \"lane\": \"all\",\n \"model\": \"<string>\",\n \"splitByTier\": false,\n \"institution\": \"<string>\",\n \"account\": \"<string>\",\n \"daterange\": [\n \"2023-12-25\"\n ],\n \"top\": 200\n}"
response = http.request(request)
puts response.read_body{
"success": true,
"data": {
"view": "<string>",
"lane": "<string>",
"model": "<string>",
"tier": "<string>",
"modeSource": "<string>",
"splitByTier": true,
"window": {
"start": "2023-11-07T05:31:56Z",
"end": "2023-11-07T05:31:56Z",
"days": 123
},
"turnMarkerSince": "2023-12-25",
"truncated": true,
"warnings": [
"<string>"
],
"rows": [
{}
]
}
}Authorizations
JWT token passed in x-access-token header
Body
tools - calls, turns, successes, MCP calls, distinct users and twins per tool and lane. turns - how many turns make 0, 1, 2, 3, 4 or 5+ tool calls per lane, with token sums. models - turns, turns with tools, cold turns, cache reads and writes per model and lane. conversations - conversation size buckets, tool use, first tool use, and distinct tools per tool-using conversation (text lanes only). The model, tier and modeSource filters select turns before they are grouped, so a conversation that mixes models or modes is measured over its matching turns only.
tools, turns, models, conversations Restrict to one lane. The conversations view does not accept convo. The workers lane exists only in the tools view. In the tools view, all returns the turn-level rows and the workers rows.
all, qanda, convo, agentic, workers Exact conversation model name to restrict to
200Count only the turns that declared this mode. auto - nothing picked a mode (classic users without the mode control, API traffic, and sends left on Auto). basic - one model chats and runs tools itself, with no background worker. advanced, expert, mythical - conducted modes. Applies to every view. Avatar embed sessions record no mode and are left out.
auto, basic, advanced, expert, mythical Count only the turns whose mode came from here. request - the send named a mode, from either interface. membership - the send named none and the server applied the member's saved mode. default - nothing named a mode. Applies to every view. It does not tell the classic interface from the new one.
request, membership, default Tools and turns views only - return one row per tier, with a tier key on every row. Any other view answers 400. A request without it returns exactly the rows it returned before this parameter existed. In the tools view, top then counts tool by lane by tier rows, so the same top truncates sooner (truncated says so) - narrow the lane or raise top.
Space-separated institution IDs to filter (institution is the API word for a Digital Twin). Must be a string - any other type is rejected.
Space-separated account IDs to filter. Must be a string.
Start and end day, inclusive, in UTC. At most 31 days. Defaults to the last 7 days.
2 elementsMaximum rows for the tools view, ordered by calls
1 <= x <= 500Was this page helpful?