> ## Documentation Index
> Fetch the complete documentation index at: https://vetta.sh/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Legal

> Identity-owned verification cases and company formations: hosted verification links, formation filing, and documents.

Every route is nested under an identity. `{identity}` accepts an `idn_…` id or the identity's name.
An identity may hold any number of **verifications** (a case with one or more members who each
complete a hosted verification) and **companies** (a formation that references a verified case).
Money is integer micro-USD (`1 USD = 1000000`).

<Note>On a deployment with no legal provider bound, every route below answers `501 feature_not_configured`.</Note>

## Verifications

### List verifications

`GET /v1/identities/{identity}/legal/verifications` — scope `agents:read`

Returns a [paginated list](/docs/api/pagination) of `legal_verification` objects, newest first.

### Start a verification

`POST /v1/identities/{identity}/legal/verifications` — scope `billing:write`

```json theme={"system"}
{
  "members": [
    { "first_name": "Ava", "last_name": "Stone", "email": "ava@example.com",
      "role": "primary", "is_responsible_party": true, "ownership_percent": 100 }
  ]
}
```

Exactly one member is `primary` and exactly one is the responsible party; ownership percentages
sum to 100. Answers `201` with the case; the primary member's `link_url` is set on this response
only, and every secondary member is emailed their own link (a secondary who could not be emailed
gets `link_url` here too). Honours `Idempotency-Key`.

```json theme={"system"}
{
  "id": "lgv_01j9x2k7q4m8n3p5r6s7t8v9wa",
  "object": "legal_verification",
  "identity_id": "idn_01j9x2k7q4m8n3p5r6s7t8v9wb",
  "status": "in_progress",
  "ready_for_formation": false,
  "members": [
    { "id": "lgm_01j9x2k7q4m8n3p5r6s7t8v9wc", "object": "legal_member",
      "verification_id": "lgv_01j9x2k7q4m8n3p5r6s7t8v9wa",
      "first_name": "Ava", "last_name": "Stone", "email": "ava@example.com",
      "role": "primary", "is_responsible_party": true, "ownership_percent": 100,
      "status": "link_ready", "link_url": "https://verify.example.com/s/…",
      "link_expires_at": "2026-09-09T00:00:00Z", "completed_at": null }
  ],
  "started_at": "2026-09-08T00:00:00Z", "completed_at": null,
  "created_at": "2026-09-08T00:00:00Z", "updated_at": "2026-09-08T00:00:00Z"
}
```

Member status moves `pending → link_ready | link_sent → in_progress → pending_review | verified | failed`.
`verified` and `failed` are terminal and are never changed by a later callback or read. The case is
`failed` if any member failed, else `pending_review` if any member is under review, else `verified`
once every member is verified, else `in_progress`.

### Read a verification

`GET /v1/identities/{identity}/legal/verifications/{verification}` — scope `agents:read`

The case with its members, `link_url` always `null`. Each unfinished member's status is refreshed
from the provider on this read, so a missed callback is caught up. `ready_for_formation` is `true`
once every member is `verified`.

### Complete a member

`POST /v1/identities/{identity}/legal/verifications/{verification}/members/{member}/complete` — scope `billing:write`

Pass `{ "token": "…" }` from the hosted page's redirect. Answers the `legal_member`.

### Resend a member's link

`POST /v1/identities/{identity}/legal/verifications/{verification}/members/{member}/resend` — scope `billing:write`

Mints a fresh hosted link for a member who has not finished. Answers the `legal_member` with
`link_url` set. A `verified` or `failed` member answers `state_conflict`.

## Companies

### List companies

`GET /v1/identities/{identity}/legal/companies` — scope `agents:read`

### Form a company

`POST /v1/identities/{identity}/legal/companies` — scope `billing:write`

```json theme={"system"}
{
  "verification_id": "lgv_…",
  "state": "WY",
  "name_options": ["Acme Labs"],
  "naics_code": "541511",
  "description": "Software consulting",
  "mailing_address": { "line1": "1 Main St", "line2": null, "city": "Cheyenne", "state": "WY", "postal_code": "82001", "country": "US" }
}
```

The verification must be `verified` (`state_conflict` otherwise). The fee — the formation price
plus the state's filing fee, both reported by `GET /v1/legal/states` — is debited from the
organization's credits **before** anything is filed; a short balance answers `402 insufficient_credits`.
Name endings are canonicalised to `LLC`. Answers `201` with status `paid`. Honours `Idempotency-Key`.

```json theme={"system"}
{
  "id": "lgc_01j9x2k7q4m8n3p5r6s7t8v9wd",
  "object": "legal_company",
  "identity_id": "idn_01j9x2k7q4m8n3p5r6s7t8v9wb",
  "verification_id": "lgv_01j9x2k7q4m8n3p5r6s7t8v9wa",
  "status": "paid",
  "entity_type": "llc",
  "state": "WY",
  "name_options": [{ "name": "Acme Labs", "ending": "LLC" }],
  "legal_name": null,
  "naics_code": "541511",
  "description": "Software consulting",
  "mailing_address": { "line1": "1 Main St", "line2": null, "city": "Cheyenne", "state": "WY", "postal_code": "82001", "country": "US" },
  "ein_status": "none",
  "price_micro_usd": 449000000,
  "required_actions": [],
  "failure_reason": null,
  "submitted_at": null, "formed_at": null,
  "created_at": "2026-09-08T00:00:00Z", "updated_at": "2026-09-08T00:00:00Z"
}
```

`price_micro_usd` is what was debited: `449000000` is \$449.00.

### Read a company

`GET /v1/identities/{identity}/legal/companies/{company}` — scope `agents:read`

Status moves `awaiting_payment → paid → submitted → formed | failed`. `required_actions` lists
anything a person still has to do.

### Submit a company

`POST /v1/identities/{identity}/legal/companies/{company}/submit` — scope `billing:write`

Files the formation. Idempotent once submitted (a second call answers the same `submitted`
company); a company that already `failed` answers `state_conflict`. A provider refusal is
recorded as status `failed` with a provider-neutral `failure_reason` and answered as the company,
not as an error.

### Documents

`GET /v1/identities/{identity}/legal/companies/{company}/documents` — scope `agents:read`

`GET /v1/identities/{identity}/legal/companies/{company}/documents/{document}` — scope `agents:read`

The first lists `legal_document` objects (`kind` is `articles`, `ein_letter`, `operating_agreement`
or `other`); the second answers a short-lived `legal_document_link`:

```json theme={"system"}
{ "object": "legal_document_link", "url": "https://files.example.com/…", "expires_at": "2026-09-08T01:00:00Z" }
```

## Reference data

`GET /v1/legal/naics_codes?q=software` — scope `agents:read`

`GET /v1/legal/states` — scope `agents:read`

Both are paginated. `naics_code` is `{ "code", "title" }`; `legal_state` carries the state's filing
fee, which is added to the formation price:

```json theme={"system"}
{ "object": "legal_state", "code": "WY", "name": "Wyoming", "formation_fee_micro_usd": 100000000 }
```

## Verification callback

`POST /v1/webhooks/legal` — no scope; the verification provider calls it

The provider's signature is verified over the raw body before anything is parsed; a body that does
not verify answers `401 unauthorized`. A verified event flips the named member to `verified`,
`failed` or `pending_review` and recomputes the case. A member already in a terminal state is left
as it is, so a redelivered event changes nothing. Answers `202 { "object": "legal_webhook", "accepted": true | false }`;
`false` means the event named a member this deployment does not hold or one already settled.

## Errors

`validation_failed` for member, name, state and description rules; `state_conflict` for a company on
an unverified case or a second submit of a failed one; `job_not_ready` when reference data cannot be
answered right now; `insufficient_credits`; `feature_not_configured`. See [Errors](/docs/api/errors).
