Skip to content

Your API

Your API is Mibo’s canonical trace shape, posted as plain JSON. It’s the path the n8n Testing Node and the Flowise template use under the hood, and the right choice for any hand-rolled integration — curl, your own code, or a stack on a managed host with no OTel access.

If your stack already emits OpenTelemetry, use OTLP instead — fewer moving parts.

{
"spans": [
{
"span_id": "a3f1...",
"parent_span_id": null,
"name": "Restaurant Reservations Agentflow",
"start_time_unix_nano": "1717689600000000000",
"end_time_unix_nano": "1717689602450000000",
"attributes": {
"gen_ai.response.text": "Your table for 4 is booked at 8pm.",
"gen_ai.usage.input_tokens": 120,
"gen_ai.usage.output_tokens": 80,
"http.response.status_code": 200
},
"status": { "code": 1 }
}
],
"metadata": {
"environment": "production",
"version": "1.2.0"
}
}

Posted to POST /public/traces. The body has spans at the top level — there is no data wrapper.

POST https://api.mibo-ai.com/public/traces
HeaderRequiredPurpose
x-api-keyYesYour Mibo API key. See API Keys & Trace Routing.
Content-TypeYesapplication/json
x-request-idRecommendedTrace’s external identifier — Mibo upserts by this. See Identity.
Content-EncodingNogzip for compressed bodies. Useful for large traces.
FieldRequiredDescription
spansYesArray of canonical spans. See Span shape.
metadataNoFree-form key-value object stored with the trace. Set metadata.mibo.platform_id to route to a specific agent.
externalMetadataNoFree-form context only Mibo’s API sees (never round-tripped to the runner).
platformIdNoExplicit agent UUID. Equivalent to metadata.mibo.platform_id at the top level.

Identity is header-onlyexternalId is read from x-request-id, never from the body.

interface CanonicalSpan {
span_id: string; // unique within the trace
parent_span_id?: string | null; // null/omit for root
name: string; // user-facing display label — see below
attributes?: Record<string, unknown>;
start_time_unix_nano?: string; // string-encoded uint64 nanoseconds
end_time_unix_nano?: string;
status?: { code?: number; message?: string }; // 1 = OK, 2 = ERROR
events?: unknown[]; // OTel-style span events; usually empty
}

span.name is the user-facing display label of the step — the label you and your end-users see in your tool (n8n workflow node display name, Flowise component label). Never the internal id, never the technical class name.

The node_call assertion does case-insensitive substring matching against span.name, so what your user sees in their tooling is what they write in their assertions.

Wire the parent graph from your tool’s execution graph. The root span has parent_span_id: null (or omit the field). Children point at their parent’s span_id. Mibo derives the trace’s start/end timing, http status, and aggregate token usage from the root span when available.

If a span is captured but its real parent was filtered out, point it at the nearest captured ancestor instead — don’t leave orphans.

  • 1 — span succeeded.
  • 2 — span errored. Mibo marks the whole trace failed if any span has status.code === 2.
  • Omitted / 0 — unset; treated as success.

These are the GenAI / HTTP semconv keys Mibo evaluates. Emit what you have; missing keys surface as missing instrumentation in the dashboard rather than silent pass/fail.

AttributeWhat it powers
gen_ai.output.messagesPreferred response source — [{ role, parts: [{ type, content }] }]. The last assistant message’s text parts are concatenated.
gen_ai.response.textFallback response source. Resolution: root → latest AI-orchestrator span → latest span overall.
gen_ai.input.messagesUser input for semantic evaluators. Latest user message wins; falls back to system, then concatenated parts. Read from the AI span.
gen_ai.promptFallback input source when input.messages is absent.
gen_ai.system / gen_ai.request.modelMarks the span as the AI orchestrator (biases response/input extraction toward it).
gen_ai.usage.input_tokens / gen_ai.usage.output_tokens / gen_ai.usage.reasoning.output_tokenstoken_limit. Read from the root, summed across descendants if absent.
gen_ai.tool.name + gen_ai.tool.call.argumentsThe span is attached to its parent’s tool_calls. Arguments may be JSON-stringified.
http.response.status_codehttp_status. Read from the root, falls back to any descendant.

You can also send your own attributes for organizational purposes (flowise.component.type, n8n.node.type, etc.). The dashboard renders them generically; assertions ignore them unless you reference them explicitly.

Mibo reads externalId from the x-request-id HTTP header:

  • Present → create-or-replace by that id. Posting again with the same x-request-id overwrites the previous trace.
  • Absent → server-generated UUID. Always creates a new trace; no idempotency.

The n8n Testing Node sets x-request-id to the n8n execution id. The Flowise template sets it to the chatflow run id. Body fields never carry identity.

The trace is matched to a Mibo agent in this order:

  1. If the API key is scoped to a single agent, that agent is used.
  2. Otherwise the request must include platformId at the top level or metadata.mibo.platform_id.
  3. If neither resolves, the request returns 400 CUSTOM_API_PLATFORM_NOT_FOUND with a hint.

OTLP uses a different rule — it routes by the service.name resource attribute matched against an agent’s OTLP service name field, and ignores mibo.platform_id.

Unlike OTLP (where exporters batch partial spans across multiple POSTs), Your API ingestion is one POST carrying every span. Build the full payload synchronously, then post once. Resending with the same x-request-id replaces the previous trace — there is no merge-by-span_id on this path.

  • 201 Created or 200 OK — trace accepted. Passive evaluation is queued automatically.
  • 400 VALIDATION_ERROR — your payload doesn’t match the Your API shape or is missing required fields (span_id, name, spans). Check the error details and retry with a corrected payload.
  • 400 CUSTOM_API_PLATFORM_NOT_FOUND — your API key doesn’t have access to the specified platform, or your request didn’t specify one. Verify your platformId and API key scope.
  • 401 UNAUTHORIZED — API key is missing, invalid, revoked, or expired. Check your credentials.
  • 413 PAYLOAD_TOO_LARGE — your trace exceeds 30 MB decompressed. Reduce batch size or filter verbose attributes.
  • 500+ errors — transient server issue. Retry with exponential backoff (e.g., wait 1s, then 2s, then 4s). Do not treat these as validation errors; your payload may be correct.

Include the x-request-id header in all requests to make trace submission idempotent. If your network request times out or you’re unsure whether it succeeded:

  • Resend with the same x-request-id — Mibo will update the existing trace instead of creating a duplicate. No harm done.
  • Without x-request-id — every POST creates a new trace, even if it’s the same data. If you retry after a network failure, you’ll get two traces.

This is critical for systems that might retry on error (use the same x-request-id as the original failed request).

For large traces, send Content-Encoding: gzip with a gzip-compressed body. Mibo will decompress it before processing. Still subject to the 30 MB decompressed limit.

Terminal window
curl -X POST https://api.mibo-ai.com/public/traces \
-H "x-api-key: $MIBO_API_KEY" \
-H "Content-Type: application/json" \
-H "Content-Encoding: gzip" \
--data-binary @payload.json.gz
Terminal window
curl -X POST https://api.mibo-ai.com/public/traces \
-H "Content-Type: application/json" \
-H "x-api-key: $MIBO_API_KEY" \
-H "x-request-id: chat-001" \
-d '{
"spans": [
{
"span_id": "root-001",
"parent_span_id": null,
"name": "Restaurant Reservations Agentflow",
"start_time_unix_nano": "1717689600000000000",
"end_time_unix_nano": "1717689602450000000",
"attributes": {
"gen_ai.response.text": "Your table for 4 is booked at 8pm.",
"gen_ai.usage.input_tokens": 120,
"gen_ai.usage.output_tokens": 80,
"http.response.status_code": 200
},
"status": { "code": 1 }
},
{
"span_id": "tool-001",
"parent_span_id": "root-001",
"name": "create_booking",
"attributes": {
"gen_ai.tool.name": "create_booking",
"gen_ai.tool.call.arguments": "{\"date\":\"2026-06-07\",\"time\":\"20:00\",\"party\":4}"
},
"status": { "code": 1 }
}
],
"metadata": { "environment": "production" }
}'

A 2xx response means the trace was accepted; passive evaluation runs in the background against your active test cases.

Building canonical payloads from common stacks

Section titled “Building canonical payloads from common stacks”

You probably don’t need to write the JSON by hand:

For anything else, just construct the payload in your code and POST it. The shape is small enough to write inline.

The endpoint returns 400 for:

  • Missing or empty spans.
  • Span without span_id or name.
  • Body that’s neither OTLP nor Your API.
  • Multi-agent API key with no platformId / metadata.mibo.platform_id.

Legacy proprietary shapes ({ NodeName: { output, ... } }, agentFlowExecutedData, “n8n trace push”, etc.) are not accepted. Migrate to Your API.