SynapSynapDocs
Hub REST API

Proposals

The Proposals operations of the Hub REST API (9 operations).

For BuildersOpen-source podSynap CloudGenerated from Synap-core/backend@c31e261ab (main)
POST
/documents/proposals

Authorization

bearerAuth
headerAuthorizationBearer <API key>

Pod API key (Bearer) issued via /api/hub/setup/agent or pod admin tooling. Browser/web clients may use the X-Session-Token header instead. See GET /api/hub/auth/status to introspect the calling key.

Request Body

application/json
  1. body
documentId*string
userId*string
agentUserId?string
sourceMessageId?string
sessionId?string
proposedContent*string
baseRevision?integer
Range0 <= value
allowRemovingEmbeds?boolean
reasoning?string

Response Body

Created proposal

application/json
  1. response
id*string
status?string
[key: string]?unknown
Example Request
curl -X POST 'https://pod.example.com/api/hub/documents/proposals' \  -H 'Content-Type: application/json' \  -d '{  "documentId": "string",  "userId": "string",  "proposedContent": "string"}'
Example Response
{  "id": "string",  "status": "string",  "property1": null,  "property2": null}

List proposals

GET
/proposals

Authorization

bearerAuth
headerAuthorizationBearer <API key>

Pod API key (Bearer) issued via /api/hub/setup/agent or pod admin tooling. Browser/web clients may use the X-Session-Token header instead. See GET /api/hub/auth/status to introspect the calling key.

Query Parameters

userId?string
workspaceId?string
status?string

Defaults to pending. Use all to return every status. auto_approved returns the audit receipts of agent writes that were executed immediately under governance rather than queued for review.

Value in"pending""approved""rejected""auto_approved""reverted""approval_failed""withdrawn""expired""all"
view?string

full (DEFAULT) returns the complete proposal row including the unbounded data payload. basic returns identity + provenance scalars and a capped summary, with NO data — use it to enumerate a queue without paying for every payload.

Value in"full""basic"
limit?integer

Page size. Defaults to 50, clamped to 200.

Range1 <= value <= 200
offset?|

Rows to skip (native SQL OFFSET). Defaults to 0. The response carries total and hasMore, so a caller never has to infer the size of the queue from the size of the page.

Range0 <= value

Response Body

Proposals — full rows by default, basic rows when view=basic

application/json
  1. response
proposals*array<>|array<>
limit*number

Effective page size after clamping.

offset*number

Effective offset after clamping.

hasMore*boolean

True when at least one row exists past this page — the signal that distinguishes a full page from the end of the queue.

Example Request
curl -X GET 'https://pod.example.com/api/hub/proposals'
Example Response
{  "proposals": [    {      "id": "string",      "workspaceId": "string",      "targetType": "string",      "targetId": "string",      "proposalType": "string",      "data": {        "property1": null,        "property2": null      },      "status": "pending",      "agentUserId": "string",      "threadId": "string",      "sourceMessageId": "string",      "createdBy": "string",      "createdAt": "string",      "updatedAt": "string",      "sessionId": "string",      "class": "ephemeral",      "lifetimeHours": 0,      "setup": {        "params": [          {            "name": "string",            "label": "string",            "type": "string",            "required": true,            "description": "string",            "secret": true,            "satisfied": true,            "ref": "string",            "refName": "string",            "refService": "string",            "refUnresolved": true          }        ],        "connection": {          "required": true,          "kind": "string",          "provider": "string",          "state": "string",          "property1": null,          "property2": null        },        "blocking": true,        "nextAction": {          "kind": "string",          "hint": "string",          "url": "string",          "opensIn": "web",          "property1": null,          "property2": null        }      }    }  ],  "limit": 0,  "offset": 0,  "hasMore": true}

Create a proposal

POST
/proposals

Authorization

bearerAuth
headerAuthorizationBearer <API key>

Pod API key (Bearer) issued via /api/hub/setup/agent or pod admin tooling. Browser/web clients may use the X-Session-Token header instead. See GET /api/hub/auth/status to introspect the calling key.

Request Body

application/json
  1. body
workspaceId?string|null
agentUserId?string
channelId?string

Channel/thread originating the proposal.

targetType*string

Subject type the proposal mutates (e.g. entity, view).

targetId*string

ID of the target subject (existing record or stable temp id).

proposalType*string

Sub-action, e.g. entity.create, view.update, vault.request.

data*

Free-form proposal payload — shape is targetType-specific.

summary?string
sessionId?string

Focus session ID to link this proposal to.

sourceMessageId?string

Message that originated the proposal — for event chain causality.

Response Body

Created proposal

application/json
  1. response
id*string
status*"pending"
Value in"pending"
Example Request
curl -X POST 'https://pod.example.com/api/hub/proposals' \  -H 'Content-Type: application/json' \  -d '{  "targetType": "string",  "targetId": "string",  "proposalType": "string",  "data": {    "property1": null,    "property2": null  }}'
Example Response
{  "id": "string",  "status": "pending"}
GET
/proposals/{id}

Authorization

bearerAuth
headerAuthorizationBearer <API key>

Pod API key (Bearer) issued via /api/hub/setup/agent or pod admin tooling. Browser/web clients may use the X-Session-Token header instead. See GET /api/hub/auth/status to introspect the calling key.

Path Parameters

id*string

Response Body

The proposal

application/json
  1. response
id*string
workspaceId*string|null
targetType*string
targetId*string
proposalType*string
data*
status*ProposalRowStatus
Value in"pending""approved""rejected""auto_approved""reverted""approval_failed""withdrawn""expired"
agentUserId?string|null
threadId?string|null
sourceMessageId?string|null
createdBy?string|null
createdAt?string|
updatedAt?string|
sessionId?string|null
class*ProposalClass

Decision class — derived from proposalType x targetType, not stored.

Value in"ephemeral""curatorial""objectWork""governance""access"
lifetimeHours*|

Hours this proposal stays answerable once its context is gone. null for every class that never expires; only ephemeral has one.

setup?
Example Request
curl -X GET 'https://pod.example.com/api/hub/proposals/string'
Example Response
{  "id": "string",  "workspaceId": "string",  "targetType": "string",  "targetId": "string",  "proposalType": "string",  "data": {    "property1": null,    "property2": null  },  "status": "pending",  "agentUserId": "string",  "threadId": "string",  "sourceMessageId": "string",  "createdBy": "string",  "createdAt": "string",  "updatedAt": "string",  "sessionId": "string",  "class": "ephemeral",  "lifetimeHours": 0,  "setup": {    "params": [      {        "name": "string",        "label": "string",        "type": "string",        "required": true,        "description": "string",        "secret": true,        "satisfied": true,        "ref": "string",        "refName": "string",        "refService": "string",        "refUnresolved": true      }    ],    "connection": {      "required": true,      "kind": "string",      "provider": "string",      "state": "string",      "property1": null,      "property2": null    },    "blocking": true,    "nextAction": {      "kind": "string",      "hint": "string",      "url": "string",      "opensIn": "web",      "property1": null,      "property2": null    }  }}
PATCH
/proposals/{id}

Authorization

bearerAuth
headerAuthorizationBearer <API key>

Pod API key (Bearer) issued via /api/hub/setup/agent or pod admin tooling. Browser/web clients may use the X-Session-Token header instead. See GET /api/hub/auth/status to introspect the calling key.

Path Parameters

id*string

Request Body

application/json
  1. body
data*

Updated proposal payload (replaces existing data).

summary?string

Human-readable summary of the revision.

Response Body

Updated proposal

application/json
  1. response
id*string
workspaceId*string|null
targetType*string
targetId*string
proposalType*string
data*
status*ProposalRowStatus
Value in"pending""approved""rejected""auto_approved""reverted""approval_failed""withdrawn""expired"
agentUserId?string|null
threadId?string|null
sourceMessageId?string|null
createdBy?string|null
createdAt?string|
updatedAt?string|
sessionId?string|null
class*ProposalClass

Decision class — derived from proposalType x targetType, not stored.

Value in"ephemeral""curatorial""objectWork""governance""access"
lifetimeHours*|

Hours this proposal stays answerable once its context is gone. null for every class that never expires; only ephemeral has one.

setup?
Example Request
curl -X PATCH 'https://pod.example.com/api/hub/proposals/string' \  -H 'Content-Type: application/json' \  -d '{  "data": {    "property1": null,    "property2": null  }}'
Example Response
{  "id": "string",  "workspaceId": "string",  "targetType": "string",  "targetId": "string",  "proposalType": "string",  "data": {    "property1": null,    "property2": null  },  "status": "pending",  "agentUserId": "string",  "threadId": "string",  "sourceMessageId": "string",  "createdBy": "string",  "createdAt": "string",  "updatedAt": "string",  "sessionId": "string",  "class": "ephemeral",  "lifetimeHours": 0,  "setup": {    "params": [      {        "name": "string",        "label": "string",        "type": "string",        "required": true,        "description": "string",        "secret": true,        "satisfied": true,        "ref": "string",        "refName": "string",        "refService": "string",        "refUnresolved": true      }    ],    "connection": {      "required": true,      "kind": "string",      "provider": "string",      "state": "string",      "property1": null,      "property2": null    },    "blocking": true,    "nextAction": {      "kind": "string",      "hint": "string",      "url": "string",      "opensIn": "web",      "property1": null,      "property2": null    }  }}
POST
/proposals/{id}/revert

Authorization

bearerAuth
headerAuthorizationBearer <API key>

Pod API key (Bearer) issued via /api/hub/setup/agent or pod admin tooling. Browser/web clients may use the X-Session-Token header instead. See GET /api/hub/auth/status to introspect the calling key.

Path Parameters

id*string

Request Body

application/json
  1. body
reason?string

Response Body

Revert applied

application/json
  1. response
success*boolean
reverted*
Example Request
curl -X POST 'https://pod.example.com/api/hub/proposals/string/revert' \  -H 'Content-Type: application/json' \  -d '{}'
Example Response
{  "success": true,  "reverted": {    "entityIds": [      "string"    ],    "relationIds": [      "string"    ],    "documentIds": [      "string"    ]  }}
POST
/proposals/{id}/approve

Authorization

bearerAuth
headerAuthorizationBearer <API key>

Pod API key (Bearer) issued via /api/hub/setup/agent or pod admin tooling. Browser/web clients may use the X-Session-Token header instead. See GET /api/hub/auth/status to introspect the calling key.

Path Parameters

id*string

Request Body

application/json
  1. body
identityResolution?

Weak same-name choice for a single entity/create. fill_empty, keep_existing, and use_capture require existingEntityId. separate does not. Absent ⇒ today's create.

identityResolutionByRef?

Composite per-create choice, keyed by the entity ref dispositions use. One entry is not applied to any other op.

Response Body

Proposal approved

application/json
  1. response
success*boolean
proposalId*string
proposal?
primaryId?string

The row the approval produced or LINKED to, when the executor reports one — e.g. the focus session a focus_session/create approval inserted, or the existing open session it reused.

linked?number

How many existing rows the approval linked to instead of creating (e.g. 1 when an open session of the same goal and scope already existed).

identity?

Set when an identity resolution ran. Not stored on the proposal row — only this response carries it. fill_empty includes filled and conflicts.

identities?array<>

Composite: one entry per op that ran a resolution. Singular identity is also set when exactly one op resolved.

Example Request
curl -X POST 'https://pod.example.com/api/hub/proposals/string/approve' \  -H 'Content-Type: application/json' \  -d '{}'
Example Response
{  "success": true,  "proposalId": "string",  "proposal": {    "id": "string",    "workspaceId": "string",    "targetType": "string",    "targetId": "string",    "proposalType": "string",    "data": {      "property1": null,      "property2": null    },    "status": "pending",    "agentUserId": "string",    "threadId": "string",    "sourceMessageId": "string",    "createdBy": "string",    "createdAt": "string",    "updatedAt": "string",    "sessionId": "string",    "class": "ephemeral",    "lifetimeHours": 0,    "setup": {      "params": [        {          "name": "string",          "label": "string",          "type": "string",          "required": true,          "description": "string",          "secret": true,          "satisfied": true,          "ref": "string",          "refName": "string",          "refService": "string",          "refUnresolved": true        }      ],      "connection": {        "required": true,        "kind": "string",        "provider": "string",        "state": "string",        "property1": null,        "property2": null      },      "blocking": true,      "nextAction": {        "kind": "string",        "hint": "string",        "url": "string",        "opensIn": "web",        "property1": null,        "property2": null      }    }  },  "primaryId": "string",  "linked": 0,  "identity": {    "verb": "fill_empty",    "entityId": "string",    "filled": [      "string"    ],    "conflicts": [      {        "key": "string",        "kept": null,        "incoming": null      }    ]  },  "identities": [    {      "ref": "string",      "verb": "fill_empty",      "entityId": "string",      "filled": [        "string"      ],      "conflicts": [        {          "key": "string",          "kept": null,          "incoming": null        }      ]    }  ]}
POST
/proposals/{id}/reject

Authorization

bearerAuth
headerAuthorizationBearer <API key>

Pod API key (Bearer) issued via /api/hub/setup/agent or pod admin tooling. Browser/web clients may use the X-Session-Token header instead. See GET /api/hub/auth/status to introspect the calling key.

Path Parameters

id*string

Request Body

application/json
  1. body
reason?string
reasonCode?string
Value in"wrong_entity""duplicate""wrong_kind_or_facet""wrong_link_type""wrong_workspace""bad_data""not_relevant""other"

Response Body

Proposal rejected

application/json
  1. response
success*boolean
proposalId*string
Example Request
curl -X POST 'https://pod.example.com/api/hub/proposals/string/reject' \  -H 'Content-Type: application/json' \  -d '{}'
Example Response
{  "success": true,  "proposalId": "string"}
POST
/proposals/dev-approval

Authorization

bearerAuth
headerAuthorizationBearer <API key>

Pod API key (Bearer) issued via /api/hub/setup/agent or pod admin tooling. Browser/web clients may use the X-Session-Token header instead. See GET /api/hub/auth/status to introspect the calling key.

Request Body

application/json
  1. body
type*string

Which dev-loop gate is being filed.

Value in"dev.plan_approval""dev.deploy_approval"
workspaceId?string|null
projectId?string|null
channelId?string
sourceMessageId?string
payload*|

The gate payload — shape is determined by type.

Response Body

The filed gate proposal

application/json
  1. response
id*string
status*string
sessionId*string
Example Request
curl -X POST 'https://pod.example.com/api/hub/proposals/dev-approval' \  -H 'Content-Type: application/json' \  -d '{  "type": "dev.plan_approval",  "payload": {    "sessionId": "f6567dd8-e069-418e-8893-7d22fcf12459",    "repo": "string",    "branch": "string",    "planMarkdown": "string",    "gateCommand": "string"  }}'
Example Response
{  "id": "string",  "status": "string",  "sessionId": "string"}