opbox

The party.* family (12 verbs). Every verb enters through the one front door: it is capability-checked, permission-checked, scope-checked and audited before it runs (see Security & permissions). Each entry below lists its capability, risk class (which fixes the minimum autonomy level), the minimum caller permission, its availability tier, whether it is idempotent, and any outbound egress.

Archiveparty.archive

PropertyValue
Capabilityparty.write
Risk classSensitive - autonomy L2
Min. permissionAdmin
AvailabilityAdmin only (CLI/HTTP; never advertised over MCP)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

ACTIVE|SUSPENDED -> ARCHIVED (+reason); stamps archived_at. Terminal (retained-not-deleted, INV-8): still readable via party.get; reversal is a fresh party.

Call POST /v/party.archive with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.

Createparty.create

PropertyValue
Capabilityparty.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityCore - always on (MCP tier 1)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

{ kind, displayName, identifiers?: [{type, value}], extra? }. WRITE/L1. Creates ONE party row. If identifiers are supplied, each mints a GlobalIdentifier via HMAC (INV-10) - and the dedup unique index is honoured: an identifier already bound (to ANY party in the tenant) is a Conflict carrying the existing party id (the caller routes that to party.merge).

Input

FieldTypeRequiredDescription
kindstringyes
displayNamestringyes

Call POST /v/party.create with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool party.create.

Setparty.facet.set

PropertyValue
Capabilityparty.facet.write
Risk classSensitive - autonomy L2
Min. permissionAdmin
AvailabilityOn demand (MCP tier 2)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

{ partyId, kyc: { kycStatus?, riskLevel?, pepFlag?, sanctionsMatch?, nationality?, passportNumber?, dateOfBirth?, … } }. SENSITIVE/L2/ADMIN. Upserts the party_kyc 1:1 facet. Every PII field is AES-256-GCM-before-write via the SHARED crate::crypto AEAD (INV-7 - not a new cipher), AAD-bound to (workspace, party, field) so a blob moved between rows fails to authenticate. The plaintext PII NEVER reaches a column - only the ciphertext *_enc bytea + the explicit encryption_scheme.

Call POST /v/party.facet.set with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool party.facet.set.

Getparty.get

PropertyValue
Capabilityparty.read
Risk classRead - autonomy L0
Min. permissionMember
AvailabilityCore - always on (MCP tier 1)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)
Render schemaYes - drives an inline chat artefact and a GUI panel

Returns the core, the (in+out) relationship edges, the count of bound identifiers (NEVER the hashes - they are irreversible and not display data, INV-10), and the KYC STANDING (status/risk/flags only - the encrypted PII columns are NOT decrypted into a read, INV-7).

Input

FieldTypeRequiredDescription
partyIdstringyes

Call POST /v/party.get with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool party.get.

Reviewparty.kyc.review

PropertyValue
Capabilityparty.facet.write
Risk classSensitive - autonomy L2
Min. permissionAdmin
AvailabilityOn demand (MCP tier 2)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)
Legal basisCC-OPBOX 27

/Sensitive/ADMIN ([2026] CC-OPBOX 27 / LEXBY-FI 5). The PARTIAL KYC re-verify: rewrites ONLY the supplied STANDING fields on an EXISTING party_kyc row, sets kyc_verified_at, and DERIVES kyc_expires_at (= verified_at + the validity period) on a VERIFIED transition - while PRESERVING every unsupplied column, CRITICALLY every *_enc PII facet (they are NOT in the SET clause, so byte-for-byte untouched - the court-ordered tested invariant). Distinct from party.facet.set (the full-PII (re)load, which deliberately clears stale facets); routing through fact.set is forbidden (the single-value fact store cannot hold party_kyc’s nine AAD-bound encrypted columns). Requires an existing standing (re-verify, not create - use party.facet.set to load first). A VERIFIED write never lands without a kyc_expires_at.

Call POST /v/party.kyc.review with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool party.kyc.review.

Listparty.list

PropertyValue
Capabilityparty.read
Risk classRead - autonomy L0
Min. permissionMember
AvailabilityCore - always on (MCP tier 1)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)
Render schemaYes - drives an inline chat artefact and a GUI panel

List parties in the workspace (filter kind/status), paginated + RLS-scoped. Returns the slim standing (id/kind/displayName/status), NOT the unredacted facets (those stay behind party.get, INV-7). Computes nothing authoritative (INV-14).

Input

FieldTypeRequiredDescription

Call POST /v/party.list with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool party.list.

Mergeparty.merge

PropertyValue
Capabilityparty.merge
Risk classSensitive - autonomy L2
Min. permissionAdmin
AvailabilityAdmin only (CLI/HTTP; never advertised over MCP)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

The dedup-fold: source is folded INTO target (source → status MERGED, merged_into_id = target). The merge is GATED on confidence against MERGE_CONFIDENCE_FLOOR (INV-10): • confidence ≥ floor → MERGE (re-point source’s identifiers + relationships to target, fold source). • confidence < floor → NO merge; open a Review IDENTITY-lane item (the human decides). Returns { merged:false, reviewId } - the substrate NEVER auto-merges below the floor. The match key is the HMAC GlobalIdentifier (no plaintext); merging re-binds source’s identifiers to target (a re-bind that would collide on target’s own identity is the index’s job to reject).

Input { sourcePartyId, targetPartyId, confidence } (fields ending in ? are optional)

Call POST /v/party.merge with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.

Profileparty.profile

PropertyValue
Capabilityparty.read
Risk classRead - autonomy L0
Min. permissionMember
AvailabilityOn demand (MCP tier 2)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)
Render schemaYes - drives an inline chat artefact and a GUI panel

The compliance-officer STANDING view of one party: the core, a SUMMARY of its relationship edges (counts by status, never the full graph - that stays behind party.get/ link.query), and the KYC STANDING (status/risk/flags/dates/nationality ONLY). NO decrypted PII ever crosses this read (INV-7): the *_enc ciphertext columns are NOT selected, and the identifier hashes are NOT returned (INV-10, a hash is not display data) - only a count of bound identities. Workspace- scoped via the explicit workspace_id = $ predicate + RLS; a party in another workspace reads back not-found (anti-enumeration, the same shape party.get uses).

Input { partyId } (fields ending in ? are optional)

Call POST /v/party.profile with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool party.profile.

Reactivateparty.reactivate

PropertyValue
Capabilityparty.write
Risk classSensitive - autonomy L2
Min. permissionAdmin
AvailabilityAdmin only (CLI/HTTP; never advertised over MCP)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

SUSPENDED -> ACTIVE (+reason). Refuses ARCHIVED (terminal).

Call POST /v/party.reactivate with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.

Listparty.relationship.list

PropertyValue
Capabilityparty.read
Risk classRead - autonomy L0
Min. permissionMember
AvailabilityCore - always on (MCP tier 1)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)
Render schemaYes - drives an inline chat artefact and a GUI panel

The workspace’s party relationships ([2026] CC-OPBOX 24 gate (b)): the party_relationship rows the link writers mint (directors/shareholders/UBOs/secretaries with role, share %, appointed/resigned, nominee) but which NO read verb surfaced - the company-registers read. Slim standing; numeric/date cast to text so no decimal/date dep. { role?, status?, partyId? }, paginated, RLS-scoped (workspace-global, exactly like party.list - parties carry no per-row ACL). No new store/write/state (the lawful gate-(b) substrate-extension; extends the existing party.* namespace).

Call POST /v/party.relationship.list with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool party.relationship.list.

Suspendparty.suspend

PropertyValue
Capabilityparty.write
Risk classSensitive - autonomy L2
Min. permissionAdmin
AvailabilityAdmin only (CLI/HTTP; never advertised over MCP)
IdempotentYes
EgressNone - in-box (pure Postgres, no outbound call)

ACTIVE -> SUSPENDED (+reason). Reversible via reactivate.

Call POST /v/party.suspend with a JSON body; returns JSON (or { error, code }). Not advertised over MCP (admin-only); reachable via the CLI and HTTP doors.

Updateparty.update

PropertyValue
Capabilityparty.write
Risk classWrite - autonomy L1
Min. permissionMember
AvailabilityCore - always on (MCP tier 1)
IdempotentNo
EgressNone - in-box (pure Postgres, no outbound call)

Edit a party’s core fields (currently displayName). One write path (INV-1). An empty update is rejected (no silent touch); a missing party is a not-found BadInput.

Call POST /v/party.update with a JSON body; returns JSON (or { error, code }). Also exposed as the MCP tool party.update.