Skip to content

REST API

The daemon exposes a REST API on the configured port (default: 31415). All endpoints are prefixed with /api/ and accept JSON request bodies.

Local only

The API is bound to 127.0.0.1 with CORS restricted to localhost origins. It is not accessible from the network.

Agents

GET /api/agents

List all active agents.

bash
curl http://localhost:31415/api/agents
json
[
  {
    "id": "backend-1",
    "role": "backend",
    "status": "active",
    "provider": "claude-code",
    "model": "claude-sonnet-4-6",
    "scope": ["src/api/**"],
    "contextUsage": 0.45,
    "spawnedAt": "2026-04-05T10:00:00Z"
  }
]

POST /api/agents

Spawn a new agent. Optionally set workingDir to spawn the agent inside a subdirectory.

bash
curl -X POST http://localhost:31415/api/agents \
  -H "Content-Type: application/json" \
  -d '{"role": "backend", "scope": ["src/api/**"], "model": "claude-sonnet-4-6", "workingDir": "packages/backend"}'

DELETE /api/agents/:id

Kill an agent.

bash
curl -X DELETE http://localhost:31415/api/agents/backend-1

POST /api/agents/:id/rotate

Trigger context rotation for an agent. Generates a handoff brief, kills the session, and respawns with fresh context.

bash
curl -X POST http://localhost:31415/api/agents/backend-1/rotate
json
{
  "old": "backend-1",
  "new": "backend-2",
  "handoffBrief": true,
  "reason": "manual"
}

POST /api/agents/:id/instruct

Send an instruction to a running agent. Uses rotation-based delivery -- the instruction is injected into the agent's context on its next rotation cycle.

bash
curl -X POST http://localhost:31415/api/agents/backend-1/instruct \
  -H "Content-Type: application/json" \
  -d '{"message": "Refactor the auth middleware to use JWT instead of sessions"}'

POST /api/agents/:id/query

Query an agent without disrupting its current work. Runs a headless call to gather information.

bash
curl -X POST http://localhost:31415/api/agents/backend-1/query \
  -H "Content-Type: application/json" \
  -d '{"question": "What is the current state of the database migration?"}'
json
{
  "answer": "Migration 003 is complete. Working on 004 — adding the sessions table.",
  "agentId": "backend-1"
}

Health

GET /api/health

Health check.

bash
curl http://localhost:31415/api/health
json
{
  "status": "ok",
  "uptime": 3600,
  "agents": 3,
  "version": "0.3.0"
}

Teams

GET /api/teams

List saved teams.

bash
curl http://localhost:31415/api/teams

POST /api/teams

Save current configuration as a team.

bash
curl -X POST http://localhost:31415/api/teams \
  -H "Content-Type: application/json" \
  -d '{"name": "fullstack"}'

POST /api/teams/:name/load

Load and spawn a saved team.

bash
curl -X POST http://localhost:31415/api/teams/fullstack/load

DELETE /api/teams/:name

Delete a saved team.

bash
curl -X DELETE http://localhost:31415/api/teams/fullstack

Credentials

GET /api/credentials

List stored credentials (keys are masked).

bash
curl http://localhost:31415/api/credentials

POST /api/credentials

Store an API key for a provider.

bash
curl -X POST http://localhost:31415/api/credentials \
  -H "Content-Type: application/json" \
  -d '{"provider": "codex", "key": "sk-your-key"}'

DELETE /api/credentials/:provider

Delete a stored credential.

bash
curl -X DELETE http://localhost:31415/api/credentials/codex

Providers

GET /api/providers

List available providers and their installation status.

bash
curl http://localhost:31415/api/providers

Configuration

GET /api/config

Get current configuration.

bash
curl http://localhost:31415/api/config

PATCH /api/config

Update configuration values.

bash
curl -X PATCH http://localhost:31415/api/config \
  -H "Content-Type: application/json" \
  -d '{"rotationThreshold": 0.7, "maxAgents": 8}'

Dashboard

GET /api/dashboard

Aggregated dashboard data -- agent summary, token usage, savings, and system status in a single call.

bash
curl http://localhost:31415/api/dashboard
json
{
  "agents": { "active": 3, "total": 7, "rotations": 4 },
  "tokens": { "used": 1250000, "saved": 380000, "budget": null },
  "savings": { "rotation": 210000, "coldStart": 120000, "conflict": 50000 },
  "uptime": 7200
}

Project Manager

POST /api/pm/review

Trigger a PM review of a specific agent's recent changes.

bash
curl -X POST http://localhost:31415/api/pm/review \
  -H "Content-Type: application/json" \
  -d '{"agentId": "backend-1"}'

GET /api/pm/history

Get PM review history.

bash
curl http://localhost:31415/api/pm/history

Journalist

GET /api/journalist

Get Journalist status and last cycle info.

bash
curl http://localhost:31415/api/journalist
json
{
  "running": true,
  "lastCycle": "2026-04-05T10:02:00Z",
  "interval": 120,
  "documentsGenerated": ["GROOVE_PROJECT_MAP.md", "GROOVE_DECISIONS.md"]
}

POST /api/journalist/cycle

Manually trigger a Journalist synthesis cycle.

bash
curl -X POST http://localhost:31415/api/journalist/cycle

Approvals

GET /api/approvals

List pending approval requests.

bash
curl http://localhost:31415/api/approvals

POST /api/approvals

Submit an approval decision.

bash
curl -X POST http://localhost:31415/api/approvals \
  -H "Content-Type: application/json" \
  -d '{"id": "approval-1", "decision": "approve"}'

Tokens

GET /api/tokens/summary

Token usage summary with savings breakdown.

bash
curl http://localhost:31415/api/tokens/summary
json
{
  "totalUsed": 1250000,
  "totalSaved": 380000,
  "breakdown": {
    "rotation": 210000,
    "coldStart": 120000,
    "conflict": 50000
  },
  "perAgent": [
    { "id": "backend-1", "used": 450000, "model": "claude-sonnet-4-6" }
  ]
}

Routing

GET /api/routing

Adaptive model routing status and cost tracking.

bash
curl http://localhost:31415/api/routing
json
{
  "mode": "auto",
  "decisions": 12,
  "costSaved": 0.45,
  "currentMappings": {
    "backend-1": { "model": "claude-sonnet-4-6", "reason": "medium_complexity" }
  }
}

Rotation

GET /api/rotation

Rotation statistics and history.

bash
curl http://localhost:31415/api/rotation
json
{
  "totalRotations": 4,
  "history": [
    {
      "old": "backend-1",
      "new": "backend-2",
      "reason": "context_threshold",
      "timestamp": "2026-04-05T10:30:00Z"
    }
  ]
}

Gateways

GET /api/gateways

List all configured gateways with connection status.

bash
curl http://localhost:31415/api/gateways
json
[
  {
    "id": "telegram-abc123",
    "type": "telegram",
    "displayName": "Telegram",
    "connected": true,
    "enabled": true,
    "chatId": "123456789",
    "notifications": { "preset": "critical" },
    "commandPermission": "full",
    "allowedUsers": 1,
    "botUsername": "my_groove_bot"
  }
]

POST /api/gateways

Create a new gateway.

bash
curl -X POST http://localhost:31415/api/gateways \
  -H "Content-Type: application/json" \
  -d '{"type": "telegram"}'

Supported types: telegram, discord, slack.

GET /api/gateways/:id

Get a specific gateway's status and configuration.

bash
curl http://localhost:31415/api/gateways/telegram-abc123

PATCH /api/gateways/:id

Update gateway configuration.

bash
curl -X PATCH http://localhost:31415/api/gateways/telegram-abc123 \
  -H "Content-Type: application/json" \
  -d '{"notifications": {"preset": "lifecycle"}, "commandPermission": "read-only"}'

Updatable fields: enabled, chatId, allowedUsers, notifications, commandPermission.

DELETE /api/gateways/:id

Delete a gateway and disconnect it.

bash
curl -X DELETE http://localhost:31415/api/gateways/telegram-abc123

POST /api/gateways/:id/test

Send a test notification through the gateway.

bash
curl -X POST http://localhost:31415/api/gateways/telegram-abc123/test

POST /api/gateways/:id/connect

Manually connect a gateway.

bash
curl -X POST http://localhost:31415/api/gateways/telegram-abc123/connect

POST /api/gateways/:id/disconnect

Manually disconnect a gateway.

bash
curl -X POST http://localhost:31415/api/gateways/telegram-abc123/disconnect

POST /api/gateways/:id/credentials

Set a credential for a gateway (e.g., bot token).

bash
curl -X POST http://localhost:31415/api/gateways/telegram-abc123/credentials \
  -H "Content-Type: application/json" \
  -d '{"key": "bot_token", "value": "123456:ABC-DEF..."}'

DELETE /api/gateways/:id/credentials/:key

Delete a gateway credential.

bash
curl -X DELETE http://localhost:31415/api/gateways/telegram-abc123/credentials/bot_token

Codebase Indexer

GET /api/indexer

Indexer status -- whether the scan has run and how many workspaces were detected.

bash
curl http://localhost:31415/api/indexer
json
{
  "indexed": true,
  "lastIndexTime": 1712300000000,
  "workspaceCount": 3,
  "stats": { "totalFiles": 847, "totalDirs": 42, "treeDepth": 4 }
}

GET /api/indexer/workspaces

List detected workspaces with metadata.

bash
curl http://localhost:31415/api/indexer/workspaces
json
{
  "workspaces": [
    { "path": "packages/frontend", "name": "@mysite/frontend", "type": "npm-workspaces", "files": 124, "dirs": 8 },
    { "path": "packages/backend", "name": "@mysite/backend", "type": "npm-workspaces", "files": 89, "dirs": 6 }
  ]
}

POST /api/indexer/rescan

Re-scan the project structure. Useful after adding new workspaces or restructuring.

bash
curl -X POST http://localhost:31415/api/indexer/rescan

Integrations

GET /api/integrations/:id/tools

List the tools available from an installed integration. Starts the MCP server if not already running.

bash
curl http://localhost:31415/api/integrations/github/tools
json
{
  "tools": [
    {
      "name": "create_issue",
      "description": "Create a new issue in a GitHub repository",
      "inputSchema": {
        "type": "object",
        "properties": {
          "owner": { "type": "string" },
          "repo": { "type": "string" },
          "title": { "type": "string" },
          "body": { "type": "string" }
        },
        "required": ["owner", "repo", "title"]
      }
    }
  ]
}

POST /api/integrations/:id/exec

Execute a tool on an installed integration. The daemon proxies the call to the MCP server over JSON-RPC and returns the result as plain JSON. Dangerous tools return 202 and require human approval before executing.

Standard call (non-gated tool):

bash
curl -X POST http://localhost:31415/api/integrations/github/exec \
  -H "Content-Type: application/json" \
  -d '{"tool": "search_repositories", "params": {"query": "groove"}}'
json
{
  "result": {
    "content": [
      { "type": "text", "text": "Found 3 repositories matching \"groove\"..." }
    ]
  }
}

Approval-gated call (dangerous tool):

bash
curl -X POST http://localhost:31415/api/integrations/github/exec \
  -H "Content-Type: application/json" \
  -d '{"tool": "create_issue", "params": {"owner": "myorg", "repo": "myapp", "title": "Fix redirect"}}'
json
{
  "requiresApproval": true,
  "approvalId": "approval-42",
  "message": "Tool \"create_issue\" requires approval. Retry with this approvalId once approved."
}

Retry after approval:

bash
curl -X POST http://localhost:31415/api/integrations/github/exec \
  -H "Content-Type: application/json" \
  -d '{"tool": "create_issue", "params": {"owner": "myorg", "repo": "myapp", "title": "Fix redirect"}, "approvalId": "approval-42"}'

Request body:

FieldTypeRequiredDescription
toolstringYesName of the MCP tool to execute
paramsobjectNoArguments to pass to the tool (defaults to {})
approvalIdstringNoApproval ID from a previous 202 response — required to execute gated tools
agentstringNoAgent ID making the call — recorded in audit log

Responses:

StatusMeaning
200Tool executed successfully
202Tool requires approval — approvalId returned, retry after approval
400Validation error, integration not installed, or MCP server error
403Approval was rejected
404approvalId not found
429Rate limit exceeded (30 calls/min per integration)

Audit events: Every call is logged — integration.exec on success, integration.exec.blocked when approval-gated, integration.exec.rate_limited when throttled.

Adaptive

GET /api/adaptive

Adaptive threshold data -- per-provider, per-role rotation thresholds and session scores.

bash
curl http://localhost:31415/api/adaptive
json
{
  "thresholds": {
    "claude-code": { "backend": 0.73, "frontend": 0.78 },
    "codex": { "backend": 0.75 }
  },
  "convergence": { "claude-code": true, "codex": false }
}

Coordination

Agent coordination on shared resources (npm install, server restart, shared config). Returns 423 on conflict with the owner's identity. Locks auto-expire after 10 minutes to prevent deadlock.

POST /api/coordination/declare

Acquire an exclusive lock on a list of resources.

Request body:

FieldTypeRequiredDescription
agentIdstringYesAgent declaring the operation
operationstringYesHuman-readable name (e.g., "npm install")
resourcesstring[]YesResources to lock (arbitrary strings, exact-match)
ttlMsnumberNoLock TTL in ms. Default 600000 (10 min)

Success response (200):

json
{ "declared": true, "operation": "npm install", "resources": ["package.json"] }

Conflict response (423):

json
{
  "conflict": true,
  "owner": "bk-2",
  "operation": "build",
  "resource": "package.json",
  "expiresAt": 1744500000000
}

POST /api/coordination/complete

Release any locks held by the agent. Always call this after an operation — even on failure — to avoid deadlock.

Request body:

FieldTypeRequired
agentIdstringYes

GET /api/coordination

List all currently-declared operations.

json
{
  "operations": {
    "bk-2": {
      "name": "npm install",
      "resources": ["package.json"],
      "acquiredAt": 1744499700000,
      "expiresAt": 1744500300000
    }
  }
}

Memory (Layer 7)

Persistent agent memory that survives rotations, restarts, and new spawns. See Persistent Memory guide for conceptual overview.

GET /api/memory/constraints

List all project constraints.

json
{ "constraints": [{ "hash": "a1b2c3d4", "category": "hard", "text": "ESM only — never use require()" }] }

POST /api/memory/constraints

Add a new constraint. Dedup'd by hash — identical text returns { added: false, reason: "duplicate" }.

Request body: { "text": "...", "category": "hard" } — text 3-500 chars, category optional (defaults to "general").

DELETE /api/memory/constraints/:hash

Remove a constraint by its hash.

GET /api/memory/handoff-chain

List roles that have an active handoff chain.

json
{ "roles": ["backend", "frontend", "planner"] }

GET /api/memory/handoff-chain/:role

Full chain entries for a role (last 10 rotations, newest first).

json
{
  "role": "backend",
  "entries": [
    { "rotationN": 8, "body": "## Rotation 8 — ..." },
    { "rotationN": 7, "body": "## Rotation 7 — ..." }
  ]
}

GET /api/memory/handoff-chain/:role/recent

Markdown-formatted recent briefs for injection into agent context. Query param ?count=3 (max 10).

GET /api/memory/discoveries

List error→fix discoveries. Query params: ?role=backend, ?limit=100 (max 500).

json
{
  "discoveries": [
    {
      "ts": "2026-04-12T14:00:00Z",
      "agentId": "bk-3",
      "role": "backend",
      "trigger": "Cannot find module gray-matter",
      "fix": "npm install gray-matter",
      "outcome": "success"
    }
  ]
}

POST /api/memory/discoveries

Record a new discovery.

Request body:

FieldTypeRequired
agentIdstringNo
rolestringNo (default: "unknown")
triggerstringYes (max 300 chars)
fixstringYes (max 500 chars)
outcomestringNo (must be "success" to store, default "success")

Failed attempts are not stored. Identical trigger+fix pairs are deduplicated.

GET /api/memory/specializations

Full per-agent and per-role quality profiles.

json
{
  "perAgent": {
    "bk-3": {
      "role": "backend",
      "sessionCount": 14,
      "avgQualityScore": 78,
      "fileTouches": { "packages/daemon/src/journalist.js": 23 },
      "preferredThreshold": 0.82
    }
  },
  "perProjectRole": {
    "backend": { "sessionCount": 42, "avgQualityScore": 74, "topFileChurn": { "packages/daemon/src/api.js": 18 } }
  }
}

GET /api/memory/specializations/:agentId

Single agent's profile. Returns 404 if no data.

Tokens (additions)

GET /api/tokens/by-team

Ranked per-team token burn. Answers "which team used the most tokens?" in one call.

json
{
  "teams": [
    {
      "teamId": "T1",
      "teamName": "agents",
      "isDefault": false,
      "agentCount": 4,
      "totalTokens": 48_000_000,
      "totalCostUsd": 12.40,
      "avgTokensPerAgent": 12_000_000
    }
  ]
}

Unassigned agents appear under teamId: "__unassigned__".

GET /api/tokens/summary additions

The existing summary response now includes internalOverhead — tokens and cost consumed by GROOVE's own coordination (Journalist, PM, planner, negotiator, gateway, agent Q&A). Use this to compute honest ROI against savings.total.

json
{
  "totalTokens": 275_000_000,
  "cacheHitRate": 0.887,
  "savings": { "total": 32_000_000, "fromRotation": 29_800_000, "percentage": 10.4 },
  "internalOverhead": {
    "tokens": 1_200_000,
    "costUsd": 0.36,
    "components": {
      "__journalist__": { "tokens": 800_000, "costUsd": 0.24, "sessions": 240 },
      "__pm__": { "tokens": 300_000, "costUsd": 0.09, "sessions": 18 },
      "__negotiator__": { "tokens": 100_000, "costUsd": 0.03, "sessions": 4 }
    }
  }
}

Toys

GET /api/toys

List all available API toys. Optionally filter by category.

bash
curl http://localhost:31415/api/toys
json
[
  {
    "id": "nasa-eonet",
    "name": "NASA EONET",
    "category": "space",
    "auth": "api-key",
    "difficulty": "intermediate",
    "description": "Earth Observatory natural events — wildfires, storms, volcanoes",
    "docsUrl": "https://eonet.gsfc.nasa.gov/docs/v3",
    "starters": [
      "Build a live earthquake map",
      "Track wildfire activity by region",
      "Create a natural disaster alert dashboard"
    ]
  }
]

Query parameters:

ParamTypeDescription
categorystringFilter by category — space, weather, finance, fun, maps, data

GET /api/toys/:id

Get a single toy by ID.

bash
curl http://localhost:31415/api/toys/nasa-eonet

Returns the same shape as a single item from GET /api/toys. Returns 404 if the toy ID doesn't exist.

POST /api/toys/:id/launch

Launch a toy — creates a new team and spawns a planner agent that web-fetches the API docs and starts a conversation.

bash
curl -X POST http://localhost:31415/api/toys/nasa-eonet/launch \
  -H "Content-Type: application/json" \
  -d '{"apiKey": "your-nasa-api-key", "starterPrompt": "Build a live earthquake map"}'
json
{
  "team": {
    "id": "T12",
    "name": "Toy: NASA EONET"
  },
  "agent": {
    "id": "planner-25",
    "role": "planner",
    "status": "active"
  },
  "toyId": "nasa-eonet"
}

Request body:

FieldTypeRequiredDescription
apiKeystringNoAPI key for the toy — encrypted and stored locally. Omit for free APIs
starterPromptstringNoPre-selected starter idea — skips the "what do you want to build?" step

Responses:

StatusMeaning
200Team created, planner spawned
400Validation error (e.g., toy requires a key but none provided)
404Toy ID not found

Routing Suggestions

GET /api/agents/:id/routing/suggestion

When the classifier has 40+ events and detects that a lighter model would handle the agent's current task, returns a downshift suggestion. Never auto-applied — user must accept via UI click.

Response (200):

json
{
  "agentId": "bk-3",
  "currentModel": { "id": "claude-opus-4-6", "name": "Opus 4.6", "tier": "heavy" },
  "suggestedModel": { "id": "claude-haiku-4-5", "name": "Haiku 4.5", "tier": "light" },
  "classifiedTier": "light",
  "eventCount": 65,
  "reason": "Last 65 events classified as light. A lighter model would likely handle this task and reduce cost."
}

Response (204): No suggestion — classifier confidence too low, tiers already aligned, or current model is already cheapest.

Never suggests upshift (moving to a heavier tier). Honors the heavy-defaults design principle.

Last updated:

FSL-1.1-Apache-2.0