Skip to content

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://api.venturi.systems; self-hosted, substitute your own instance host. See Deployment modes. All paths below are relative to that base.

Authentication

Send either header:

HTTP
Authorization: Bearer <jwt>
HTTP
X-API-Key: <key>

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:

Bash
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"
  }'

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:

Bash
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_ACCOUNT_ANTHROPIC, PROVIDER_ACCOUNT_OPENAI, PROVIDER_ACCOUNT_GOOGLE_CLOUD_VERTEX_AI, 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/v2.schema.json, additive over v1) that Venturi shares during onboarding. attribution_confidence, 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-Key protects 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 with Idempotency-Replayed: true; replaying it with a different body returns 422 Unprocessable Entity (IDEMPOTENCY_CONFLICT).
  • event_id protects the invocation. Venturi deduplicates the normalized invocation on event_id, so an event re-sent under a new Idempotency-Key is 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/problem+json per RFC 9457. For example:

JSON
{
  "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

Give feedback on this page

Sign in to send feedback. Opens in a new tab.

Copy page details
Docs feedback: Ingestion API
Page: https://docs.venturi.systems/ingestion/api/