Ingestion API
Send native observations, LangSmith-compatible runs, or OpenTelemetry traces to WeaveScope.
Use the ingestion API to send trace data from custom clients, compatibility bridges, and OpenTelemetry collectors.
Base URL:
https://app.weavescope.com
Authentication
Every request requires a project API key in one of these headers:
Authorization: Bearer ws_...
x-api-key: ws_...
The key must have ingest:write. A missing or invalid key receives 401 Unauthorized; a valid key without ingest permission receives 403 Forbidden.
Endpoints
| Method | Path | Accepts |
|---|---|---|
POST
|
/api/v1/observations/batch
| Native WeaveScope observations. |
POST
|
/api/v1/runs
| A LangSmith-compatible run creation. |
PATCH
|
/api/v1/runs/:id
| A LangSmith-compatible run update. |
POST
|
/api/v1/runs/batch
|
LangSmith-compatible post and patch arrays.
|
POST
|
/api/public/otel/v1/traces
| OpenTelemetry Protocol over HTTP (OTLP/HTTP) traces. |
Native observations
Send native events in an events array:
{
"events": [
{
"observation_id": "018f5f2a-77a6-7bd4-bb58-df7b90f8e337",
"trace_id": "018f5f2a-77a6-7bd4-bb58-df7b90f8e337",
"operation": "finish",
"name": "support.reply",
"kind": "agent",
"status": "success",
"start_time": "2026-07-02T12:00:00Z",
"end_time": "2026-07-02T12:00:03Z",
"event_version": 17830032030000002,
"inputs": {"messages": [{"role": "user", "content": "Hello"}]},
"outputs": {"text": "Hi"},
"usage": {"input_tokens": 12, "output_tokens": 7},
"custom_fields": {"account_id": "acct_123"}
}
]
}
Required fields:
| Field | Requirement |
|---|---|
observation_id or id
| A non-empty string. |
trace_id
| A non-empty string. |
operation
|
start, finish, or error.
|
kind or run_type
| One of the accepted observation kinds. |
status
| One of the accepted statuses. |
Validated optional fields:
| Field | Requirement |
|---|---|
start_time
| An ISO 8601 datetime. |
end_time
| An ISO 8601 datetime. |
event_version
|
An integer from 0 through 18446744073709551615. A string integer is also accepted.
|
Accepted kinds:
event, span, generation, agent, tool, chain, retriever, evaluator,
embedding, guardrail, graph, model, llm
Accepted statuses:
pending, running, success, ok, error, interrupted, cancelled
Example request
curl https://app.weavescope.com/api/v1/observations/batch \
-H "authorization: Bearer $WEAVESCOPE_API_KEY" \
-H "content-type: application/json" \
-d '{
"events": [
{
"observation_id": "018f5f2a-77a6-7bd4-bb58-df7b90f8e337",
"trace_id": "018f5f2a-77a6-7bd4-bb58-df7b90f8e337",
"operation": "finish",
"name": "manual.test",
"kind": "span",
"status": "success"
}
]
}'
Native responses
An accepted event returns 202 Accepted:
{
"status": "queued",
"accepted": 1,
"rejected": 0,
"results": [
{
"index": 0,
"id": "018f5f2a-77a6-7bd4-bb58-df7b90f8e337",
"status": "queued",
"raw_event_id": "..."
}
]
}
A partially accepted batch still includes an itemized results array. Rejected items contain status, code, and reason.
| Status | Meaning |
|---|---|
202 Accepted
| At least one event was accepted and queued. |
400 Bad Request
| No event was accepted or the request shape was invalid. |
401 Unauthorized
| The API key is missing or invalid. |
402 Payment Required
| The accepted-event limit was reached. |
403 Forbidden
| The API key cannot ingest. |
413 Content Too Large
| The request exceeds a size or event-count limit. |
429 Too Many Requests
| An hourly ingest or edge rate/concurrency limit was reached. |
503 Service Unavailable
| WeaveScope could not safely accept the payload. |
An hourly ingest-limit response includes hourly_ingest_reset_at. An edge 429 can use a different response body. Retry either case with backoff.
LangSmith-compatible runs
Create one run:
POST /api/v1/runs
Update one run:
PATCH /api/v1/runs/:id
Send a batch:
{
"post": [{"id": "run-id", "name": "root", "run_type": "chain"}],
"patch": [{"id": "run-id", "end_time": "2026-07-02T12:00:03Z"}]
}
Each run object must include id or observation_id.
OpenTelemetry traces
Send OTLP/HTTP traces to:
POST /api/public/otel/v1/traces
The standard OTLP/HTTP protobuf encoding uses:
content-type: application/x-protobuf
Protobuf JSON is also supported:
content-type: application/json
JSON requests must use the OTLP resourceSpans shape. Protobuf requests may use content-encoding: gzip; other encodings receive 415 Unsupported Media Type.
Both the transferred protobuf body and its decompressed value are limited to 25 MiB. A proxy may reject an oversized transferred body before it reaches WeaveScope, so that response body can differ from the OTLP response used for a decompressed-size rejection.
WeaveScope converts every OTLP span to a native observation. Span and resource attributes become metadata, with common fields such as input, output, llm.input_messages, llm.output_messages, error, and exception.message recognized automatically.
Original OTLP TraceId, SpanId, and parentSpanId values remain available as external identifiers. Internal observation IDs include both the trace ID and span ID, so equal span IDs in different traces remain distinct.
Status mapping:
-
STATUS_CODE_ERROR, or numeric code2, becomes an error observation. -
STATUS_CODE_OKbecomes a successful finished observation. -
STATUS_CODE_UNSET, or no status, becomes pending. It is treated as finished when the span has an end timestamp and started when it does not.
OTLP responses use OTLP shapes rather than the native JSON envelope:
| Case | HTTP status | Protobuf response | JSON response |
|---|---|---|---|
| All spans accepted, or the request is empty |
200 OK
|
Empty ExportTraceServiceResponse
|
{}
|
| Some or all spans rejected after a valid decode |
200 OK
|
ExportTraceServiceResponse.partial_success
|
{"partialSuccess": {"rejectedSpans": n, "errorMessage": "..."}}
|
| More than 1,000 spans |
413
|
google.rpc.Status with INVALID_ARGUMENT
|
{"code": 3, "message": "..."}
|
| Decompressed protobuf exceeds 25 MiB |
413
|
google.rpc.Status
| — |
| Invalid media type, encoding, or payload |
400 or 415
|
google.rpc.Status
|
{"code": 3, "message": "..."}
|
| Quota, idempotency, or persistence failure |
A meaningful status such as 402, 409, 429, or 503
|
google.rpc.Status
|
{"code": n, "message": "..."}
|
Custom fields
custom_fields is optional. When present, it must be a JSON object or null. You can provide it at:
-
The event root
-
metadata.custom_fields -
extra.metadata.custom_fields -
context_metadata.custom_fields
Missing and null values behave like an empty object. String, number, and boolean values are converted to strings. Nested objects, arrays, blank values, reserved keys, and secret-like keys are ignored.
Values merge in this order, with later values winning:
-
Context metadata
-
Effective metadata
-
Event-level custom fields
An array, string, number, or boolean in place of the object rejects that event with invalid_custom_fields. The reason identifies the invalid path without echoing its value. Other valid events in the request can still be accepted. OTLP reports the rejected spans through partial_success.
Payload size limits
Authenticated request bodies are limited to 25 MiB. Authentication happens before JSON decoding. For gzip-compressed OTLP protobuf, both the transferred and decompressed bodies must fit the limit.
Native batches, combined LangSmith batches, and OTLP trace requests can contain at most 1,000 events or spans. A larger request is rejected atomically with 413 Content Too Large; none of its events are counted, stored, or queued.
Within each event or span, the JSON encoding of each field below can be at most 4 MiB:
-
inputs -
outputs -
events -
metadata -
context_metadata
For LangSmith-compatible runs, extra.metadata counts as metadata. A field that is exactly 4 MiB is accepted. A larger field rejects only that event with payload_too_large; valid siblings can still be accepted. Accepted fields are stored in full and are never silently truncated.
When usage is counted
An event counts toward monthly usage after it passes validation and WeaveScope safely accepts it for processing. Rejected events do not count and do not consume hourly ingest capacity.
Processing happens after acceptance. If one accepted event later cannot be normalized, valid siblings can still appear, but the accepted event remains counted. Temporary processing failures are retried.
Hourly ingest usage counts accepted data in these fields:
-
inputs -
outputs -
metadata -
context_metadata -
events