## Upsert spans in a batch

`client.Spans.UpsertBatch(ctx, body) (*APIListSpan, error)`

**put** `/v5/spans/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 `id`s 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).

### Parameters

- `body SpanUpsertBatchParams`

  - `SpanBatchCreate param.Field[SpanBatchCreate]`

### Returns

- `type APIListSpan struct{…}`

  - `Items []Span`

    - `ID string`

    - `AccountID string`

    - `Name string`

    - `StartTimestamp Time`

    - `TraceID string`

      id for grouping traces together, uuid is recommended

    - `ApplicationInteractionID string`

      The interaction ID this span belongs to

    - `ApplicationVariantID string`

      The id of the application variant this span belongs to

    - `CreatedBy Identity`

      The identity that created the entity.

      - `ID string`

      - `Type IdentityType`

        - `const IdentityTypeUser IdentityType = "user"`

        - `const IdentityTypeServiceAccount IdentityType = "service_account"`

      - `Object IdentityObject`

        - `const IdentityObjectIdentity IdentityObject = "identity"`

    - `EndTimestamp Time`

    - `Expected map[string, any]`

    - `GroupID string`

      Reference to a group_id

    - `Input map[string, any]`

    - `InputTokens int64`

      Prompt tokens the producer reported for this span, absent when it reported none. Same quantity the input_tokens sort orders by.

    - `Metadata map[string, any]`

    - `Object SpanObject`

      - `const SpanObjectSpan SpanObject = "span"`

    - `ObsSpanID string`

      W3C span id of the observability span this span executed in.

    - `ObsTraceID string`

      W3C trace id of the observability trace this span executed in. Null for spans written without the edge, and for accounts still served by the legacy trace store.

    - `Output map[string, any]`

    - `OutputTokens int64`

      Completion tokens the producer reported for this span, absent when it reported none. Same quantity the output_tokens sort orders by.

    - `ParentID string`

      Reference to a parent span_id

    - `Status SpanStatus`

      - `const SpanStatusSuccess SpanStatus = "SUCCESS"`

      - `const SpanStatusError SpanStatus = "ERROR"`

      - `const SpanStatusCanceled SpanStatus = "CANCELED"`

    - `Type SpanType`

      - `const SpanTypeTextInput SpanType = "TEXT_INPUT"`

      - `const SpanTypeTextOutput SpanType = "TEXT_OUTPUT"`

      - `const SpanTypeCompletionInput SpanType = "COMPLETION_INPUT"`

      - `const SpanTypeCompletion SpanType = "COMPLETION"`

      - `const SpanTypeKBRetrieval SpanType = "KB_RETRIEVAL"`

      - `const SpanTypeKBInput SpanType = "KB_INPUT"`

      - `const SpanTypeReranking SpanType = "RERANKING"`

      - `const SpanTypeExternalEndpoint SpanType = "EXTERNAL_ENDPOINT"`

      - `const SpanTypePromptEngineering SpanType = "PROMPT_ENGINEERING"`

      - `const SpanTypeDocumentInput SpanType = "DOCUMENT_INPUT"`

      - `const SpanTypeMapReduce SpanType = "MAP_REDUCE"`

      - `const SpanTypeDocumentSearch SpanType = "DOCUMENT_SEARCH"`

      - `const SpanTypeDocumentPrompt SpanType = "DOCUMENT_PROMPT"`

      - `const SpanTypeCustom SpanType = "CUSTOM"`

      - `const SpanTypeCodeExecution SpanType = "CODE_EXECUTION"`

      - `const SpanTypeDataManipulation SpanType = "DATA_MANIPULATION"`

      - `const SpanTypeEvaluation SpanType = "EVALUATION"`

      - `const SpanTypeFileRetrieval SpanType = "FILE_RETRIEVAL"`

      - `const SpanTypeKBAddChunk SpanType = "KB_ADD_CHUNK"`

      - `const SpanTypeKBManagement SpanType = "KB_MANAGEMENT"`

      - `const SpanTypeGuardrail SpanType = "GUARDRAIL"`

      - `const SpanTypeOutputGuardrail SpanType = "OUTPUT_GUARDRAIL"`

      - `const SpanTypeTracer SpanType = "TRACER"`

      - `const SpanTypeAgentTracer SpanType = "AGENT_TRACER"`

      - `const SpanTypeAgentWorkflow SpanType = "AGENT_WORKFLOW"`

      - `const SpanTypeStandalone SpanType = "STANDALONE"`

  - `Object APIListSpanObject`

    - `const APIListSpanObjectList APIListSpanObject = "list"`

### Example

```go
package main

import (
  "context"
  "fmt"
  "time"

  "github.com/scaleapi/sgp-dev-go"
  "github.com/scaleapi/sgp-dev-go/option"
)

func main() {
  client := sgpdev.NewClient(
    option.WithAPIKey("My API Key"),
    option.WithAccountID("My Account ID"),
  )
  apiListSpan, err := client.Spans.UpsertBatch(context.TODO(), sgpdev.SpanUpsertBatchParams{
    SpanBatchCreate: sgpdev.SpanBatchCreateParam{
      Items: []sgpdev.SpanCreateParam{sgpdev.SpanCreateParam{
        Name: "name",
        StartTimestamp: time.Now(),
        TraceID: "trace_id",
      }},
    },
  })
  if err != nil {
    panic(err.Error())
  }
  fmt.Printf("%+v\n", apiListSpan.Items)
}
```

#### Response

```json
{
  "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"
}
```
