Upsert spans in a batch
Insert or replace multiple spans (up to 1000) in a single request.
Use this for idempotent ingestion when spans may already exist, unlike POST
/v5/spans/batch, which only inserts. Items without an id are assigned a
generated UUID. The legacy trace store treats id as global and collapses
repeated ids to the last occurrence. The tracing service keys spans by
trace_id and id; it retains cross-trace ID collisions, and for a repeated
pair keeps a completed span over an in-progress one, otherwise the later
occurrence wins. In dual-write phases each store applies its own rule, and the
primary store determines the returned list. A batch larger than 1000 spans is
rejected with a validation error, as is any item whose end_timestamp precedes
its start_timestamp, which returns a 422. When the tracing service is the
primary store, its 400, 413 and 422 rejections keep that status and detail, as does
a 403 when the request carried its own API key, and every other failure returns a
503 with a Retry-After header. A batch that forms a cycle, or a span whose parent
belongs to another trace, is rejected with 409 before either store is written. A
parent outside the batch is looked up in Postgres, so that part of the check ends
once the account’s spans stop being written there. Which writes the tracing
service rejects depends in part on its storage engine: on Postgres deployments a
NUL byte or invalid UTF-8 inside trace_id, id, parent_id or group_id
fails the whole batch with a 400 naming the field, because replacing the byte would
change the identity the response echoes; a NUL byte or invalid UTF-8 in any other
field is replaced with U+FFFD and the span is persisted. ClickHouse deployments
store the bytes verbatim.
Credential redaction: values in the free-form input, output, metadata,
and expected objects, and in name, that are credential-shaped (bearer/JWT,
API keys, connection-string passwords) or under a credential-named key are replaced
with [REDACTED:credential] before the span is persisted, so the stored and
returned span reflects the redacted value (EY 12.3).
Upsert spans in a batch
import os
from datetime import datetime
from scale_gp_beta import SGPClient
client = SGPClient(
api_key=os.environ.get("SGP_API_KEY"), # This is the default and can be omitted
)
api_list_span = client.spans.upsert_batch(
items=[{
"name": "name",
"start_timestamp": datetime.fromisoformat("2019-12-27T18:11:19.117"),
"trace_id": "trace_id",
}],
)
print(api_list_span.items){
"items": [
{
"id": "id",
"account_id": "account_id",
"name": "name",
"start_timestamp": "2019-12-27T18:11:19.117Z",
"trace_id": "trace_id",
"application_interaction_id": "application_interaction_id",
"application_variant_id": "application_variant_id",
"created_by": {
"id": "id",
"type": "user",
"object": "identity"
},
"end_timestamp": "2019-12-27T18:11:19.117Z",
"expected": {
"foo": "bar"
},
"group_id": "group_id",
"input": {
"foo": "bar"
},
"input_tokens": 0,
"metadata": {
"foo": "bar"
},
"object": "span",
"obs_span_id": "obs_span_id",
"obs_trace_id": "obs_trace_id",
"output": {
"foo": "bar"
},
"output_tokens": 0,
"parent_id": "parent_id",
"status": "SUCCESS",
"type": "TEXT_INPUT"
}
],
"object": "list"
}Returns Examples
{
"items": [
{
"id": "id",
"account_id": "account_id",
"name": "name",
"start_timestamp": "2019-12-27T18:11:19.117Z",
"trace_id": "trace_id",
"application_interaction_id": "application_interaction_id",
"application_variant_id": "application_variant_id",
"created_by": {
"id": "id",
"type": "user",
"object": "identity"
},
"end_timestamp": "2019-12-27T18:11:19.117Z",
"expected": {
"foo": "bar"
},
"group_id": "group_id",
"input": {
"foo": "bar"
},
"input_tokens": 0,
"metadata": {
"foo": "bar"
},
"object": "span",
"obs_span_id": "obs_span_id",
"obs_trace_id": "obs_trace_id",
"output": {
"foo": "bar"
},
"output_tokens": 0,
"parent_id": "parent_id",
"status": "SUCCESS",
"type": "TEXT_INPUT"
}
],
"object": "list"
}