Skip to main content
Version: 0.30.0

API Reference

Base URL: http://localhost:8080 (direct) or http://localhost:3000/api (via dashboard proxy)

All endpoints return JSON. Error responses use {"error": "message"}.

Health​

GET /health​

Liveness probe. Returns immediately, does not check dependencies.

curl http://localhost:8080/health
{"status": "ok"}

GET /health/ready​

Readiness probe. Checks MongoDB connectivity.

curl http://localhost:8080/health/ready
{"status": "ok", "services": {"mongodb": {"status": "ok"}}}

Returns 503 (with "status": "degraded" and per-service errors under services) if a dependency is unreachable.


System​

GET /api/v1/system​

Component inventory for the running deployment — the services and the in-process workers that ride inside them, each with the version it reports. The list is built from a registry, not hardcoded, so additional components registered by a build appear automatically.

curl http://localhost:8080/api/v1/system
{
"data": {
"components": [
{"name": "API", "kind": "service", "version": "0.10.0", "commit": "a1b2c3d", "build_date": "2026-06-01T12:00:00Z"},
{"name": "Agent", "kind": "service", "version": "0.10.0", "note": "agent binary bundled in the API image, run as a subprocess; shares the API image version"},
{"name": "Schema indexing", "kind": "worker", "version": "0.10.0", "runs_in": "API", "note": "runs in-process inside the API service; shares its image version"},
{"name": "Validation jobs", "kind": "worker", "version": "0.10.0", "runs_in": "API", "note": "runs in-process inside the API service; shares its image version"}
]
}
}

Each component carries:

  • name — human-facing component name.
  • kind — service (a deployable image) or worker (a background loop running inside a parent service).
  • version — the reported version. For a worker this is its parent image's version. For the Agent it depends on RUNNER_MODE: in kubernetes mode it is the tag of the configured agent image (AGENT_IMAGE); in subprocess mode (the default) it is the bundled agent binary's version (the same as the API's), since AGENT_IMAGE is not used.
  • commit, build_date — git commit and build timestamp, when stamped at build time (see Configuration → Build metadata).
  • runs_in — for a worker, the parent service it runs inside.
  • note — clarification, e.g. that a worker shares its parent image's version, or that the Agent runs as a Job rather than a live service.

Only components that are actually running are listed: the Schema indexing worker appears only when vector search (Qdrant) is configured, since it does not start otherwise.

The version values are injected at image build time via -ldflags; an un-stamped local build reports the source-tree default (0.4.0-dev). The Dashboard surfaces this inventory on its System page, adding its own build version (which the API cannot know, as the dashboard is a separate image).


Providers​

GET /api/v1/providers/llm​

List registered LLM providers with metadata and config fields.

curl http://localhost:8080/api/v1/providers/llm
{
"data": [
{
"id": "claude",
"name": "Claude (Anthropic)",
"description": "Anthropic Claude API - direct access",
"config_fields": [
{"key": "model", "label": "Model", "required": true, "type": "string", "default": "claude-sonnet-4-6"}
],
"auth_methods": [
{
"id": "api_key",
"name": "API Key",
"description": "Anthropic Claude API key.",
"fields": [
{"key": "credentials_json", "label": "API Key", "required": true, "type": "credential", "placeholder": "sk-ant-..."}
]
}
],
"models": [
{"id": "claude-opus-4-7", "display_name": "Claude Opus 4.7", "wire": "anthropic", "max_output_tokens": 128000, "pricing": {"input_per_million": 5.0, "output_per_million": 25.0}},
{"id": "claude-sonnet-4-6","display_name": "Claude Sonnet 4.6","wire": "anthropic", "max_output_tokens": 64000, "pricing": {"input_per_million": 3.0, "output_per_million": 15.0}},
{"id": "claude-haiku-4-5", "display_name": "Claude Haiku 4.5", "wire": "anthropic", "max_output_tokens": 64000, "pricing": {"input_per_million": 1.0, "output_per_million": 5.0}}
],
"default_max_output_tokens": 16384,
"supports_tools": true
}
]
}

GET /api/v1/projects/{id}/llm/extended-models​

List project-scoped LLM model entries contributed by any registered external model registry (see Plugin hooks: External model registry). Returns an empty array when no external model registry extender is registered. Each entry has the same shape as a model row inside GET /api/v1/providers/llm, so the dashboard's model picker can merge both lists without reshaping.

curl http://localhost:8080/api/v1/projects/proj-123/llm/extended-models
{
"data": [
{
"id": "ext:my-custom-model",
"display_name": "My Custom Model",
"wire": "openai-compat",
"max_output_tokens": 4096,
"max_input_tokens": 32000,
"input_price_per_million": 1.5,
"output_price_per_million": 7.5
}
]
}

GET /api/v1/providers/warehouse​

List registered warehouse providers with metadata and config fields.

curl http://localhost:8080/api/v1/providers/warehouse
{
"data": [
{
"id": "bigquery",
"name": "Google BigQuery",
"description": "Google Cloud BigQuery data warehouse",
"dialect": "BigQuery Standard SQL",
"config_fields": [
{"key": "project_id", "label": "GCP Project ID", "required": true, "type": "string"},
{"key": "location", "label": "Location", "type": "string", "default": "US"},
{"key": "dataset", "label": "Default Dataset", "type": "string"}
]
}
]
}

Domains​

GET /api/v1/domains​

List available domains with their categories.

curl http://localhost:8080/api/v1/domains
{
"data": [
{
"id": "gaming",
"categories": [
{"id": "match3", "name": "Match-3", "description": "Puzzle games with match-3 mechanics (e.g., Candy Crush, Homescapes)"},
{"id": "idle", "name": "Idle / Incremental", "description": "Games focused on resource accumulation, prestige cycles, and offline progression"},
{"id": "casual", "name": "Casual / Hyper-Casual", "description": "Simple, accessible games with short sessions and broad appeal, often ad-monetized"}
]
},
{
"id": "social",
"categories": [
{"id": "content_sharing", "name": "Content Sharing", "description": "Platforms focused on creating and sharing content — photos, videos, stories"}
]
}
]
}

GET /api/v1/domains/{domain}/categories/{category}/schema​

Get profile JSON Schema for a domain/category. Used by the dashboard to render dynamic forms.

curl http://localhost:8080/api/v1/domains/gaming/categories/match3/schema

Returns a JSON Schema object defining the profile fields (basic_info, gameplay, monetization, boosters, etc.).

GET /api/v1/domains/{domain}/categories/{category}/areas​

Get analysis areas for a domain/category.

curl http://localhost:8080/api/v1/domains/gaming/categories/match3/areas
{
"data": [
{"id": "churn", "name": "Churn Risks", "description": "Players at risk of leaving the game", "keywords": ["churn", "retention", "cohort"], "is_base": true, "priority": 1},
{"id": "engagement", "name": "Engagement Patterns", "is_base": true, "priority": 2},
{"id": "monetization", "name": "Monetization Opportunities", "is_base": true, "priority": 3},
{"id": "levels", "name": "Level Difficulty", "is_base": false, "priority": 4},
{"id": "boosters", "name": "Booster Usage", "is_base": false, "priority": 5}
]
}

Projects​

POST /api/v1/projects​

Create a new project.

curl -X POST http://localhost:8080/api/v1/projects \
-H "Content-Type: application/json" \
-d '{
"name": "Puzzle Quest Analytics",
"description": "Match-3 puzzle game analytics",
"domain": "gaming",
"category": "match3",
"warehouse": {
"provider": "bigquery",
"project_id": "my-gcp-project",
"datasets": ["analytics_data", "features"],
"location": "US",
"filter_field": "app_id",
"filter_value": "my-app-123"
},
"llm": {
"provider": "claude",
"model": "claude-sonnet-4-6",
"config": {}
}
}'
{
"data": {
"id": "507f1f77bcf86cd799439011",
"name": "Puzzle Quest Analytics",
"domain": "gaming",
"category": "match3",
"status": "active",
"warehouse": { "provider": "bigquery", "datasets": ["analytics_data"], "location": "US", "config": { /* ... */ } },
"llm": { "provider": "claude", "model": "claude-sonnet-4-6" }
}
}

The full saved project is echoed back in data. Domain pack prompts are automatically seeded into the project on creation.

GET /api/v1/projects​

List all projects.

curl http://localhost:8080/api/v1/projects
{
"data": [
{
"id": "507f1f77bcf86cd799439011",
"name": "Puzzle Quest Analytics",
"domain": "gaming",
"category": "match3",
"status": "active",
"last_run_status": "completed",
"last_run_at": "2026-03-14T10:30:00Z",
"last_run_completed_at": "2026-03-14T10:42:00Z"
}
]
}

Each project carries a summary of its most recent discovery run, derived from the latest discovery_runs record at read time:

  • last_run_status — the run's lifecycle status: pending, running, completed, failed, or cancelled. Omitted when the project has never started a run.
  • last_run_at — when the most recent run started (RFC 3339). Clients count elapsed time up from this while a run is in progress.
  • last_run_completed_at — when it finished (RFC 3339). Omitted while the run is still in progress or never completed.

GET /api/v1/projects/{id}​

Get a project with full configuration.

curl http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011

Returns the complete project object including warehouse, LLM, and profile configuration, plus the same last_run_status / last_run_at / last_run_completed_at run summary as the list endpoint.

PUT /api/v1/projects/{id}​

Update a project. Supports partial updates — only fields present in the request body are updated. Prompts and profile are preserved if not included.

curl -X PUT http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011 \
-H "Content-Type: application/json" \
-d '{
"name": "Puzzle Quest (Updated)"
}'

DELETE /api/v1/projects/{id}​

Delete a project and all its data (discoveries, feedback, secrets, prompts).

curl -X DELETE http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011

Prompts​

GET /api/v1/projects/{id}/prompts​

Get the project's editable prompts. These are copies of the domain pack defaults, customizable per-project.

curl http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011/prompts
{
"data": {
"exploration": "# Gaming Analytics Discovery Agent\n\nYou are an autonomous...",
"recommendations": "# Generate Actionable Recommendations\n\n...",
"base_context": "## Project Profile\n\n{{PROFILE}}\n\n...",
"analysis_areas": {
"churn": {
"name": "Churn Risks",
"description": "Players at risk of leaving the game",
"keywords": ["churn", "retention"],
"prompt": "# Churn Pattern Analysis\n\n...",
"is_base": true,
"enabled": true,
"priority": 1
}
}
}
}

PUT /api/v1/projects/{id}/prompts​

Update prompts. Any field can be updated independently.

curl -X PUT http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011/prompts \
-H "Content-Type: application/json" \
-d '{
"base_context": "## Updated Project Profile\n\n{{PROFILE}}\n\n...",
"analysis_areas": {
"churn": {
"name": "Churn Risks",
"prompt": "# Updated Churn Analysis\n\n...",
"enabled": true
}
}
}'

Discoveries​

POST /api/v1/projects/{id}/discover​

Trigger a discovery run. Spawns the agent as a subprocess or K8s Job.

Request body (all fields optional):

FieldTypeDefaultDescription
areasstring[](all)Selective discovery — run only these analysis areas. Empty/omitted = all areas.
max_stepsint100Maximum exploration steps.
min_stepsintfloor(0.6 * max_steps)Floor on exploration steps before the agent will accept a done signal from the LLM. 0 disables the floor. Recommended for reasoning models (Qwen3, DeepSeek-R1, GPT-OSS) that tend to terminate exploration too early. Must be in [0, max_steps] — values outside that range return 400.
# Run all areas with default steps and the 60%-of-max_steps floor
curl -X POST http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011/discover

# Run specific areas with custom step count
curl -X POST http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011/discover \
-H "Content-Type: application/json" \
-d '{"areas": ["churn", "monetization"], "max_steps": 50}'

# Force a stricter floor for a reasoning model (e.g., Qwen3 on Bedrock)
curl -X POST http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011/discover \
-H "Content-Type: application/json" \
-d '{"max_steps": 100, "min_steps": 80}'

# Disable the floor entirely
curl -X POST http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011/discover \
-H "Content-Type: application/json" \
-d '{"max_steps": 100, "min_steps": 0}'
{
"status": "started",
"run_id": "507f1f77bcf86cd799439012",
"message": "Discovery agent started"
}

Returns 409 Conflict if a discovery is already running for this project. Returns 400 Bad Request if min_steps is negative or exceeds max_steps.

GET /api/v1/projects/{id}/discoveries​

List all discoveries for a project (newest first). Excludes heavy log fields for performance.

curl http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011/discoveries
{
"data": [
{
"id": "507f1f77bcf86cd799439013",
"project_id": "507f1f77bcf86cd799439011",
"run_type": "full",
"discovery_date": "2026-03-14T10:30:00Z",
"total_steps": 42,
"duration": 480000000000,
"insights": [...],
"recommendations": [...],
"summary": {"total_insights": 7, "total_recommendations": 5, "queries_executed": 42}
}
]
}

GET /api/v1/discoveries/{id}​

Get a single discovery with full data (including exploration log, analysis log, validation log).

curl http://localhost:8080/api/v1/discoveries/507f1f77bcf86cd799439013

Returns the complete DiscoveryResult including all logs.

GET /api/v1/projects/{id}/discoveries/latest​

Get the most recent discovery for a project.

GET /api/v1/projects/{id}/discoveries/{date}​

Get a discovery by date (format: YYYY-MM-DD).

GET /api/v1/projects/{id}/status​

Get the current discovery status for a project (running, completed, or null).

curl http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011/status
{
"data": {
"run": {
"id": "507f1f77bcf86cd799439012",
"status": "running",
"phase": "exploration",
"progress": 45,
"total_queries": 22,
"insights_found": 3,
"steps": [...]
}
}
}

Runs​

GET /api/v1/runs/{runId}​

Get live discovery run status with step-by-step progress.

curl http://localhost:8080/api/v1/runs/507f1f77bcf86cd799439012
{
"data": {
"id": "507f1f77bcf86cd799439012",
"project_id": "507f1f77bcf86cd799439011",
"status": "running",
"phase": "exploration",
"phase_detail": "Step 22/100",
"progress": 45,
"started_at": "2026-03-14T10:30:00Z",
"updated_at": "2026-03-14T10:35:00Z",
"steps": [
{
"phase": "exploration",
"step_num": 1,
"timestamp": "2026-03-14T10:30:05Z",
"type": "query",
"message": "Checking retention rates...",
"llm_thinking": "Let me start by looking at retention cohorts...",
"query": "SELECT cohort_date, day_1_retention FROM ...",
"row_count": 30,
"query_time_ms": 450
}
],
"total_queries": 22,
"successful_queries": 21,
"failed_queries": 1,
"insights_found": 3
}
}

DELETE /api/v1/runs/{runId}​

Cancel a running discovery. Kills the agent process and updates the run status.

curl -X DELETE http://localhost:8080/api/v1/runs/507f1f77bcf86cd799439012
{"status": "cancelled"}

Feedback​

POST /api/v1/discoveries/{runId}/feedback​

Submit feedback on an insight, recommendation, or exploration step.

curl -X POST http://localhost:8080/api/v1/discoveries/507f1f77bcf86cd799439013/feedback \
-H "Content-Type: application/json" \
-d '{
"target_type": "insight",
"target_id": "churn-1",
"rating": "like"
}'
# Dislike with comment
curl -X POST http://localhost:8080/api/v1/discoveries/507f1f77bcf86cd799439013/feedback \
-H "Content-Type: application/json" \
-d '{
"target_type": "insight",
"target_id": "churn-2",
"rating": "dislike",
"comment": "This metric definition is wrong for our game"
}'
FieldRequiredValuesDescription
target_typeYesinsight, recommendation, exploration_stepWhat is being rated
target_idYesstringID of the target (insight ID, recommendation index, step number)
ratingYeslike, dislikeThe rating
commentNostringOptional comment (typically with dislikes)

Feedback is upserted — one rating per (discovery, target_type, target_id). Submitting again replaces the previous rating.

GET /api/v1/discoveries/{runId}/feedback​

List all feedback for a discovery.

curl http://localhost:8080/api/v1/discoveries/507f1f77bcf86cd799439013/feedback

DELETE /api/v1/feedback/{id}​

Delete a feedback entry.

curl -X DELETE http://localhost:8080/api/v1/feedback/507f1f77bcf86cd799439014

Pricing​

GET /api/v1/pricing​

Get current LLM and warehouse pricing configuration. Auto-seeded from provider defaults on first startup.

curl http://localhost:8080/api/v1/pricing

PUT /api/v1/pricing​

Update pricing (e.g., if your negotiated rates differ from defaults).

curl -X PUT http://localhost:8080/api/v1/pricing \
-H "Content-Type: application/json" \
-d '{
"llm": {
"claude-sonnet-4": {"input_per_million": 2.5, "output_per_million": 12.0}
}
}'

Cost Estimation​

POST /api/v1/projects/{id}/discover/estimate​

Estimate the cost of a discovery run without executing it. Spawns the agent with --estimate flag.

curl -X POST http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011/discover/estimate \
-H "Content-Type: application/json" \
-d '{"max_steps": 100}'
{
"data": {
"llm": {
"provider": "claude",
"model": "claude-sonnet-4-6",
"estimated_input_tokens": 250000,
"estimated_output_tokens": 50000,
"cost_usd": 0.825
},
"warehouse": {
"provider": "bigquery",
"estimated_queries": 100,
"estimated_bytes_scanned": 5368709120,
"cost_usd": 0.0375
},
"total_cost_usd": 0.8625
}
}

Connection Testing​

POST /api/v1/projects/{id}/test/warehouse​

Test the warehouse connection for a project. Spawns the agent with --test-connection warehouse to validate using the agent's IAM context.

curl -X POST http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011/test/warehouse
{
"data": {
"success": true,
"provider": "bigquery",
"datasets": ["events_prod"]
}
}

On failure:

{
"data": {
"success": false,
"error": "bigquery: cannot access dataset events_prod: googleapi: Error 403: Access Denied"
}
}

POST /api/v1/projects/{id}/test/llm​

Test the LLM provider connection for a project. Spawns the agent with --test-connection llm to validate credentials and model access.

curl -X POST http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011/test/llm
{
"data": {
"success": true,
"provider": "claude",
"model": "claude-sonnet-4-6"
}
}

Secrets​

PUT /api/v1/projects/{id}/secrets/{key}​

Create or update a per-project secret. The value is encrypted at rest.

# Set LLM API key
curl -X PUT http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011/secrets/llm-api-key \
-H "Content-Type: application/json" \
-d '{"value": "sk-ant-api03-..."}'

GET /api/v1/projects/{id}/secrets​

List secrets for a project. Returns masked values only — full values are never exposed via the API.

curl http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011/secrets
{
"data": [
{
"key": "llm-api-key",
"masked": "sk-ant***DwAA",
"updated_at": "2026-03-14T10:00:00Z"
}
]
}

Note: There is no DELETE endpoint for secrets. Secrets are removed manually via cloud console, CLI, or direct database access. This is intentional to prevent accidental deletion.


Bookmark Lists​

Named collections of insights and recommendations. Every list and bookmark is scoped by (project_id, user_id) where user_id comes from the authenticated principal — "anonymous" under the default no-auth setup.

POST /api/v1/projects/{id}/lists​

Create a bookmark list.

Request body:

{"name": "Retention ideas", "description": "optional", "color": "#2b7"}

Response 201:

{
"id": "...",
"project_id": "...",
"user_id": "anonymous",
"name": "Retention ideas",
"description": "optional",
"color": "#2b7",
"item_count": 0,
"created_at": "2026-04-17T…",
"updated_at": "2026-04-17T…"
}

Returns 400 if name is empty or longer than 200 characters.

GET /api/v1/projects/{id}/lists​

List the caller's lists for this project, newest-updated first. Each entry includes item_count.

GET /api/v1/projects/{id}/lists/{listId}​

List detail. Response includes an items array where each entry is:

{
"bookmark": {"id": "...", "target_type": "insight", "target_id": "...", ...},
"target": { /* full insight or recommendation document */ }
}

If a bookmarked target has been deleted from the source collection, target is omitted and deleted: true is set — the dashboard renders these as "[removed]" placeholders.

Returns 404 if the list does not exist or is not owned by the caller.

PATCH /api/v1/projects/{id}/lists/{listId}​

Partial update. Only provided fields change; created_at is never touched.

{"name": "New name"}

Returns 400 on empty name, 404 on wrong owner.

DELETE /api/v1/projects/{id}/lists/{listId}​

Deletes the list and cascades to every bookmark in it. The underlying insights and recommendations are untouched. Returns 404 on wrong owner.

POST /api/v1/projects/{id}/lists/{listId}/items​

Add a bookmark. Idempotent — calling with the same (target_type, target_id) that already exists in the list returns the existing bookmark.

{"target_type": "insight", "target_id": "...", "discovery_id": "...", "note": "optional"}

target_type must be "insight" or "recommendation". Returns 404 if the list is not owned by the caller.

DELETE /api/v1/projects/{id}/lists/{listId}/items/{bookmarkId}​

Remove a bookmark. Returns 404 if the bookmark is in a different list or not owned by the caller.

GET /api/v1/projects/{id}/bookmarks?target_type=insight&target_id=...​

Reverse lookup used by the dashboard's Add-to-list menu. Returns an array of list IDs (scoped to the caller) that currently contain the given target. Empty array when the target is not bookmarked.


Read Marks​

Per-user state for which insights and recommendations the caller has already opened.

POST /api/v1/projects/{id}/reads​

Mark a target read. Idempotent — repeated calls refresh read_at without creating duplicates, enforced by a unique compound index on (project_id, user_id, target_type, target_id).

{"target_type": "insight", "target_id": "..."}

DELETE /api/v1/projects/{id}/reads​

Mark a target unread. Same body shape as above. Idempotent: returns 200 whether or not a mark existed.

GET /api/v1/projects/{id}/reads?target_type=insight​

Returns the target_ids the caller has read, as a flat array of strings. List pages use this to apply greyed-out styling to read rows without fetching full mark documents.

["ins-1", "ins-2", "ins-5"]

Error Responses​

All error responses follow the same format:

{"error": "descriptive error message"}

Common HTTP status codes:

StatusMeaning
200Success
201Created
202Accepted (async operation started)
400Bad request (invalid input)
404Not found
409Conflict (e.g., discovery already running)
500Internal server error

Next Steps​