Skip to content

Wire envelope spec

The three SDKs (Python, Node, Go) send the exact same JSON envelope to POST /api/v1/ai/ingest with Authorization: Bearer aiv_.... You can also POST directly without an SDK; the envelope is the contract.

POST /api/v1/ai/ingest
Host: api.monsys.ai
Authorization: Bearer aiv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
{
"trace_id": "uuid", // client-generated UUIDv4 (required)
"root_op": "rag.chat", // free-text label (required)
"status": "ok" | "error" | "partial", // (required)
"started_at": "2026-05-12T09:50:00Z", // RFC3339 (required)
"ended_at": "2026-05-12T09:50:01Z", // RFC3339 (optional)
"user_session_hash": "", // SHA256 of session id, optional
"sampling_decision": "full", // "full" | "head" | "summary"
"spans": [ ... ] // 1..N spans (required)
}
{
"span_id": "uuid", // UUIDv4 (required)
"parent_span_id": "uuid", // "" for root span
"op": "openai.chat", // free-text (required)
"provider": "openai", // canonical provider name
"model": "gpt-4o-mini", // model id
"started_at": "2026-05-12T09:50:00.001Z",
"ended_at": "2026-05-12T09:50:00.987Z",
"status": "ok", // "ok" | "error" | "refused" | "partial"
"input_tokens": 42,
"output_tokens": 17,
"prompt": "...", // raw — redacted on the hub
"completion": "...", // raw — redacted on the hub
"system_msg": "...",
"tool_io": "...", // tool call or function result
"attributes": { ... }, // optional top-level metadata
"events": [ { ... }, ... ] // optional event stream
}

An agent is more than prompt and completion. What a tool returns, which tool schema the model is shown and what it writes to memory are the channels through which indirect prompt injection, MCP tool poisoning and memory poisoning arrive. You record them as typed events on a span; the hub hashes and redacts the content like prompts, scans for deterministic injection indicators and tracks MCP schemas for drift. monsys blocks nothing (ADR-0009, ADR-0029).

"events": [
{ "kind": "tool_call", "name": "read_email", "args": { "folder": "inbox" }, "at": "2026-10-30T09:00:00Z" },
{ "kind": "tool_result", "name": "read_email", "result": "…text the model gets to see…" },
{ "kind": "mcp_schema", "server": "crm", "name": "delete_user",
"description": "Deletes a user by id", "input_schema": { "type": "object", "properties": { "id": { "type": "string" } } } },
{ "kind": "memory_write", "store": "vector", "key": "customer-404", "content": "…" }
]
kind required what the hub does
tool_call name, args hash + redaction; tool statistics
tool_result name, result hash + redaction; injection indicators
mcp_schema server, name, description, input_schema canonical hash; drift alert on change; indicators on the description
memory_write store, key, content hash + redaction; injection indicators

Indicators (regex, no model): ignore_previous, role_override, system_prompt_exfil, tool_invocation, secrecy, hidden_html, zero_width, markdown_exfil, shell_pipe, prompt_delimiters, base64_blob. A hit raises an ai_injection alert in the action inbox; a changed schema raises ai_mcp_drift and counts as configuration drift in the Trust Score until you confirm the change. Everything lands hashed in the evidence pack (agent_events.jsonl, mcp_schemas.json).

SDK helpers: Python span.tool_call(), span.tool_result(), span.mcp_schema(), span.memory_write(); Node span.toolCall() etc.; Go span.ToolCall() etc.

Provider provider value
OpenAI openai
Anthropic Claude anthropic
Google Gemini google
Mistral AI mistral
Azure OpenAI azure
Local (Ollama) ollama

Other providers are accepted but get no pricing lookup (cost = 0).

On ingest the hub looks up ai_model_pricing by (provider, model). Match is exact: new models not in the table get total_cost_micro_eur = 0. Request an update via info@be-hosted.be (pricing changelog page: coming soon).

The pricing row used is pinned per span via a pricing_snapshot_id FK: backfilling prices does not change historical costs.

{
"received": true,
"trace_id": "uuid",
"spans": 3,
"pii_hits": 1,
"total_cost_micro_eur": 603290
}

HTTP status:

  • 200: processed (see body)
  • 401: token invalid or revoked
  • 403: tenant has ai_observability_enabled = false
  • 400: invalid envelope (see error in body)
  • 5xx: hub-side error; the SDK does not retry (failures are silent)
Terminal window
curl -X POST https://api.monsys.ai/api/v1/ai/ingest \
-H "Authorization: Bearer aiv_xxxx" \
-H "Content-Type: application/json" \
-d '{
"trace_id": "0e2216d5-7b6d-448a-924c-c7a08b1a7e4a",
"root_op": "manual_test",
"status": "ok",
"started_at": "2026-05-12T09:50:00Z",
"ended_at": "2026-05-12T09:50:01Z",
"sampling_decision": "full",
"spans": [{
"span_id": "a1b2c3d4-e5f6-4789-9abc-def012345678",
"parent_span_id": "",
"op": "openai.chat",
"provider": "openai", "model": "gpt-4o-mini",
"started_at": "2026-05-12T09:50:00.001Z",
"ended_at": "2026-05-12T09:50:00.987Z",
"status": "ok",
"input_tokens": 22, "output_tokens": 12,
"prompt": "My IBAN is BE68 5390 0754 7034.",
"completion": "I cannot help with banking details.",
"attributes": {}, "events": []
}]
}'

We map OTel GenAI semantic conventions onto this envelope internally, but don’t accept OTLP directly yet. Reason: OTLP/HTTP binary overhead is overkill for one-call-per-trace. If you have an OTel-only stack, write a thin adapter from OTel span → our envelope (see envelope-from-otel.md: coming soon).