Skip to documentation

Platform

Build reports

A report is the consulting deliverable: a client-ready snapshot of the knowledge base as of a moment in time — coverage, KPIs, top entities, gaps, and what to interview next — persisted immutably and re-readable months later, byte-for-byte the same. This guide generates a snapshot with one call and fetches it back as structured JSON.

Prerequisites

  • A key with reports:write (generate) and reports:read (list/fetch).
  • A workspace with some ingested documents — a report over an empty KB is a report about an empty KB.

For what each section means and how reproducibility stamping works, see Reports & PDF deliverables.

Generate a snapshot

Generating a report computes each section against the KB at that instant and persists the result, then returns the finished snapshot — coverage matrix, KPIs, and narrative summary all present in the 201. No model is invoked; the numbers come from the persisted graph, which is why the call is fast and repeatable. Those numbers are frozen at generation — a report you hand a client in July still reads the same in December, however much the KB has grown.

POST/api/v1/reportsBearer · reports:write

Generate an as-of report snapshot over the current KB. Returns 201 with the completed snapshot — coverage and summary are computed synchronously from the persisted graph, with no model call. The PDF render, if any, follows separately.

Request body

titlestringReport title, e.g. "Northwind — Q3 engagement report". Defaults to a workspace + date title.
kindstringengagement (default) or coverage. Chooses which snapshot the generator assembles.
Example requestbash
curl -s -L -X POST https://eli.ai/api/v1/reports \
  -H "Authorization: Bearer $ELI_KEY" \
  -H "Content-Type: application/json" \
  -d '{"title":"Northwind — Q3 engagement report"}'
Responsejson
{
  "id": "01KY13DDDDDDDDDDDDDDDDDDDD",
  "title": "Northwind — Q3 engagement report",
  "kind": "engagement",
  "status": "ready",
  "layoutVersion": 4,
  "createdBy": "01KY12CCCCCCCCCCCCCCCCCCCC",
  "generatedAt": "2026-07-21T12:00:00.000Z",
  "coverage": {
    "matrix": {
      "businessUnits": [
        "Operations"
      ],
      "topics": [
        "Compliance"
      ],
      "cells": [
        {
          "businessUnit": "Operations",
          "topic": "Compliance",
          "docCount": 8,
          "entityCount": 12,
          "medianAgeDays": 14,
          "zeroCoverage": false
        }
      ]
    },
    "summary": {
      "docs": 214,
      "entities": 182,
      "orphanEntities": 11,
      "stalenessMedianDays": 23,
      "totalInterviews": 18,
      "interviewsByBu": [
        {
          "businessUnit": "Operations",
          "count": 7,
          "lastInterviewAt": "2026-07-15T15:30:00.000Z"
        }
      ],
      "businessUnitCount": 1,
      "topicCount": 1,
      "generatedAt": "2026-07-21T12:00:00.000Z"
    },
    "interviewNext": [
      {
        "businessUnit": "Operations",
        "topic": "Compliance",
        "docCount": 8,
        "medianAgeDays": 14,
        "interviews": 7,
        "reasons": [
          "lowest document coverage"
        ],
        "label": "Operations · Compliance"
      }
    ]
  },
  "summary": {
    "kpis": {
      "docs": 214,
      "entities": 182,
      "relations": 388,
      "orphanEntities": 11,
      "stalenessMedianDays": 23,
      "totalInterviews": 18,
      "businessUnitCount": 1,
      "topicCount": 1,
      "coveredCells": 1,
      "zeroCoverageCells": 0,
      "coveragePct": 100
    },
    "topEntities": [
      {
        "entityId": "01KY0ZW3AAAAAAAAAAAAAAAAAA",
        "name": "Order Platform",
        "type": "system",
        "pagerank": 0.0421,
        "degree": 17,
        "communityId": 2
      }
    ],
    "gaps": {
      "total": 0,
      "items": []
    },
    "generatedAt": "2026-07-21T12:00:00.000Z",
    "layoutVersion": 4
  },
  "graphImage": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iLi4u"
}

Fetch it back

Fetch one snapshot by id to get its bodies back, or list the workspace's reports to get metadata rows. The two shapes differ on purpose: GET /reports never ships coverage or summary, so listing stays cheap however large the snapshots grow.

GET/api/v1/reports/{id}Bearer · reports:read

Fetch a report snapshot: its stamped metadata plus the computed coverage matrix and narrative summary. Workspace and filesystem fields are omitted — the key already selects the workspace.

Example requestbash
curl -s https://eli.ai/api/v1/reports/01KY13DDDDDDDDDDDDDDDDDDDD \
  -H "Authorization: Bearer $ELI_KEY"
Responsejson
{
  "id": "01KY13DDDDDDDDDDDDDDDDDDDD",
  "title": "Northwind — Q3 engagement report",
  "kind": "engagement",
  "status": "ready",
  "layoutVersion": 4,
  "createdBy": "01KY12CCCCCCCCCCCCCCCCCCCC",
  "generatedAt": "2026-07-21T12:00:00.000Z",
  "coverage": {
    "matrix": {
      "businessUnits": [
        "Operations"
      ],
      "topics": [
        "Compliance"
      ],
      "cells": [
        {
          "businessUnit": "Operations",
          "topic": "Compliance",
          "docCount": 8,
          "entityCount": 12,
          "medianAgeDays": 14,
          "zeroCoverage": false
        }
      ]
    },
    "summary": {
      "docs": 214,
      "entities": 182,
      "orphanEntities": 11,
      "stalenessMedianDays": 23,
      "totalInterviews": 18,
      "interviewsByBu": [
        {
          "businessUnit": "Operations",
          "count": 7,
          "lastInterviewAt": "2026-07-15T15:30:00.000Z"
        }
      ],
      "businessUnitCount": 1,
      "topicCount": 1,
      "generatedAt": "2026-07-21T12:00:00.000Z"
    },
    "interviewNext": [
      {
        "businessUnit": "Operations",
        "topic": "Compliance",
        "docCount": 8,
        "medianAgeDays": 14,
        "interviews": 7,
        "reasons": [
          "lowest document coverage"
        ],
        "label": "Operations · Compliance"
      }
    ]
  },
  "summary": {
    "kpis": {
      "docs": 214,
      "entities": 182,
      "relations": 388,
      "orphanEntities": 11,
      "stalenessMedianDays": 23,
      "totalInterviews": 18,
      "businessUnitCount": 1,
      "topicCount": 1,
      "coveredCells": 1,
      "zeroCoverageCells": 0,
      "coveragePct": 100
    },
    "topEntities": [
      {
        "entityId": "01KY0ZW3AAAAAAAAAAAAAAAAAA",
        "name": "Order Platform",
        "type": "system",
        "pagerank": 0.0421,
        "degree": 17,
        "communityId": 2
      }
    ],
    "gaps": {
      "total": 0,
      "items": []
    },
    "generatedAt": "2026-07-21T12:00:00.000Z",
    "layoutVersion": 4
  },
  "graphImage": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iLi4u"
}
GET/api/v1/reportsBearer · reports:read

List report snapshots for the workspace, newest first. Rows carry metadata only — listing stays cheap no matter how large the snapshots are.

Example requestbash
curl -s https://eli.ai/api/v1/reports \
  -H "Authorization: Bearer $ELI_KEY"
Responsejson
{
  "items": [
    {
      "id": "01KY13DDDDDDDDDDDDDDDDDDDD",
      "title": "Northwind — Q3 engagement report",
      "kind": "engagement",
      "status": "ready",
      "layoutVersion": 4,
      "createdBy": "01KY12CCCCCCCCCCCCCCCCCCCC",
      "generatedAt": "2026-07-21T12:00:00.000Z"
    }
  ],
  "total": 1
}

The PDF is a session download, not an API resource

/api/v1 serves the report as data — that is the form a program can diff, chart, or re-render. The rendered PDF is downloaded from the workspace UI, backed by the session-authenticated route GET /api/w/{workspaceId}/report/{id}/pdf (any member; owner/admin can trigger a render with ?render=1). A 404 with { "status": "not_rendered" } means no PDF exists yet — ask for a render, then re-request.

Rendering is serialized

Headless-Chromium PDF rendering runs on a concurrency-1 queue, so a burst of report generations queues rather than exhausting the box. The structured snapshot never waits on the PDF: POST /reports returns the computed sections immediately, and the render lands afterwards.

The leverage

One POST turns the live state of an engagement into a frozen, reproducible artifact — the paid deliverable — and because it comes back as structured JSON you can also drop the same numbers into your own dashboard, email, or Slack digest without re-deriving them. Schedule the call and a Monday-morning client report is rendered before anyone opens their inbox; every re-download of an old snapshot is identical, because the layout version that produced it is stamped in. See Scheduling, drift & alerts for the cron substrate.