Handbook
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 directoryFEEDBACK360_REPORTS_ROOT— defaultreports_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). - 409 —
report/data.jsonorreport/metrics.jsonmissing.
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.jsonmissing, or verify verdict notpass
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
competenciesquery 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 existingpdp_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, orPdpError(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.jsondocument. - 409 — no active workspace, no session, or review step not confirmed
(
{"error": "review step not confirmed"}or similarPdpErrormessage).
Read finalized plan
GET /api/pdp
- 200 —
pdp.jsonbody. - 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>}; persistspdp_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
providerorapi_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
mockwhenFEEDBACK360_AI_MOCKis 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
}
status—idle(never run since server start),running,done,failed.- Section
status—pending,writing,ok,rejected(failed validation after retry),failed(run aborted mid-section).retriedmarks sections that needed a second attempt; rejected sections include areason. eta_seconds— average duration of completed sections × remaining work;nulluntil 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).
Related docs
- Engineering guide — CLI, pipeline stages, legacy
--serve - Studio app logic — state machines and gates behind these endpoints
- Data contract —
data.json/metrics.jsonschemas - Pipeline — architecture and verification harness
- AI providers — optional enrichment layer
- Personal Development Plan — interview stages, validation, schemas