Skip to main content
Version: Next

API Reference

Version: 0.3.0

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", "checks": {"mongodb": "ok"}}

Returns 503 if MongoDB is unreachable.


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": "api_key", "label": "API Key", "required": true, "type": "string", "placeholder": "sk-ant-..."},
{"key": "model", "label": "Model", "required": true, "type": "string", "default": "claude-sonnet-4-20250514"}
],
"default_pricing": {
"claude-sonnet-4": {"input_per_million": 3.0, "output_per_million": 15.0},
"claude-opus-4": {"input_per_million": 15.0, "output_per_million": 75.0}
},
"max_output_tokens": {
"claude-sonnet-4": 16384,
"claude-opus-4": 16384,
"claude-haiku-4-5": 8192
}
}
]
}

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",
"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-20250514",
"config": {}
},
"schedule": {
"enabled": false,
"cron_expr": "0 2 * * *",
"max_steps": 100
}
}'
{
"data": {
"id": "507f1f77bcf86cd799439011",
"name": "Puzzle Quest Analytics",
"domain": "gaming",
"category": "match3",
"created_at": "2026-03-14T10:00:00Z"
}
}

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_at": "2026-03-14T10:30:00Z",
"last_run_status": "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, schedule, and profile configuration.

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)",
"schedule": {"enabled": true, "cron_expr": "0 3 * * *", "max_steps": 150}
}'

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

Pack Generation

These endpoints exist on every build but return 404 Not Found unless a pack-generation provider is registered (the stock community build returns 404; deployments that load the generator plugin handle them).

POST /api/v1/projects/{id}/pack-generate

Launch domain-pack generation on a project that is currently in pack_generation_pending state. The agent reads the project's knowledge sources and warehouse schema, synthesizes a DomainPack, persists it, and parks the project at pack_generation_done for user review.

curl -X POST http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011/pack-generate
{
"data": {
"run_id": "run_abc",
"async": true,
"pack_slug": "acme-marketplace",
"attempts": 0
}
}
  • async: true (HTTP 202) — generation is running; poll GET /api/v1/projects/{id} for state to flip to pack_generation_done.
  • async: false (HTTP 200) — generation finished synchronously; attempts is the number of validator passes.

Errors:

  • 404 Not Found — pack generation is not enabled on this deployment.
  • 409 Conflict — project is not in pack_generation_pending.
  • 400 Bad Request — project does not have generate_pack populated.

POST /api/v1/projects/{id}/pack-generate/regenerate

Re-emit a single section of the saved pack with user feedback. Valid only after the pack has been generated (project in pack_generation_done).

curl -X POST http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011/pack-generate/regenerate \
-H "Content-Type: application/json" \
-d '{ "section": "analysis_areas", "feedback": "Add more retention focus." }'
{
"data": {
"pack_slug": "acme-marketplace",
"section": "analysis_areas",
"attempts": 1
}
}

Recognised sections: metadata, categories, analysis_areas, profile_schema, base_context, exploration, recommendations.

Accepting a generated pack

Move the project from pack_generation_done to ready via the standard project update endpoint:

curl -X PUT http://localhost:8080/api/v1/projects/507f1f77bcf86cd799439011 \
-H "Content-Type: application/json" \
-d '{ "state": "ready" }'

After this, the project behaves like any other — discovery is unlocked.


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-20250514",
"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-20250514"
}
}

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" in community (NoAuth) mode, the OIDC sub claim in enterprise mode.

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 or if the target does not exist in its source collection.

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