Ingestion API¶
Emit AI inference events to Venturi with a single HTTP POST. Use this when your
code or pipeline already has a point that observes each AI call.
Base URL
Examples use the placeholder https://<your-venturi-instance>. On the
SaaS tier, substitute https://; self-hosted,
substitute your own instance host. See
Deployment modes. All paths below are
relative to that base.
Authentication¶
Send either header:
Venturi issues the credential during tenant provisioning. Both ingestion routes
require a valid credential issued to your tenant, and a missing or invalid
credential is rejected 401. The credential decides which tenant records the
event: a tenant identifier in the request body cannot override it. The closed
scope catalog has no ingestion scope, so these two
routes declare no catalog scope and accept any valid credential for your tenant.
Issue a dedicated API key to each ingestion pipeline so you can rotate or revoke
it without affecting your other integrations. An API key is long-lived until it
expires, is rotated, or is revoked, so give the ingestion key an expiry and
rotate it on your own schedule (see
API keys). Only the
health probes on this page accept unauthenticated calls,
and they return no tenant data.
POST /api/v1/invocations¶
Record one AI inference as an InvocationEvent. Returns 202 Accepted: the
event is queued for the attribution pipeline.
Minimal request¶
These fields are required by the controlled schema:
curl -X POST "https://<your-venturi-instance>/api/v1/invocations" \
-H "Content-Type: application/json" \
-H "X-API-Key: $VENTURI_API_KEY" \
-H "Idempotency-Key: 1d10d1ba-0407-4f3a-a5ee-1a00f40a6bf2" \
-d '{
"event_id": "8f3c2ac0-4dd7-49b3-b004-4f7b7d9c73b0",
"ingestion_layer": "OTEL",
"ingestion_timestamp": "2026-05-29T18:30:00Z",
"provider": "openai",
"requested_model": "gpt-4o"
}'
Recommended request¶
Add the fields that make attribution useful (tokens, cost, and the identity or
service responsible). This example records a second invocation, so it carries
its own event_id and Idempotency-Key:
curl -X POST "https://<your-venturi-instance>/api/v1/invocations" \
-H "Content-Type: application/json" \
-H "X-API-Key: $VENTURI_API_KEY" \
-H "Idempotency-Key: 9e77162e-a528-4bd9-93ac-4ac4e3655897" \
-d '{
"event_id": "ee2f716b-a59e-4a7a-8bce-59c90214094a",
"ingestion_layer": "OTEL",
"ingestion_timestamp": "2026-05-29T18:31:12Z",
"provider": "openai",
"requested_model": "gpt-4o",
"input_tokens": 1200,
"output_tokens": 350,
"estimated_cost_usd": 0.0123,
"latency_ms": 840,
"attributed_identity": "alice.chen",
"attributed_service": "checkout-api",
"trace_id": "4c77b7f064ab4d9bb68f163a9f0b31ce"
}'
Never send message content
The schema has no field for prompt or completion text, by design. Send metadata only: tokens, model, cost, identity, latency. Venturi’s pipeline does not store content.
Key fields¶
| Field | Required | Notes |
|---|---|---|
event_id |
✅ | UUID, unique per invocation; Venturi deduplicates the invocation on it. |
ingestion_layer |
✅ | Capture source from the schema’s closed set: PROXY, OTEL, BILLING, INFRA_METRICS, DATA_PLATFORM, SAAS_CONNECTOR, PROVIDER_, PROVIDER_, PROVIDER_, or FILE_IMPORT. Application code that emits its own events uses OTEL. |
ingestion_timestamp |
✅ | RFC 3339 / ISO 8601 UTC. |
provider |
✅ | openai, anthropic, bedrock, azure_openai, vertex_ai, and other provider names. |
requested_model |
✅ | The model you asked for. |
input_tokens |
Optional | Non-negative integers; drive token attribution. |
output_tokens |
Optional | Non-negative integers; drive token attribution. |
estimated_cost_usd |
Optional | JSON number, such as 0.0123, as the controlled schema types it. Venturi stores it as a fixed-precision decimal; the REST API returns every amount as a decimal string (see Reading attribution). |
attributed_identity |
Optional | Who or what made the call. |
attributed_service |
Optional | Who or what made the call. |
trace_id |
Optional | Correlate multi-step agent workflows. |
The full field set (cost breakdown, energy/carbon, Kubernetes and SPIFFE
identity, workflow correlation) is defined in the controlled schema: the
versioned InvocationEvent JSON Schema (invocationevent/, additive
over v1) that Venturi shares during onboarding.
attribution_, where present, is capped at 0.95; Venturi never
claims certainty.
Idempotency¶
Both ingestion routes, like every other mutating tenant data, product, and
partner API route, require a client-generated UUID or ULID in Idempotency-Key;
a POST without one returns 400. (The OAuth token endpoint, the drop-in
proxy routes, and the SCIM endpoints are the only exemptions; see the
REST API idempotency contract.) The two
identifiers protect different things:
Idempotency-Keyprotects the HTTP request. Venturi keeps the tenant-and-route-scoped key-to-result mapping for 24 hours. Replaying the same key with the same body returns the original status and result withIdempotency-Replayed: true; replaying it with a different body returns422 Unprocessable Entity(IDEMPOTENCY_).CONFLICT event_idprotects the invocation. Venturi deduplicates the normalized invocation onevent_id, so an event re-sent under a newIdempotency-Keyis accepted at the HTTP layer and still never produces a second invocation.
When you retry the same event, keep both values stable.
POST /api/v1/events¶
For generic observability events that are not a single model invocation. Same
authentication and Idempotency-Key rules; also returns 202 Accepted.
Responses¶
| Status | Meaning |
|---|---|
202 Accepted |
Event queued. |
400 Bad Request |
The Idempotency-Key header is missing. |
401 Unauthorized |
Missing or invalid credential. |
422 Unprocessable Entity |
Payload failed schema validation (check the required fields, the ingestion_layer value, and that estimated_cost_usd is a JSON number rather than a quoted string), or an Idempotency-Key was reused with a different body. |
429 Too Many Requests |
Rate limit exceeded. Honor Retry-After and retry with jitter while preserving both identifiers. |
All error responses use Content-Type: application/ per RFC 9457.
For example:
{
"type": "https://venturi.systems/problems/rate-limited",
"title": "Too Many Requests",
"status": 429,
"detail": "The tenant ingestion rate limit was exceeded.",
"instance": "/api/v1/invocations",
"trace_id": "9b2e4c61-3f7a-4d08-8e15-6a0c7d9f2b43",
"tenant_id": "tnt_01HF774RGJ2YZB7V8TNEXESE0V",
"error_code": "RATE_LIMITED",
"docs_url": "https://docs.venturi.systems/ingestion/api/"
}
Every authenticated response carries RateLimit-Limit, RateLimit-Remaining,
and RateLimit-Reset; a 429 also carries Retry-After. Limits are
tenant-specific. See Versioning & rate limits
for the lookup endpoint and header semantics.
Health endpoints¶
GET /healthz is an unauthenticated liveness probe. A 200 confirms that the
process is running; it does not prove dependencies or ingestion readiness.
GET /readyz is the unauthenticated readiness probe for deployment health
checks. It reports not ready when a dependency that a fail-closed surface needs,
such as the authentication store, is unavailable. When only a dependency on the
fail-open AI traffic path is impaired, it stays ready and reports the degraded
state, because traffic keeps flowing. Neither endpoint returns tenant data. On
the SaaS tier both probes report the shared API host rather than your dedicated
data plane (see
Tenant routing on the SaaS host).
Verifying it worked¶
After a successful 202, the event appears in the dashboard’s
Invocation feed (/invocations) within approximately 30 seconds. See
Verify & confirm.
Prefer not to touch application code? Use the drop-in proxy
Sign in to send feedback. Opens in a new tab.
Copy page details
Docs feedback: Ingestion API
Page: https://docs.venturi.systems/ingestion/api/