Capablio

360 Insights Studio — HTTP API

The Studio backend (python -m feedback360 --app) exposes a local JSON API for managing report workspaces, running the parse → analyze → render pipeline with verification, and exporting offline zip packs. It binds…

Updated

Mode Flag Default port
Legacy dynamic app --serve --out <dir> 8913
Studio API --app 8914

Health

GET /health
{
  "service": "feedback360-studio",
  "status": "ok",
  "version": "0.1.0",
  "active_workspace": "/home/user/Documents/360Reports/leonid-zvyahintsev-20260710-131500"
}

active_workspace is null when no workspace is loaded.

Settings

Settings persist in <config_dir>/settings.json (see User directories).

GET /api/settings
PUT /api/settings

Default shape:

{
  "schema_version": 1,
  "reports_root": "/home/user/Documents/360Reports",
  "ai": {"provider": null}
}

PUT merges into the stored object. reports_root must be an absolute path string (400 otherwise). Unknown keys under ai are preserved across read-modify-write cycles.

Environment overrides for tests:

  • FEEDBACK360_CONFIG_DIR — config directory
  • FEEDBACK360_REPORTS_ROOT — default reports_root

Workspaces

A workspace is one generated report run:

<reports_root>/<slug>/
├── sources/thomas.pdf          # copy of input Thomas PDF
├── sources/snapshot.pdf        # optional snapshot deck copy
├── report/
│   ├── data.json
│   ├── metrics.json
│   ├── 360-report.html
│   └── wiki/*.html
├── verify.json                 # extraction harness result
└── snapshot.zip                # created by export (after verify pass)

slug = <subject-slug>-<YYYYMMDD-HHMMSS> (subject from generate request, default report).

List workspaces

GET /api/workspaces

Returns a JSON array:

[
  {
    "path": "/home/user/Documents/360Reports/leonid-zvyahintsev-20260710-131500",
    "slug": "leonid-zvyahintsev-20260710-131500",
    "created": "2026-07-10T10:15:00+00:00",
    "has_report": true,
    "verify_status": "pass"
  }
]

Missing reports_root yields [].

Select active workspace

POST /api/workspaces/select
Content-Type: application/json

{"path": "/home/user/Documents/360Reports/my-run-20260710-120000"}
  • 200 — workspace loaded; legacy routes serve its report/ artifacts.
  • 404 — path is not a workspace (no sources/thomas.pdf).
  • 409report/data.json or report/metrics.json missing.

Generate job

POST /api/generate
Content-Type: application/json

{
  "thomas_path": "/path/to/thomas-360-report.pdf",
  "snapshot_path": "/path/to/snapshot-deck.pdf",
  "subject": "Leonid Zvyahintsev"
}
  • 202 {"job_id": "<hex>"} — background job started.
  • 400 — PDF path missing or not a file.

Poll progress:

GET /api/jobs/<job_id>
{
  "status": "running",
  "stages": [
    {"name": "parse", "status": "done", "detail": ""},
    {"name": "analyze", "status": "running", "detail": ""},
    {"name": "render", "status": "pending", "detail": ""},
    {"name": "verify", "status": "pending", "detail": ""}
  ],
  "error": null,
  "workspace": "/tmp/reports/leonid-zvyahintsev-20260710-131500"
}

Stage status values: pending, running, done, failed.

Job status: running, done, or failed.

On successful verify, the new workspace becomes the active workspace automatically. Verify failure marks the verify stage failed, job done with error set, but artifacts remain for inspection; export stays blocked.

Verify gate

After render, the job runs feedback360.verify_extraction (arithmetic + full PDF reconciliation against the copied Thomas PDF) and writes verify.json:

{
  "verdict": "pass",
  "checked_at": "2026-07-10T10:16:02.123456+00:00",
  "summary": {
    "statements": "54/54",
    "averages": 1296,
    "comments": 45,
    "distributions": 1080
  },
  "errors": []
}

Export requires verdict: "pass".

Export

POST /api/export
Content-Type: application/json

{}
  • 200 {"zip_path": "<workspace>/snapshot.zip"}
  • 409 — no active workspace, verify.json missing, or verify verdict not pass

Zip layout (flat pack, offline):

Entry Description
index.html Redirect to 360-report.html
360-report.html Interactive dashboard
data.json, metrics.json Structured data
wiki/ Reader HTML guides
pdp.json Finalized Personal Development Plan (included only when present)
pdp_checklist.json PDP execution checklist with per-item status (included only when present)
README.txt Pack instructions

PDP catalog

Static reads of the bundled catalog/pdp_catalog.json. These routes do not require an active workspace.

Full catalog

GET /api/pdp/catalog
  • 200 — full catalog JSON (same shape as catalog/pdp_catalog.json).
  • 500 — catalog failed validation at load time.

Suggest trainings and assignments

GET /api/pdp/catalog/suggest?competencies=PROVIDES%20VISION%20%26%20DIRECTION,DEVELOPS%20TALENT&budget=mid&fmt=online

Query parameters:

Param Required Description
competencies yes Comma-separated competency names (URL-encoded).
budget no COST_TIER_RANK key or any (default any).
fmt no Format substring filter (case-insensitive), e.g. online.
  • 200 — suggestion payload:
{
  "pathway": {"id": "PATH_90_DAY_CORE", "name": "90-day core pathway", "areas": ["..."]},
  "areas": [
    {
      "competency": "PROVIDES VISION & DIRECTION",
      "area": {"id": "AREA_01_VISION_DIRECTION", "name": "...", "priority": "critical"},
      "trainings": [{"id": "TRN_...", "title": "...", "provider": "...", "cost_tier": "mid", "format": "..."}],
      "assignments": [{"id": "ASN_...", "title": "...", "duration": "...", "difficulty": "medium"}]
    }
  ]
}

pathway is null when no pathway overlaps the assigned areas. Each area lists up to 8 trainings and 8 assignments after filtering.

  • 400 — missing or empty competencies query parameter ({"error": "competencies query parameter is required"}).
  • 500 — catalog load/validation failure.

PDP

Personal Development Plan interview endpoints. All require an active workspace; otherwise 409 {"error": "no active workspace"}.

Provider for AI-assisted goal drafting and custom-action review uses the same in-memory credentials as /api/ai/*. When no provider is configured, the interview uses deterministic templates and unreviewed custom actions.

Start session

POST /api/pdp/session/start
Content-Type: application/json

{}

Plain mode (default): body {} or omit "catalog". Same seven-stage flow as before — no constraints, pathway, training-*, or sourcing-* steps.

Catalog mode: pass {"catalog": true} to source formal trainings and on-the-job assignments from the bundled catalog after the focus step (see pdp.md).

POST /api/pdp/session/start
Content-Type: application/json

{"catalog": true}
  • 200 — fresh session dict (steps, cursor, answers, validations, …). Overwrites any existing pdp_session.json. Catalog sessions include "catalog_mode": true.
  • 409 — no active workspace.

Example response (truncated):

{
  "schema_version": 1,
  "status": "active",
  "created_at": "2026-07-10T12:00:00+00:00",
  "catalog_mode": true,
  "steps": [{"id": "orient", "stage": 1, "kind": "confirm", "prompt": "..."}],
  "answers": {},
  "validations": {},
  "cursor": "orient"
}

Get session

GET /api/pdp/session
  • 200 — current session dict.
  • 404{"error": "no PDP session"}.
  • 409 — no active workspace.

Submit answer

POST /api/pdp/session/answer
Content-Type: application/json

{"step_id": "orient", "answer": {"choice": "yes"}}
  • 200{"validation": {...}, "next_step": <step|null>, "done": bool}.
  • 400 — missing step_id/answer, or PdpError (wrong step, unknown step).
  • 404 — no session file.
  • 409 — no active workspace.

Example validation responses:

{"validation": {"status": "accepted"}, "next_step": {"id": "blindspot-1"}, "done": false}
{
  "validation": {
    "status": "pushback",
    "contested": true,
    "message": "Evidence shows self 6.0/7 vs others 5.2/7 ...",
    "evidence": []
  },
  "next_step": {"id": "focus"},
  "done": false
}
{
  "validation": {
    "status": "needs_override",
    "message": "No top-3 CTA competency selected — provide an override reason."
  },
  "next_step": null,
  "done": false
}

Finalize plan

POST /api/pdp/finalize
Content-Type: application/json

{}
  • 200 — finalized pdp.json document.
  • 409 — no active workspace, no session, or review step not confirmed ({"error": "review step not confirmed"} or similar PdpError message).

Read finalized plan

GET /api/pdp
  • 200pdp.json body.
  • 404{"error": "no finalized PDP"}.
  • 409 — no active workspace.

Read checklist

GET /api/pdp/checklist
  • 200{"checklist": <pdp_checklist.json>, "progress": <rollup>}.
  • 404{"error": "no PDP checklist"} (plan not finalized yet).
  • 409 — no active workspace.

Example:

{
  "checklist": {
    "schema_version": 1,
    "subject": "Leonid Zvyahintsev",
    "items": [{"id": "goal:provides-vision-direction", "status": "todo", "kind": "goal"}]
  },
  "progress": {
    "total": 14,
    "done": 2,
    "in_progress": 1,
    "todo": 11,
    "by_area": [{"area": "PROVIDES VISION & DIRECTION", "total": 9, "done": 1}]
  }
}

Update checklist item

POST /api/pdp/checklist/item
Content-Type: application/json

{"id": "goal:provides-vision-direction", "status": "in_progress"}

status must be todo, in_progress, or done.

  • 200{"item": <updated item>, "progress": <rollup>}; persists pdp_checklist.json.
  • 400 — missing id/status, or invalid status ({"error": "status must be one of ['done', 'in_progress', 'todo']"}).
  • 404 — no checklist file ({"error": "no PDP checklist"}), or unknown id ({"error": "unknown checklist item: <id>"}).
  • 409 — no active workspace.

Legacy routes (active workspace)

When a workspace is active, these mirror --serve behavior against <workspace>/report/:

Route Method Description
/ GET Dynamic app page (render_app_page)
/report GET Static 360-report.html
/wiki/<page> GET Wiki HTML (index.html, …)
/api/data GET Raw data.json
/api/metrics POST Recomputed metrics (live controls)

Without an active workspace: / returns a minimal page (id="no-workspace"); other routes return 409 JSON {"error": "no active workspace"}.

AI enrichment (optional)

AI narratives are optional. The core report works with no provider configured. See AI providers for key handling and validation.

Status

GET /api/ai/status
{
  "provider": "openai",
  "model": "gpt-4o-mini",
  "configured": true,
  "cursor_detected": false
}

configured is true when build_provider would succeed with current settings and in-memory credentials. cursor_detected uses shutil.which("cursor-agent").

Session credentials

POST /api/ai/credentials
Content-Type: application/json

{"provider": "openai", "api_key": "sk-..."}
  • 200 {"stored": true} — key held in memory only; never written to disk.
  • 400 — missing provider or api_key.

Test provider

POST /api/ai/test
Content-Type: application/json

{"provider": "openai", "model": "gpt-4o-mini"}
  • 200 {"ok": true|false, "message": "..."} — transport errors are results, not HTTP errors.
  • 400 — unknown provider (including mock when FEEDBACK360_AI_MOCK is unset).

Uses provider from body or settings plus session credentials.

Run enrichment

POST /api/ai/enrich
Content-Type: application/json

{"sections": ["summary", "gaps"]}

sections is optional; default is all seven narrative sections.

  • 200 — enrichment object (same shape as ai_enrichment.json).
  • 400 — no provider configured or invalid sections.
  • 409 — no active workspace.

Writes <workspace>/report/ai_enrichment.json and re-renders 360-report.html. data.json and metrics.json bytes are never modified.

Only one enrichment run may be active at a time; a second POST /api/ai/enrich while one is running returns 409 {"error": "an enrichment run is already in progress"}.

Enrichment progress

While POST /api/ai/enrich is in flight (the server is threaded), clients can poll live progress:

GET /api/ai/enrich/progress
{
  "status": "running",
  "provider": "cursor-agent",
  "model": "composer-2.5",
  "sections": [
    {"name": "summary", "label": "Executive summary", "status": "ok", "retried": false},
    {"name": "matrix", "label": "Importance matrix", "status": "writing", "retried": false},
    {"name": "gaps", "label": "Self-awareness gaps", "status": "pending", "retried": false}
  ],
  "sections_total": 7,
  "sections_done": 1,
  "percent": 21,
  "elapsed_seconds": 12.4,
  "eta_seconds": 68.0,
  "tokens_in": 4210,
  "tokens_out": 380,
  "error": null
}
  • statusidle (never run since server start), running, done, failed.
  • Section statuspending, writing, ok, rejected (failed validation after retry), failed (run aborted mid-section). retried marks sections that needed a second attempt; rejected sections include a reason.
  • eta_seconds — average duration of completed sections × remaining work; null until the first section completes and after the run ends.
  • tokens_in / tokens_out — rough estimates (~4 chars per token) of prompt and response volume; providers in this integration expose no exact usage.

Read enrichment

GET /api/ai/enrichment
  • 200 — current active workspace enrichment JSON.
  • 404 — no enrichment file for the active workspace.
  • 409 — no active workspace.

User directories

Platform Config dir
Linux $XDG_CONFIG_HOME/feedback360-studio or ~/.config/feedback360-studio
macOS ~/Library/Application Support/feedback360-studio
Windows %APPDATA%\feedback360-studio

Default reports_root: ~/Documents/360Reports when ~/Documents exists, else ~/360Reports (created on first generate).