import decimalai
# Auto-instrumentation (recommended)
decimalai.init(api_key="dai_sk_...", openai_agents=True)
# Or manual tracing with decorator
@decimalai.trace(agent_name="my-agent")
def run_agent(query: str) -> str:
return llm.invoke(query)
run_agent("What is the weather?")curl --request POST \
--url https://api.decimal.ai/api/v1/traces \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{}'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({})
};
fetch('https://api.decimal.ai/api/v1/traces', 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://api.decimal.ai/api/v1/traces",
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([
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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://api.decimal.ai/api/v1/traces"
payload := strings.NewReader("{}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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://api.decimal.ai/api/v1/traces")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.decimal.ai/api/v1/traces")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{}"
response = http.request(request)
puts response.read_body{
"status": "ok",
"id": "trc_abc123",
"trace_id": "trc_abc123",
"agent_name": "my-agent",
"spans": 5,
"llm_calls": 3
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}Ingest a trace
Ingest a single trace from the SDK.
Data Model
A trace contains two types of children:
spans— represent operations (agent steps, tool calls, chains). Nest viaparent_span_idto form a tree.llm_calls— represent individual LLM invocations with full fidelity (model, tokens, messages, response). Link to a parent span viaspan_id.
Structuring Rules
Spans should be used for containers and non-LLM operations:
span_type | Use for | Example |
|---|---|---|
agent | Top-level agent invocation (root span) | finance-research-agent |
tool | Tool/function execution | get_stock_price |
chain | Multi-step pipeline or sub-chain | research-pipeline |
retrieval | RAG retrieval step | vector-search |
llm | Wrapper span for an LLM call (optional) | LLM: plan step 1 |
LLM calls should be used for every LLM invocation:
- Always set
span_idto the parent span that triggered this call - Include
rendered_input(list of messages),output,model_name,provider - Include
started_at/ended_atfor timeline visualization - Include token counts (
input_tokens,output_tokens) for cost tracking - Include
cache_read_tokens/cache_creation_tokenswhen the provider reports a prompt-cache split. Send them as the provider reported them — do NOT fold them intoinput_tokens. Omit a field entirely (rather than sending0) when the provider did not report it: the platform storesnullas “unknown” and0as “reported, and it was a cache miss”, and only the second one answers “did my prefix stay cacheable?”. Anthropic’scache_read_input_tokens/cache_creation_input_tokensand OpenAI’sprompt_tokens_details.cached_tokensare also accepted under their native names.
Tree Rendering
The frontend builds a unified tree from both tables:
- If an
llm_callhasspan_idmatching a span → it enriches that span (shown as one node with LLM details in a tab) - If an
llm_callhas no matching span → shown as a standalone 🧠 node under the root span - All children are sorted by
started_atfor chronological order
Example Structure
agent-step (span, type=agent)
├── llm_call_1 (llm_call, span_id=agent-step)
├── tool-span (span, type=tool, parent_span_id=agent-step)
└── llm_call_2 (llm_call, span_id=agent-step)
Returns 409 if a trace with the same ID already exists.
Partial success
The trace row itself is written first; the enrichment stages that follow
(built-in evals, SDK eval scores, skill activations, manifest inference,
parent propagation) are individually guarded so one broken stage cannot
drop the trace. If any of them fails, the response is 207 Multi-Status
with status="partial" and failed_stages=[...] naming the stages whose
writes did not land — the trace ID is still returned and is still valid.
Treat 207 as “stored, but do not trust the derived data for this trace”.
import decimalai
# Auto-instrumentation (recommended)
decimalai.init(api_key="dai_sk_...", openai_agents=True)
# Or manual tracing with decorator
@decimalai.trace(agent_name="my-agent")
def run_agent(query: str) -> str:
return llm.invoke(query)
run_agent("What is the weather?")curl --request POST \
--url https://api.decimal.ai/api/v1/traces \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{}'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({})
};
fetch('https://api.decimal.ai/api/v1/traces', 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://api.decimal.ai/api/v1/traces",
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([
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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://api.decimal.ai/api/v1/traces"
payload := strings.NewReader("{}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
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://api.decimal.ai/api/v1/traces")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.decimal.ai/api/v1/traces")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{}"
response = http.request(request)
puts response.read_body{
"status": "ok",
"id": "trc_abc123",
"trace_id": "trc_abc123",
"agent_name": "my-agent",
"spans": 5,
"llm_calls": 3
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>",
"input": "<unknown>",
"ctx": {}
}
]
}Authorizations
Enter your API key (e.g. dai_sk_test_key_001)
Headers
Cookies
Body
The body is of type Payload · object.
Response
Ingestion result with counts
The response is of type Response Ingest Trace Api V1 Traces Post · object.