Manually log spans

Log individual or batched LLM calls to Respan via the Spans API — for custom pipelines without an SDK.

Use the Spans API to log individual LLM calls or batches directly over HTTP. This is useful for custom pipelines, languages without a Respan SDK, or one-off integrations. For SDK-based setup, see Set up the SDK. For existing OpenTelemetry setups, see OpenTelemetry (OTLP).


Spans API

Log individual spans directly using the Spans API. This is useful for logging LLM calls from custom pipelines.

1import requests
2import json
3
4url = "https://api.respan.ai/api/request-logs/"
5payload = {
6 "model": "gpt-4o",
7 "log_type": "chat",
8 "input": json.dumps([
9 {
10 "role": "user",
11 "content": "How can I help a customer with a billing issue?"
12 }
13 ]),
14 "output": json.dumps({
15 "role": "assistant",
16 "content": "I'd be happy to help with billing issues. First, let me check your account details..."
17 }),
18 "customer_identifier": "support_agent_001"
19}
20
21headers = {
22 "Authorization": "Bearer YOUR_RESPAN_API_KEY",
23 "Content-Type": "application/json"
24}
25
26response = requests.post(url, headers=headers, json=payload)

See the full Spans API reference for all available fields.

Batch spans

Use POST /api/request-logs/bulk/ when you already have multiple completed calls to log. Wrap the same per-span objects accepted by the single-span endpoint in a logs array.

1import requests
2
3url = "https://api.respan.ai/api/request-logs/bulk/"
4headers = {
5 "Authorization": "Bearer YOUR_RESPAN_API_KEY",
6 "Content-Type": "application/json",
7}
8payload = {
9 "logs": [
10 {
11 "model": "gpt-4o-mini",
12 "prompt_messages": [
13 {"role": "user", "content": "What is 2 + 2?"}
14 ],
15 "completion_message": {"role": "assistant", "content": "4"},
16 "metadata": {"source": "lambda-batch"},
17 },
18 {
19 "model": "gpt-4o-mini",
20 "prompt_messages": [
21 {"role": "user", "content": "What is the capital of France?"}
22 ],
23 "completion_message": {"role": "assistant", "content": "Paris"},
24 "metadata": {"source": "lambda-batch"},
25 },
26 ]
27}
28
29response = requests.post(url, headers=headers, json=payload, timeout=30)
30result = response.json()
31print(result)

When the batch reaches per-row processing, the response reports accepted and rejected rows. Error indices are zero-based positions in the submitted logs array:

1{
2 "success_count": 1,
3 "error_count": 1,
4 "errors": [
5 {
6 "index": 1,
7 "error": "Invalid logging format: Input should be a valid list"
8 }
9 ]
10}
  • Send between 1 and 500 spans per request.
  • A full or partial success returns 201 Created. Always inspect error_count; retry only the rows listed in errors, because successful rows were already accepted.
  • A malformed or empty logs array, or a batch where every row fails, returns 400 Bad Request.
  • More than 500 rows returns 422 Unprocessable Entity before any row is processed.
  • For API-key calls, the limit is 30 bulk requests per minute per organization, shared across all API keys. JWT calls are limited per user. Full 500-row batches provide a theoretical maximum of about 15,000 spans per minute. See API rate limits.

success_count means those rows passed synchronous validation and were accepted for ingestion. Storage is completed asynchronously.

For Lambda workloads, send events to a queue such as SQS or Kinesis and let a small consumer flush when it collects 500 rows or reaches a short time interval. This keeps ingestion latency out of the request path and makes retrying only failed indices straightforward.

See the full bulk spans API reference for the request and response schemas.