Skip to content

API reference

View as Markdown

Most people never call these endpoints directly: the Python SDK and OpenTelemetry call them for you. Use this page if you are writing your own client.

  • Base URL: https://ingest.ai-tally.com
  • Authentication: every request sends your API key as Authorization: Bearer <key>. The key needs write or admin scope.
  • Where this comes from: the details below are generated from the ai-tally gateway’s own code.

POST https://ingest.ai-tally.com/v1/batches

The endpoint the Python SDK posts to. Idempotent on batch_id: resending a batch after a timeout or a 429/503 never double counts it.

Authentication: Authorization: Bearer $TALLY_KEY, a key withwrite or admin scope.

Headers

NameRequired
X-Ingest-ProtocolNo

Body (JSON): The ai-tally batch format (aitally.wire.BatchRequest). Unknown fields are tolerated. With key auth the key decides the tenant, so tenant_id may be empty.

FieldTypeNotes
tenant_id (required)stringSend an empty string. Your API key already identifies your organization; a value that is not the key's own tenant is refused with TENANT_MISMATCH.
batch_idstringIdempotency key for the batch.
sdk_versionstring
client_send_ts_nsinteger
resource_spansarrayFlat span objects whose keys are gen_ai.* attributes. Prompt, completion and other body fields are never stored. Name the software that sent the span with gen_ai.integration.name (lowercase letters, digits, ., _, -; up to 64 characters) and gen_ai.integration.version (for example 0.1.0). Both are optional; a value outside that shape is dropped and the span is still accepted.
business_eventsarray
identity_linksarray
samplingobject

Success body (200)

FieldTypeNotes
batch_idstring
status"accepted" | "partial" | "rejected" | "retry"
accepted_spansinteger
partial_errorsarray
envelope_errorsarray
server_hintsobject
replayedboolean

Responses

StatusMeaning
200Accepted. status is accepted, or partial when some spans were rejected (see partial_errors). A batch whose batch_id was already processed returns the original result with replayed: true.
400INVALID_SCHEMA: X-Ingest-Protocol names a protocol the gateway does not support. Supported: ingest-v1.
401UNAUTHENTICATED: missing bearer token, or an invalid or revoked key.
403FORBIDDEN_SCOPE: the key has read scope and cannot write spans. TENANT_MISMATCH: the body names a tenant the key is not bound to.
422Every span in the batch was rejected (a BatchResponse with status: rejected and per-span partial_errors), or the body is malformed ({"detail": "..."}).
429RATE_LIMITED, QUOTA_EXCEEDED: back off for Retry-After seconds and resend the same body with the same batch_id. Header: Retry-After.
503Temporarily unable to accept the batch (status: retry). Includes IDEMPOTENCY_UNAVAILABLE. Resend the same body with the same batch_id; it is never double counted. Header: Retry-After.

Each span is a flat object of gen_ai.* attributes. Give each batch its own batch_id: sending the same batch_id again returns the first result instead of counting the batch twice, which makes retries safe.

Terminal window
curl https://ingest.ai-tally.com/v1/batches \
-H "Authorization: Bearer $TALLY_KEY" \
-H "Content-Type: application/json" \
-d '{"tenant_id": "", "batch_id": "my-batch-0001", "resource_spans": [{
"trace_id": "t1", "span_id": "s1",
"gen_ai.system": "openai", "gen_ai.operation.name": "chat",
"gen_ai.request.model": "gpt-4o-mini",
"gen_ai.usage.input_tokens": 12, "gen_ai.usage.output_tokens": 5}]}'

POST https://ingest.ai-tally.com/v1/otlp/traces

Accepts an OTLP ExportTraceServiceRequest as protobuf (application/x-protobuf, the default for OTLP/HTTP exporters and the Collector's otlphttp exporter) or JSON (application/json). Any other Content-Type is a 415. The body may be gzip-compressed (Content-Encoding: gzip). A protobuf request gets an ExportTraceServiceResponse back on 200, with partial_success.rejected_spans counting any refused spans. /v1/traces is the same operation, so a base OTEL_EXPORTER_OTLP_ENDPOINT works. OTLP over gRPC is not served. Spans are grouped by gen_ai.operation.name.

Authentication: Authorization: Bearer $TALLY_KEY, a key withwrite or admin scope.

Body (JSON): OTLP ExportTraceServiceRequest (JSON encoding).

FieldTypeNotes
resourceSpansarray

Success body (200)

FieldTypeNotes
batch_idstring
status"accepted" | "partial" | "rejected" | "retry"
accepted_spansinteger
partial_errorsarray
envelope_errorsarray
server_hintsobject
replayedboolean

Responses

StatusMeaning
200Accepted. status is accepted, or partial when some spans were rejected (see partial_errors). A batch whose batch_id was already processed returns the original result with replayed: true.
400The body is not a decodable protobuf or JSON export, or not valid gzip.
401UNAUTHENTICATED: missing bearer token, or an invalid or revoked key. Checked before the body is read.
403FORBIDDEN_SCOPE: the key has read scope and cannot write spans. TENANT_MISMATCH: the body names a tenant the key is not bound to.
413The body, after gzip, is larger than the smaller of 32 MiB (the gateway's otlp_max_body_bytes default) and max_batch_size x max_span_bytes (see /v1/capabilities), or the export holds more than max_batch_size spans. Split the export into smaller requests.
415Content-Type is not application/x-protobuf or application/json, or Content-Encoding is not gzip or identity.
422Every span in the batch was rejected (a BatchResponse with status: rejected and per-span partial_errors), or the body is malformed ({"detail": "..."}).
429RATE_LIMITED, QUOTA_EXCEEDED: back off for Retry-After seconds and resend the same body with the same batch_id. Header: Retry-After.
503Temporarily unable to accept the batch (status: retry). Includes IDEMPOTENCY_UNAVAILABLE. Resend the same body with the same batch_id; it is never double counted. Header: Retry-After.
Terminal window
curl https://ingest.ai-tally.com/v1/otlp/traces \
-H "Authorization: Bearer $TALLY_KEY" \
-H "Content-Type: application/json" \
-d '{"resourceSpans": [{"scopeSpans": [{"spans": [{
"traceId": "5b8efff798038103d269b633813fc60c", "spanId": "eee19b7ec3c1b174",
"name": "chat", "startTimeUnixNano": "'"$(date +%s)000000000"'",
"attributes": [
{"key": "gen_ai.system", "value": {"stringValue": "openai"}},
{"key": "gen_ai.operation.name", "value": {"stringValue": "chat"}},
{"key": "gen_ai.request.model", "value": {"stringValue": "gpt-4o-mini"}},
{"key": "gen_ai.usage.input_tokens", "value": {"intValue": "12"}},
{"key": "gen_ai.usage.output_tokens", "value": {"intValue": "5"}}]}]}]}]}'

GET https://ingest.ai-tally.com/v1/tenant/hmac-key

Returns the active HMAC-SHA256 key the SDK uses to hash account ids before they leave your process. Always requires a key with write or admin scope. The response is a secret: do not log it.

Authentication: Authorization: Bearer $TALLY_KEY, a key withwrite or admin scope.

Success body (200)

FieldTypeNotes
tenant_idstring
key_versionstringKey version, like v1.
key_material_b64stringTreat as a secret.
algorithm"HMAC-SHA256"

Responses

StatusMeaning
200The active key and its version.
401UNAUTHENTICATED: missing bearer token, or an invalid or revoked key.
403FORBIDDEN_SCOPE: a read key cannot fetch key material. HMAC_EXPORT_DISABLED: key export is disabled for this organization.
404No key material exists for this organization. Hash nothing and send spans unattributed rather than sending raw ids.
503The key store could not be reached. Retryable; no key material was returned.

The reply contains your organization’s secret hashing key. Keep it out of logs. Most apps never call this: aitally.hash_account calls it for you.

Terminal window
curl https://ingest.ai-tally.com/v1/tenant/hmac-key \
-H "Authorization: Bearer $TALLY_KEY"
  • Sending from Python? The Python SDK handles batching, retries and hashing for you.