Can UBO information be submitted separately from the business?

Follow-up probe to the MT failure-webhooks spike (RENTAL-594) · run 2026-07-07 against the shared Modern Treasury sandbox · raw REST, no SDK · scripts 07-ubo-probe.mjs / 08-ubo-attach-variants.mjs, wire captures under snapshots/07-ubo-probe/ and snapshots/08-ubo-attach-variants/ · card commerce#insane-dynamic-shield

Verdict: an owner's personal details can be sent to MT at any time — but declaring them an owner of a business can only happen in the API call that creates the business. MT stores two separate things: the person (an individual legal entity — name, DOB, SSN, address) and the ownership link tying that person to a business (the association — "beneficial owner, 40%"). The people are flexible; the links are frozen at business create.

Why we ran this

David asked (comment on commerce#retire-lawsuit-intact) whether UBO information can be submitted separately from the business — e.g. business first, owners later. The original spike never exercised associations, but its captures hinted "probably yes": MT models UBOs as separate legal entities linked via legal_entity_associations, the create-time completeness checklist has no beneficial-owner rule, and PATCH re-triggers verification. The probe was run to confirm before answering. The hint was wrong.

What was attempted

Every plausible API surface for attaching a UBO after the business exists:

#AttemptResultEvidence
1 POST /api/legal_entities with embedded legal_entity_associations (control) 201 — works 08/05-embedded-at-create.json
2 PATCH /api/legal_entities/:id, association with embedded child individual 200, silently dropped 07/03-patch-attach-ubo.json
3 PATCH /api/legal_entities/:id, association by child_legal_entity_id to a pre-created individual 200, silently dropped 08/03-patch-link-by-id.json
4 POST /api/legal_entity_associations (standalone resource) 404 — no such path 07/04-standalone-association.json
5 POST /api/legal_entities/:id/legal_entity_associations (nested route) 404 — no such path 08/04-nested-route.json
6 POST /api/legal_entities creating a standalone individual (a would-be UBO on its own) 201 — works 08/01-individual-create.json
7 POST /api/legal_entities (business create) referencing the pre-created individual by child_legal_entity_id 201 — works 09/02-create-link-by-id.json
8 PATCH /api/legal_entities/:id with legal_entity_associations: [] (remove all owners) 200, silently dropped 10/02-patch-empty-array.json
9 PATCH /api/legal_entities/:id with a subset of the owners (remove one of two) 200, silently dropped 10/03-patch-subset.json
10 DELETE /api/legal_entity_associations/:id 404 — no such path 10/04-delete-association.json
11 DELETE /api/legal_entities/:child_id (discard the person outright) 404 — no DELETE on legal entities 10/05-delete-child-entity.json
12 PATCH /api/legal_entities/:child_id amending the person's details (email) 200 — works 10/01-patch-child-details.json

Evidence paths are relative to snapshots/; 07/ = 07-ubo-probe, 08/ = 08-ubo-attach-variants, 09/ = 09-link-by-id-at-create, 10/ = 10-ubo-removal (a business created with two embedded owners; attempts 8–12 all ran against it). Each file holds the exact request body sent and the raw response. Attempt 7 was added after David pointed at MT's "Using existing child legal entities" guide: the individual from attempt 6 was linked at a fresh business create — association persisted (ownership_percentage: 40 round-tripped) and the business activated. Note the docs' constraint: an association takes child_legal_entity or child_legal_entity_id, never both.

The two decisive wire captures

PATCH is a silent no-op, not a rejection

Business 912a0ee8 was created with no associations and auto-activated. A PATCH carrying a complete beneficial owner (name, DOB, address, SSN, 40% ownership) returned 200 OK — and the response body still shows "legal_entity_associations": []. No updated event was ever emitted (the events log stayed [activated, created] through 30s of polling), so MT didn't just decline the field — it never registered a change at all. The same happened when linking a pre-created individual by id (attempt 3).

PATCH /api/legal_entities/912a0ee8-5d48-46ba-b2ba-39ece2354082
{ "legal_entity_associations": [ { "relationship_types": ["beneficial_owner"],
    "ownership_percentage": 40, "child_legal_entity": { ...complete individual... } } ] }

→ HTTP 200
   "legal_entity_associations": []        ← field silently dropped
   events after 30s: [activated, created] ← no "updated" ever fired

Embedded at create is the one path that works

The identical association payload, embedded in a fresh business create, persists fully: the association gets an id, the child becomes a real legal entity with its own id and pending status, and ownership_percentage round-trips.

POST /api/legal_entities   (business + embedded legal_entity_associations)

→ HTTP 201, parent 89fb5992-5647-4424-acdd-57469b9d3ae4 (status: pending)
   association 30224e62-c5c1-47eb-9a34-5d05f11066b0
     relationship_types: ["beneficial_owner"], ownership_percentage: 40
     child_legal_entity: 7fe6fe37-f26d-4cbc-9b0e-b78cb80bde24
       (type: individual, status: pending)

Corroboration against MT's docs and SDK

The sandbox probes were run first from prior API knowledge plus two guessed paths; the findings were then checked against Modern Treasury's public documentation and their official (Stainless-generated) Node SDK on 2026-07-07. Everything lines up — and adds a twist:

Method notes

What this means for us

For MT support: the standalone associations endpoint was removed from the API around early April 2026 (see corroboration above) — the question is "what is the intended path for adding, amending, or removing UBOs after entity creation?", not whether a newer version has one. Related: legal entities have no DELETE, so how is a dead entity discarded to free its EIN for a re-create? Separately, Persona doesn't run in sandbox, so whether production verification holds or fails a business with missing UBOs is invisible here; bundle with the existing profile-configurability question.

File inventory

FileContents
07-ubo-probe.mjsCreate association-less business → PATCH embedded UBO → standalone endpoint → event polling
08-ubo-attach-variants.mjsStandalone individual create → link-by-id PATCH → nested route → embedded-at-create control
snapshots/07-ubo-probe/7 request/response captures incl. the silent-drop PATCH and 404
snapshots/08-ubo-attach-variants/6 captures incl. the working embedded-at-create response
NOTES.mdParent spike writeup; UBO findings appended as a follow-up section