Neatlogs
Instrumentation

HTTP Trace Injection

Send traces to Neatlogs via HTTP without using the SDK. Useful for custom integrations, non-Python/TypeScript environments, or direct API calls.

In addition to SDK-based instrumentation (Python SDK and TypeScript scripts), you can inject traces directly via HTTP API. This is useful when you want to:

  • Use Neatlogs from non-Python/non-TypeScript environments
  • Integrate with custom frameworks or orchestration tools
  • Send traces from external systems or agents that can't use the SDK
  • Build lightweight integrations without SDK dependencies

Quick Start

Send a POST request to the Neatlogs batch ingestion endpoint:

curl -X POST https://neatlogs.com/api/v1/traces/v4/batch \
  -H "Content-Type: application/json" \
  -H "x-api-key: <your-api-key>" \
  -d '{
    "workflow_name": "my-workflow",
    "spans": [
      {
        "span_id": "sp_123",
        "name": "my-operation",
        "start_time": 1717555210000,
        "end_time": 1717555215000,
        "attributes": {
          "openinference.span.kind": "WORKFLOW",
          "status": "success"
        }
      }
    ]
  }'

Authentication

Include your API key in the request header:

x-api-key: <your-api-key>

Get your API key from Settings → API Keys in Neatlogs.


Endpoint

Endpoint: POST /api/v1/traces/v4/batch

Base URL: https://neatlogs.com

Full URL: https://neatlogs.com/api/v1/traces/v4/batch


Request Format

Body

{
  "workflow_name": "string (required) - Name of the workflow/operation",
  "spans": [
    {
      "span_id": "string (required) - Unique span identifier",
      "parent_span_id": "string (optional) - UUID of parent span",
      "name": "string (required) - Span name/operation",
      "start_time": "number (required) - Unix timestamp in milliseconds",
      "end_time": "number (required) - Unix timestamp in milliseconds",
      "attributes": {
        "openinference.span.kind": "string (required) - LLM, TOOL, AGENT, WORKFLOW, etc.",
        "status": "string (optional) - success or error",
        "error_message": "string (optional) - Error details if status is error",
        "custom_field": "any (optional) - Custom attributes"
      },
      "input": "object (optional) - Input data",
      "output": "object (optional) - Output data"
    }
  ],
  "metrics": [
    {
      "name": "string - Metric name",
      "value": "number - Metric value",
      "unit": "string (optional) - Unit (ms, tokens, etc.)"
    }
  ]
}

Response

Success (202 Accepted):

{
  "status": "accepted",
  "batch_id": "batch_123456"
}

Error (400/401):

{
  "error": "Invalid payload format"
}

Span Kinds

The openinference.span.kind attribute determines the span type. Supported kinds:

KindDescriptionUse Case
WORKFLOWTop-level workflow/operationEntry point, agent orchestration
LLMLanguage model callClaude, GPT, Mistral calls
TOOLExternal tool/API callDatabase, API, third-party service
AGENTAI agent executionAgent loop, decision making
CHAINSequential operation chainMulti-step workflow
RETRIEVERData retrieval operationRAG, vector search, database query
EMBEDDINGEmbedding generationVector embedding, encoding

Example: Python

import requests
import time
import uuid

API_KEY = "your-api-key"
BASE_URL = "https://neatlogs.com"

def send_trace(workflow_name, spans):
    """Send a trace to Neatlogs via HTTP."""
    response = requests.post(
        f"{BASE_URL}/api/v1/traces/v4/batch",
        headers={"x-api-key": API_KEY, "Content-Type": "application/json"},
        json={
            "workflow_name": workflow_name,
            "spans": spans,
        }
    )
    
    if response.status_code == 202:
        result = response.json()
        print(f"Trace sent: {result['batch_id']}")
    else:
        print(f"Error: {response.status_code} - {response.text}")

# Example: Send a trace
now_ms = int(time.time() * 1000)
send_trace(
    workflow_name="my-agent",
    spans=[
        {
            "span_id": str(uuid.uuid4()),
            "name": "process_query",
            "start_time": now_ms,
            "end_time": now_ms + 5000,
            "attributes": {
                "openinference.span.kind": "WORKFLOW",
                "status": "success"
            },
            "input": {"query": "What is the weather?"},
            "output": {"result": "Sunny, 72°F"}
        },
        {
            "span_id": str(uuid.uuid4()),
            "parent_span_id": "parent-uuid",
            "name": "call_weather_api",
            "start_time": now_ms + 500,
            "end_time": now_ms + 3000,
            "attributes": {
                "openinference.span.kind": "TOOL",
                "status": "success"
            },
            "input": {"location": "San Francisco"},
            "output": {"temperature": 72, "condition": "sunny"}
        }
    ]
)

Example: JavaScript/TypeScript

async function sendTrace(
  workflowName: string,
  spans: Array<{
    span_id: string;
    parent_span_id?: string;
    name: string;
    start_time: number;
    end_time: number;
    attributes: Record<string, any>;
    input?: any;
    output?: any;
  }>
) {
  const response = await fetch(
    "https://neatlogs.com/api/v1/traces/v4/batch",
    {
      method: "POST",
      headers: {
        "x-api-key": process.env.NEATLOGS_API_KEY || "",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        workflow_name: workflowName,
        spans: spans,
      }),
    }
  );

  if (response.status === 202) {
    const result = await response.json();
    console.log(`Trace sent: ${result.batch_id}`);
  } else {
    console.error(`Error: ${response.status} - ${await response.text()}`);
  }
}

// Usage
const nowMs = Date.now();
sendTrace("my-agent", [
  {
    span_id: crypto.randomUUID(),
    name: "process_query",
    start_time: nowMs,
    end_time: nowMs + 5000,
    attributes: {
      "openinference.span.kind": "WORKFLOW",
      status: "success",
    },
    input: { query: "What is the weather?" },
    output: { result: "Sunny, 72°F" },
  },
]);

Example: cURL

Send a complete trace:

curl -X POST https://neatlogs.com/api/v1/traces/v4/batch \
  -H "Content-Type: application/json" \
  -H "x-api-key: sk_proj_abc123..." \
  -d '{
    "workflow_name": "customer-support-agent",
    "spans": [
      {
        "span_id": "sp-root-001",
        "name": "handle_customer_request",
        "start_time": 1717555210000,
        "end_time": 1717555235000,
        "attributes": {
          "openinference.span.kind": "WORKFLOW",
          "status": "success"
        },
        "input": {
          "customer_id": "cust_123",
          "question": "How do I reset my password?"
        },
        "output": {
          "answer": "Click 'Forgot Password' on the login page..."
        }
      },
      {
        "span_id": "sp-llm-001",
        "parent_span_id": "sp-root-001",
        "name": "call_llm",
        "start_time": 1717555212000,
        "end_time": 1717555225000,
        "attributes": {
          "openinference.span.kind": "LLM",
          "status": "success"
        },
        "input": {
          "prompt": "Generate a helpful response about password resets..."
        },
        "output": {
          "response": "Click Forgot Password..."
        }
      }
    ]
  }'

When to Use HTTP vs. SDK

MethodBest ForProsCons
SDK (Python/TS)Native applicationsAuto-instrumentation, decorators, easy setupLanguage-specific
HTTP APIAny language/environmentLanguage-agnostic, lightweight, flexibleManual span creation
MCP ToolsClaude/AI agentsStandard protocol, tool discoveryRequires MCP client

Timestamp Format

Use Unix milliseconds for start_time and end_time:

// Current time in milliseconds
const nowMs = Date.now();

// Convert from seconds to milliseconds
const unixSeconds = Math.floor(Date.now() / 1000);
const unixMs = unixSeconds * 1000;

// From ISO 8601 string
const iso = "2026-06-05T14:32:10Z";
const isoMs = new Date(iso).getTime();

Nested Spans (Parent-Child)

Use parent_span_id to create span hierarchies:

{
  "spans": [
    {
      "span_id": "root-123",
      "name": "workflow",
      "attributes": { "openinference.span.kind": "WORKFLOW" }
    },
    {
      "span_id": "child-456",
      "parent_span_id": "root-123",
      "name": "nested_operation",
      "attributes": { "openinference.span.kind": "TOOL" }
    }
  ]
}

Neatlogs automatically builds the tree structure based on parent-child relationships.


Error Handling

Spans with status error should include error details:

{
  "span_id": "sp-error-001",
  "name": "api_call",
  "attributes": {
    "openinference.span.kind": "TOOL",
    "status": "error",
    "error_message": "Connection timeout after 30s",
    "error_type": "TimeoutError"
  },
  "output": null
}

Batch Size & Performance

  • Max payload size: 1 MB per request
  • Recommended span count: 10–1,000 spans per batch
  • Max spans per batch: No hard limit, but keep payload under 1 MB
  • Latency: Traces typically appear in Neatlogs within 1-2 seconds

Batch multiple spans in a single request for better performance than sending individual spans.


Comparison with SDK Methods

FeatureSDKHTTP API
Auto-instrumentation
Decorators
Context managers
Manual span creation
Language-agnostic
External systems
Webhooks

See Also

On this page