# Capture

> The Capture operations of the Hub REST API (3 operations).

URL: https://docs.synap.live/docs/reference/hub-api/capture · Generated from Synap-core/backend@c31e261ab (main)

Paths are relative to `/api/hub` on a pod.

## POST /capture/cluster-tabs

Cluster open browser tabs into topic groups

Forwards tabs to the Intelligence Service for AI clustering and returns a list of named clusters with each cluster's tabs.

Request body (`application/json`): ClusterTabsRequest

| Field | Type | Required | Description |
|---|---|---|---|
| `tabs` | array<object> | yes |  |

Responses:

- `200`: Tab clusters
- `400`: Bad request
- `403`: Forbidden
- `502`: Upstream IS error
- `503`: Clustering service unavailable

## POST /capture/structure

AI-structure raw input into entity proposals

Sends free-form text (and optional URL/HTML/context) to the AI capture pipeline. Returns proposed entities ready for /capture/execute. A HUMAN caller (no agent key) instead gets CONFIRM MODE: the plan is persisted as a pending composite proposal and the response is the submit result — status/proposalId/reviewUrl/summary/entityCount/relationCount/bindingCount/applied/writeReceipt, plus (only when non-empty) pendingDuplicateCandidates, relationsFailed, unmodeledProperties, project and projectCandidate. Two dissimilar payloads share this route, so the 200 schema stays LOOSE — but it now DECLARES the honesty triple (`degraded`, `degradedReason`, `extraction`) that the tRPC door has always forwarded and this one published nowhere. `degradedReason` may be a pod plumbing reason (is_auth_error | is_invalid_response | is_empty_result) or an Intelligence Service extraction reason (vision_provider_not_configured, pdf_scanned_needs_ocr, transcription_provider_not_configured, unsupported_type, …) — the latter are CONFIGURATION states, not outages, and must not be reported to a user as something to retry.

Request body (`application/json`): CaptureStructureRequest

| Field | Type | Required | Description |
|---|---|---|---|
| `userId` | string |  |  |
| `text` | string |  |  |
| `file` | object |  |  |
| `url` | string |  |  |
| `html` | string |  |  |
| `context` | string |  |  |
| `instructions` | string |  |  |
| `workspaceId` | string |  |  |
| `previousEntities` | array<object> |  |  |

Responses:

- `200`: Structured entity proposals
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error

## POST /capture/execute

Materialize structured proposals into entities

Creates the proposed entities and relations from a /capture/structure response.

Request body (`application/json`): CaptureExecuteRequest

| Field | Type | Required | Description |
|---|---|---|---|
| `userId` | string |  |  |
| `workspaceId` | string |  |  |
| `projectId` | string \| null |  |  |
| `entities` | array<object> | yes |  |
| `relations` | array<object> |  |  |
| `targetWorkspaceId` | string \| null |  |  |
| `keepRaw` | boolean |  |  |
| `file` | object |  |  |
| `idempotencyKey` | string |  |  |
| `workspaceRouting` | "auto" \| "ask" \| "locked" |  |  |
| `aiWorkspaceId` | string \| null |  |  |
| `aiWorkspaceConfidence` | number \| null |  |  |
| `aiWorkspaceReason` | string \| null |  |  |
| `aiWorkspaceDecision` | object \| null |  |  |
| `workspaceChoice` | "accepted" \| "changed" \| "removed" \| "ignored" |  |  |
| `aiProjectId` | string \| null |  |  |
| `aiProjectConfidence` | number \| null |  |  |
| `aiProjectReason` | string \| null |  |  |
| `sessionId` | string |  |  |
| `intentText` | string |  |  |

Responses:

- `200`: Created entities + relations
- `400`: Bad request
- `403`: Forbidden
- `500`: Internal error
