# Dimies — conversation analytics for AI agents # Integration spec for AI coding agents and developers. # Base URL of this instance: https://app.dimies.com ## What Dimies does Dimies ingests the conversations end users have with a product's AI agent/chatbot and produces product analytics: automatic topic discovery, resolution/containment rates, sentiment & frustration signals, out-of-scope request reporting, a safety feed (prompt injection, jailbreaks, abuse, data extraction — with verbatim evidence), and per-conversation execution traces (spans with latency/tokens/cost). ## Authentication Every request needs an API key (created in the Dimies dashboard under Settings): Authorization: Bearer dm_live_... The integrating app should read it from the DIMIES_API_KEY environment variable and the instance URL from DIMIES_URL. Never hardcode keys. ## Integration option A — ingest API (recommended) POST https://app.dimies.com/api/v1/conversations Content-Type: application/json Send the FULL conversation so far each time. Repeated calls with the same conversation_id REPLACE previous state (idempotent upsert) — no diffing, no message IDs. Call it after each completed exchange, or once when the conversation ends. Fire-and-forget: never block or throw on the product's response path because of analytics. Body schema: { "conversation_id": string, // your ID; upsert key (optional; derived if omitted) "user_id": string, // your end-user ID (optional) "channel": string, // e.g. "web", "whatsapp", "voice" (optional) "closed": boolean, // true => analyze immediately (optional; otherwise // analysis runs after ~10 min of inactivity) "metadata": object, // anything to keep (optional) "messages": [ // 1..500 items, required { "role": "user"|"assistant"|"system"|"tool", "content": string, "timestamp": number|string } // epoch ms or ISO-8601 (optional) ], "trace": [ Span, ... ] // optional execution spans, max 200 (see below) } Span schema (all fields optional except name): { "id": string, // STRONGLY RECOMMENDED: a stable id you control // (e.g. "llm-1", "tool-refund-1"). It is the merge // key; children reference it via parent_id. "parent_id": string, "name": string, // e.g. "openai.chat.completions", "search-kb" "type": "llm"|"tool"|"retrieval"|"function"|"other", "started_at": number|string, "ended_at": number|string, "input": any, "output": any, // the payload — see PAYLOAD below "model": string, "tokens_in": number, "tokens_out": number, "cost": number, "status": "ok"|"error", "error": string, "metadata": object } PAYLOAD — what shows up when you click a span: The span detail panel shows "input", "output", "error" and "metadata". If all four are empty it reads "No payload recorded for this span." To make a span useful, ALWAYS attach the text: - llm span: input = the full prompt/messages sent to the model, output = the model's completion. Also set model/tokens/cost. - tool span: input = the arguments, output = the result (or error). - retrieval span: input = the query, output = the documents/snippets returned. Values can be strings or objects (objects are JSON-stringified, stored as text, truncated at 20k chars). Field names are EXACT. Unknown keys are silently ignored — a common mistake is sending "prompt"/"completion"/"response"/"messages" instead of "input"/"output"; those are dropped and the panel shows no payload. Use "input" and "output". Traces MERGE (they do NOT replace): each span upserts by "id". Sending spans incrementally (each turn) or resending the full trace every time are both idempotent — earlier spans are never deleted. Field-level merge: a resend updates ONLY the fields you include. Omitting a field keeps its previously stored value — so resending {id, status:"error"} to mark a failure will NOT wipe the input/output/model you sent earlier. To clear a field, send it explicitly as null. ALWAYS give each span a stable "id": - lets you update a span later by resending the same id (e.g. create the span when a tool starts, then resend {id, status:"error", error:"..."} on failure); - guarantees no duplicates when you resend. If "id" is omitted, spans are de-duped by a fingerprint of name + timestamps, so a full-trace resend with omitted ids AND regenerated timestamps would accumulate duplicates. Assigning ids avoids this entirely. Full llm span example: { "id": "llm-1", "parent_id": "root", "name": "vera.llm", "type": "llm", "model": "gpt-4o", "tokens_in": 1240, "tokens_out": 85, "cost": 0.0017, "started_at": 1720000001000, "ended_at": 1720000003900, "input": "System: You are Vera...\nUser: How do I reset my password?", "output": "Go to Settings -> Security -> Reset password." } Success response: { "ok": true, "conversation_id": "...", "status": "open"|"pending" } Errors: 401 invalid key; 400 with { error, details } on validation failure. Example: curl -X POST https://app.dimies.com/api/v1/conversations \ -H "Authorization: Bearer $DIMIES_API_KEY" \ -H "Content-Type: application/json" \ -d '{"conversation_id":"chat_1","user_id":"u_1","closed":true, "messages":[{"role":"user","content":"How do I reset my password?"}, {"role":"assistant","content":"Settings -> Security -> Reset."}]}' ## Integration option B — zero-code LLM proxy Swap the LLM client base URL; conversations are captured automatically (streaming supported; the provider API key passes through untouched): OpenAI: baseURL = https://app.dimies.com/api/v1/proxy/openai/v1 Anthropic: baseURL = https://app.dimies.com/api/v1/proxy/anthropic Proxy upstreams supported today: OpenAI and Anthropic ONLY. Other OpenAI-compatible providers (Groq, Together, OpenRouter, LiteLLM, Ollama, Moonshot/Kimi) are coming soon — for those, use option A (ingest API + trace) for now. Required header on the client: x-dimies-key: dm_live_... Optional headers: x-dimies-conversation-id (group multi-turn chats), x-dimies-user-id, x-dimies-channel. Each captured call also records an "llm" span (latency, tokens, model). Trade-off: the proxy sits on the request path; option A is fully out-of-band. ## Recipe for AI coding agents 1. Locate where the app exchanges messages with its LLM / serves the agent. 2. Add a small fire-and-forget helper (or run: npx dimies init) and call it with the full message list after each exchange; pass conversation_id and user_id from the app's own session/user identifiers; set closed: true when the conversation ends if known. 3. Wrap LLM/tool/retrieval steps as trace spans when the call sites are obvious. Give EVERY span a stable "id" (e.g. "llm-1", "tool--1") — it is the merge key; without it, repeated sends can duplicate spans. 4. Add DIMIES_API_KEY and DIMIES_URL to .env.example. 5. Verify with: npx dimies test --key $DIMIES_API_KEY --url https://app.dimies.com or check https://app.dimies.com/app after triggering one conversation. ## CLI npx dimies init|test|prompt|spec (package: "dimies" on npm) ## Dashboard https://app.dimies.com/app — topics, outcomes, safety feed, out-of-scope report, traces.