ข้ามไปยังเนื้อหา

API and Event Contracts

Key payload fields:

{
"userMessage": "User input",
"threadId": "thread_uuid",
"agentType": "idea",
"projectId": "project_uuid",
"metadata": {
"knowledge_search": true,
"attached_file_ids": ["doc_or_file_uuid"]
}
}

Current validation behavior in BFF proxy:

  • Request keys are allowlisted.
  • Metadata keys are allowlisted.
  • Unknown request or metadata keys return 400.
  • If both threadId and sessionId are provided and mismatch, BFF returns 400.

Thread field normalization:

  • threadId is the canonical UI/BFF field.
  • sessionId is still accepted as a deprecated alias for backward compatibility.
  • Preferred runtime behavior is a real UUID from the UI thread creation flow.
  • If missing or new, BFF returns 400 and requires thread creation first.
BoundaryCanonical NameAccepted AliasNote
Browser -> BFF chat thread keythreadIdsessionIdBFF rejects mismatched values when both exist.
Browser -> BFF resume decision keytoolCallIdtool_call_id, idBFF normalizes outbound decisions to snake_case for engine.
BFF -> Engine chat thread keysessionId-Engine schema maps sessionId into thread_id.
BFF -> Engine resume decision keytool_call_id-Engine resume contract is snake_case.

BFF sends internal contract headers on every engine call:

  • Content-Type: application/json
  • Authorization: Bearer <ENGINE_SERVICE_AUTH_TOKEN> (when configured)
  • X-Contract-Version: 2026-02
  • X-Request-Id: <request_id>
  • X-User-Id: <user_uuid>
  • X-Project-Id: <project_uuid> (when present)
  • X-Thread-Id: <thread_uuid> (when present)
  • X-Agent-Type: <agent_type>
  • Accept: text/event-stream for streaming endpoints

Engine validates these headers against body claims when INTERNAL_API_ENFORCE=true.

{
"userMessage": "User input",
"sessionId": "thread_uuid",
"agentType": "idea",
"projectId": "project_uuid",
"userId": "user_uuid",
"provider": "groq",
"model": null,
"systemPrompt": "...",
"chatHistory": null,
"metadata": {
"knowledge_search": true,
"attached_document_ids": ["resolved_doc_uuid"],
"canvas_context": {
"mode": "summary",
"summary": {
"nodeCount": 10,
"byType": { "strategy_item": 5 },
"byStatus": { "approve": 2, "reject": 1, "pending": 7 }
},
"context_ref": {
"project_id": "project_uuid",
"thread_id": "thread_uuid",
"agent_id": "strategy",
"revision": "0123456789abcdef"
}
},
"canvas_profile": {}
}
}

Browser to BFF:

{
"threadId": "thread_uuid",
"projectId": "project_uuid",
"agentType": "idea",
"approved": true,
"reason": "optional",
"decisions": [
{ "toolCallId": "tool_1", "approved": true },
{ "toolCallId": "tool_2", "approved": false, "reason": "not needed" }
]
}

BFF to engine (normalized):

{
"thread_id": "thread_uuid",
"project_id": "project_uuid",
"user_id": "user_uuid",
"action_id": null,
"approved": true,
"reason": "optional",
"decisions": [
{ "tool_call_id": "tool_1", "approved": true, "reason": null },
{ "tool_call_id": "tool_2", "approved": false, "reason": "not needed" }
]
}

If decisions[] is present but malformed, BFF returns 400.

GET /api/canvas/workspace

Required query:

  • agentId
  • projectId

Optional query:

  • threadId
  • includeEdges
  • includeAllAgents
  • limit

GET /api/canvas/context

Required query:

  • agentId
  • projectId

Optional query:

  • threadId
  • maxNodes
  • includeContent
  • includeRelationships
  • framework

POST /api/canvas/mutations

{
"agentId": "strategy",
"projectId": "project_uuid",
"threadId": "thread_uuid",
"idempotencyKey": "client-generated-key",
"operations": [
{
"operation": "update_node",
"nodeId": "node_uuid",
"data": { "title": "Updated" }
}
]
}

All /api/canvas/* routes require a real authenticated user token.

Postman Import -> Raw text works best with single-line cURL (no trailing \ and no shell env variables).

GET /api/canvas/workspace:

Terminal window
curl --location --request GET "http://localhost:3000/api/canvas/workspace?agentId=strategy&projectId=<project_uuid>&threadId=<thread_uuid>&includeEdges=true&includeAllAgents=false&limit=200" --header "Authorization: Bearer <USER_ACCESS_TOKEN>"

GET /api/canvas/context:

Terminal window
curl --location --request GET "http://localhost:3000/api/canvas/context?agentId=strategy&projectId=<project_uuid>&threadId=<thread_uuid>&maxNodes=50&includeContent=true&includeRelationships=true&framework=swot" --header "Authorization: Bearer <USER_ACCESS_TOKEN>"

If you prefer Postman environment variables, replace values with {{API_BASE}}, {{USER_TOKEN}}, {{PROJECT_ID}}, {{THREAD_ID}}, and {{AGENT_ID}} after import.

GET /api/internal/canvas/context is internal-only for engine service calls.

Required:

  • Bearer token matching INTERNAL_ENGINE_TOKEN
  • X-Contract-Version matching INTERNAL_API_CONTRACT_VERSION
  • trusted scope headers (X-User-Id, X-Project-Id, X-Thread-Id)

Query projectId/threadId/agentId must match trusted claims or route returns 403.

Example internal call (Postman import-ready):

Terminal window
curl --location --request GET "http://localhost:3000/api/internal/canvas/context?agentId=strategy&projectId=<project_uuid>&threadId=<thread_uuid>&includeContent=true&includeRelationships=true" --header "Authorization: Bearer <INTERNAL_ENGINE_TOKEN>" --header "X-Contract-Version: 2026-02" --header "X-User-Id: <user_uuid>" --header "X-Project-Id: <project_uuid>" --header "X-Thread-Id: <thread_uuid>"

Engine emits SSE payloads with keys such as:

  • chunk
  • tool_call
  • search_results
  • type: hitl_interrupt
  • done
  • error

BFF stream relay removes canvas_mutation_intents fields before forwarding to browser.

  • 400 Unknown request field(s) or Unknown metadata field(s): browser payload contains disallowed keys.
  • 400 Ambiguous thread identifier: threadId and sessionId mismatch.
  • 400 Thread ID is required: thread not materialized yet (threadId missing or set to new).
  • 400 Invalid decisions payload: malformed decisions[] (missing toolCallId/tool_call_id or approved).
  • 400 Invalid internal contract version: BFF and engine INTERNAL_API_CONTRACT_VERSION mismatch.
  • 403 Thread not found or access denied: thread/user/project scope mismatch.