curl --request POST \
--url https://api.arize.com/v2/spans/annotate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"project_id": "proj_abc123",
"start_time": "2024-01-01T00:00:00Z",
"end_time": "2024-01-08T00:00:00Z",
"annotations": [
{
"record_id": "span_abc",
"values": [
{
"name": "relevance",
"label": "good",
"score": 0.9
}
]
}
]
}
'import requests
url = "https://api.arize.com/v2/spans/annotate"
payload = {
"project_id": "proj_abc123",
"start_time": "2024-01-01T00:00:00Z",
"end_time": "2024-01-08T00:00:00Z",
"annotations": [
{
"record_id": "span_abc",
"values": [
{
"name": "relevance",
"label": "good",
"score": 0.9
}
]
}
]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
project_id: 'proj_abc123',
start_time: '2024-01-01T00:00:00Z',
end_time: '2024-01-08T00:00:00Z',
annotations: [
{
record_id: 'span_abc',
values: [{name: 'relevance', label: 'good', score: 0.9}]
}
]
})
};
fetch('https://api.arize.com/v2/spans/annotate', 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.arize.com/v2/spans/annotate",
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([
'project_id' => 'proj_abc123',
'start_time' => '2024-01-01T00:00:00Z',
'end_time' => '2024-01-08T00:00:00Z',
'annotations' => [
[
'record_id' => 'span_abc',
'values' => [
[
'name' => 'relevance',
'label' => 'good',
'score' => 0.9
]
]
]
]
]),
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.arize.com/v2/spans/annotate"
payload := strings.NewReader("{\n \"project_id\": \"proj_abc123\",\n \"start_time\": \"2024-01-01T00:00:00Z\",\n \"end_time\": \"2024-01-08T00:00:00Z\",\n \"annotations\": [\n {\n \"record_id\": \"span_abc\",\n \"values\": [\n {\n \"name\": \"relevance\",\n \"label\": \"good\",\n \"score\": 0.9\n }\n ]\n }\n ]\n}")
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.arize.com/v2/spans/annotate")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"project_id\": \"proj_abc123\",\n \"start_time\": \"2024-01-01T00:00:00Z\",\n \"end_time\": \"2024-01-08T00:00:00Z\",\n \"annotations\": [\n {\n \"record_id\": \"span_abc\",\n \"values\": [\n {\n \"name\": \"relevance\",\n \"label\": \"good\",\n \"score\": 0.9\n }\n ]\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.arize.com/v2/spans/annotate")
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 = "{\n \"project_id\": \"proj_abc123\",\n \"start_time\": \"2024-01-01T00:00:00Z\",\n \"end_time\": \"2024-01-08T00:00:00Z\",\n \"annotations\": [\n {\n \"record_id\": \"span_abc\",\n \"values\": [\n {\n \"name\": \"relevance\",\n \"label\": \"good\",\n \"score\": 0.9\n }\n ]\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"status": 400,
"title": "Invalid request parameters",
"detail": "The 'name' field is required and must be a non-empty string.",
"instance": "/resource",
"type": "https://arize.com/docs/ax/rest-reference/errors#invalid-request"
}{
"status": 401,
"title": "Authentication required",
"detail": "You must be authenticated to access this resource.",
"instance": "/resource",
"type": "https://arize.com/docs/ax/rest-reference/errors#authentication-required"
}{
"status": 403,
"title": "Access forbidden",
"detail": "You do not have permission to access this resource.",
"instance": "/resource/12345",
"type": "https://arize.com/docs/ax/rest-reference/errors#access-forbidden"
}{
"status": 404,
"title": "Resource not found",
"detail": "The requested resource with ID '12345' was not found.",
"instance": "/resource/12345",
"type": "https://arize.com/docs/ax/rest-reference/errors#resource-not-found"
}{
"status": 422,
"title": "Unprocessable Entity",
"detail": "One or more fields failed validation.",
"instance": "/resource/12345",
"type": "https://arize.com/docs/ax/rest-reference/errors#unprocessable-entity"
}{
"status": 429,
"title": "Rate limit exceeded",
"detail": "You have exceeded the allowed number of requests. Please try again later.",
"instance": "/resource",
"type": "https://arize.com/docs/ax/rest-reference/errors#rate-limit-exceeded"
}Annotate a batch of project spans
Write human annotations to a batch of spans in a project.
Idempotency: Writes use upsert semantics — submitting the same annotation config name for the same span overwrites the previous value. Retrying on network failure will not create duplicates.
202 Accepted: The annotations have been accepted and will be written. Visibility in read queries may lag by a short interval.
Partial failure: Writes are grouped by calendar day and processed sequentially. A non-2xx response means the request failed during the write phase — annotations for earlier calendar-day buckets may already be saved while later ones are not. It is safe to retry the full request; re-submitting a record that was already saved will overwrite it with the same value (no duplicates).
Payload Requirements
project_idis required and must identify a project the caller has span annotation access to.annotationsis a list of per-span annotation inputs. Each entry identifies one span by itsrecord_idand provides one or more annotation values.- Each
record_idmust be unique within the request (duplicates return 400). - Each record’s
valueslist must not contain duplicate annotation config names (returns 400). granularityselects whatrecord_ididentifies:SPAN(a span ID, the default),TRACE(a trace’s root span ID), orSESSION(a session ID). For SESSION, the annotation is written to the root span of the session’s earliest trace found within the lookup window.start_time/end_timeconstrain the time range for span lookup. If omitted,start_timedefaults to 31 days beforeend_time(7 days for SESSION granularity) andend_timeto now. Bothstart_timeandend_timemay not be in the future. For SPAN/TRACE the window may not exceed 31 days; for SESSION it may not exceed 7 days. If ANY span cannot be located within the given range, the entire request is rejected with 404 and no annotations are written (all-or-nothing pre-validation). Only after all spans are confirmed does the write phase begin.- Annotation names must match existing annotation configs in the project’s space.
- Up to 1000 records may be annotated per request for SPAN/TRACE granularity; up to 100 records per request for SESSION granularity.
Valid example
{
"project_id": "proj_abc123",
"annotations": [
{"record_id": "span_abc", "values": [{"name": "relevance", "label": "good", "score": 1.0}]}
]
}
Valid example (session granularity)
{
"project_id": "proj_abc123",
"granularity": "SESSION",
"annotations": [
{"record_id": "session_abc", "values": [{"name": "quality", "label": "good", "score": 1.0}]}
]
}
Invalid example (annotation name not found in space)
{
"project_id": "proj_abc123",
"annotations": [
{"record_id": "span_abc", "values": [{"name": "nonexistent_config"}]}
]
}
Invalid example (time window exceeds 31 days)
{
"project_id": "proj_abc123",
"start_time": "2025-01-01T00:00:00Z",
"end_time": "2025-03-01T00:00:00Z",
"annotations": [
{"record_id": "span_abc", "values": [{"name": "relevance", "label": "good"}]}
]
}
Invalid example (session time window exceeds 7 days)
{
"project_id": "proj_abc123",
"granularity": "SESSION",
"start_time": "2025-01-01T00:00:00Z",
"end_time": "2025-01-15T00:00:00Z",
"annotations": [
{"record_id": "session_abc", "values": [{"name": "quality", "label": "good"}]}
]
}
curl --request POST \
--url https://api.arize.com/v2/spans/annotate \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"project_id": "proj_abc123",
"start_time": "2024-01-01T00:00:00Z",
"end_time": "2024-01-08T00:00:00Z",
"annotations": [
{
"record_id": "span_abc",
"values": [
{
"name": "relevance",
"label": "good",
"score": 0.9
}
]
}
]
}
'import requests
url = "https://api.arize.com/v2/spans/annotate"
payload = {
"project_id": "proj_abc123",
"start_time": "2024-01-01T00:00:00Z",
"end_time": "2024-01-08T00:00:00Z",
"annotations": [
{
"record_id": "span_abc",
"values": [
{
"name": "relevance",
"label": "good",
"score": 0.9
}
]
}
]
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
project_id: 'proj_abc123',
start_time: '2024-01-01T00:00:00Z',
end_time: '2024-01-08T00:00:00Z',
annotations: [
{
record_id: 'span_abc',
values: [{name: 'relevance', label: 'good', score: 0.9}]
}
]
})
};
fetch('https://api.arize.com/v2/spans/annotate', 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.arize.com/v2/spans/annotate",
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([
'project_id' => 'proj_abc123',
'start_time' => '2024-01-01T00:00:00Z',
'end_time' => '2024-01-08T00:00:00Z',
'annotations' => [
[
'record_id' => 'span_abc',
'values' => [
[
'name' => 'relevance',
'label' => 'good',
'score' => 0.9
]
]
]
]
]),
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.arize.com/v2/spans/annotate"
payload := strings.NewReader("{\n \"project_id\": \"proj_abc123\",\n \"start_time\": \"2024-01-01T00:00:00Z\",\n \"end_time\": \"2024-01-08T00:00:00Z\",\n \"annotations\": [\n {\n \"record_id\": \"span_abc\",\n \"values\": [\n {\n \"name\": \"relevance\",\n \"label\": \"good\",\n \"score\": 0.9\n }\n ]\n }\n ]\n}")
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.arize.com/v2/spans/annotate")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"project_id\": \"proj_abc123\",\n \"start_time\": \"2024-01-01T00:00:00Z\",\n \"end_time\": \"2024-01-08T00:00:00Z\",\n \"annotations\": [\n {\n \"record_id\": \"span_abc\",\n \"values\": [\n {\n \"name\": \"relevance\",\n \"label\": \"good\",\n \"score\": 0.9\n }\n ]\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.arize.com/v2/spans/annotate")
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 = "{\n \"project_id\": \"proj_abc123\",\n \"start_time\": \"2024-01-01T00:00:00Z\",\n \"end_time\": \"2024-01-08T00:00:00Z\",\n \"annotations\": [\n {\n \"record_id\": \"span_abc\",\n \"values\": [\n {\n \"name\": \"relevance\",\n \"label\": \"good\",\n \"score\": 0.9\n }\n ]\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"status": 400,
"title": "Invalid request parameters",
"detail": "The 'name' field is required and must be a non-empty string.",
"instance": "/resource",
"type": "https://arize.com/docs/ax/rest-reference/errors#invalid-request"
}{
"status": 401,
"title": "Authentication required",
"detail": "You must be authenticated to access this resource.",
"instance": "/resource",
"type": "https://arize.com/docs/ax/rest-reference/errors#authentication-required"
}{
"status": 403,
"title": "Access forbidden",
"detail": "You do not have permission to access this resource.",
"instance": "/resource/12345",
"type": "https://arize.com/docs/ax/rest-reference/errors#access-forbidden"
}{
"status": 404,
"title": "Resource not found",
"detail": "The requested resource with ID '12345' was not found.",
"instance": "/resource/12345",
"type": "https://arize.com/docs/ax/rest-reference/errors#resource-not-found"
}{
"status": 422,
"title": "Unprocessable Entity",
"detail": "One or more fields failed validation.",
"instance": "/resource/12345",
"type": "https://arize.com/docs/ax/rest-reference/errors#unprocessable-entity"
}{
"status": 429,
"title": "Rate limit exceeded",
"detail": "You have exceeded the allowed number of requests. Please try again later.",
"instance": "/resource",
"type": "https://arize.com/docs/ax/rest-reference/errors#rate-limit-exceeded"
}Authorizations
Most Arize AI endpoints require authentication. For those endpoints that require authentication, include your API key in the request header using the format
Body
Body containing span annotation batch
Batch annotation request for project spans.
The project (model) ID whose spans are being annotated.
"proj_abc123"
Batch of annotations to write. Up to 1000 records per request for SPAN or TRACE granularity; up to 100 records per request for SESSION granularity.
1 - 1000 elementsShow child attributes
Show child attributes
Start of the time range for span lookup. Optional; defaults to 31 days before end_time, or 7 days before end_time when granularity is SESSION.
"2024-01-01T00:00:00Z"
End of the time range for span lookup. Optional; defaults to now.
"2024-01-08T00:00:00Z"
Whether the record is a span, a trace, or a session, which affects whether annotations are written as span, trace, or session annotations. For TRACE, each record_id must be a trace's root span; attempts to write trace annotations on non-root spans will be rejected. For SESSION, each record_id is a session ID; the annotation is written to the root span of the session's earliest trace within the lookup window. Optional; defaults to 'SPAN'.
SPAN, TRACE, SESSION Response
Annotations accepted. Writes are idempotent; retry on failure is safe.
Was this page helpful?