zestPersonId for use in subscription flows.
Bulk import endpoint
Request body
zestPersonId per row. Use it on subsequent POST /v1/spvs/{slug}/subscriptions, POST .../forms, and POST .../fundings calls to identify the investor. If you ever see 404 not_found on a subscription create for a zestPersonId returned by POST /v1/investors, surface the errorId to Zest support — never retry the investor create as a workaround.
Field reference
Control plane
Personal
Contact
residentialAddress (nested, required)
mailingAddress (nested, required)
Used for tax + subscription document delivery. Often identical to residentialAddress but separated for jurisdictions where they differ.
Tax
Compliance
Identification
Required-nullable convention
Three fields (hasBackupWithholding, qualifiedStatus, passportNumber) are required-nullable: the key MUST appear in your JSON body, but the value MAY be null. Omitting the key entirely returns 400 validation_error; sending null is valid.
Qualified status
qualifiedStatus accepts one of the following enum values, or null:
QUALIFIED_PURCHASERQUALIFIED_CLIENTTRUSTEE_QUALIFIED_PURCHASERKNOWLEDGEABLE_EMPLOYEEKNOWLEDGEABLE_EXECUTIVE
400 validation_error at the request level.
Partial-success semantics
The endpoint always returns200 OK with a per-row result envelope. A single bad row never fails the batch (with the exception of request-level validation errors — see Failure cases below).
Per-row error codes
Correlation: partnerInvestorId
partnerInvestorId is the stable join key between your system and Zest. It must be:
- Unique within your tenant.
- Present on every row (no nulls).
- Stable across retries.
(client_id, partnerInvestorId) -> zestPersonId mapping. Replaying the same id always returns the existing zestPersonId, never a fresh one. This makes the endpoint safe to retry under any failure mode — duplicates are deduplicated.
Idempotency layers
There are two independent layers of replay protection:- Row-level dedup via
partnerInvestorId(always on). - Response caching via
Idempotency-Keyheader (24h TTL, optional).
created rows return already_exists on the second pass.
For interactive single-row creates, add Idempotency-Key so the cached response is served byte-for-byte on retry.
Webhook fan-out
Eachcreated row fires one investor.created webhook with a minimal payload — only the six fields needed for correlation:
taxId, passportNumber, addresses) do not appear in the webhook payload — they stay inside Zest. If you need the full investor record later, request a fetch endpoint (currently a deferred follow-up; raise with Zest support).
already_exists and failed rows do not fire webhooks — replays are silent on the wire.