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.
KYC documents
POST /v1/investors. The request is multipart/form-data.
Fields
Send any combination of these fields. At least one is required.Rules
- A request may send any subset of the fields. You can upload everything at once or in several requests.
- Each request is stored separately. A later upload never overwrites earlier evidence, so to correct a document, upload it again.
- Each file is at most 10 MB and must be PDF, JPEG, PNG or WEBP.
approvalCommentsis at most 5000 characters (after trimming); longer text returns400 invalid_request.approvalCommentsorapprovalDocumentsatisfies thecompliance-approvalrequirement. Sending both is fine.- Only documents uploaded through this endpoint by your application count toward the requirement.
- An unknown
zestPersonId, an investor created by another partner, and an investor you did not create all return the same404 not_found.
Response
A successful request returns201 Created. missingRequiredTypes lists what is still outstanding. Subscriptions for the investor are refused until it is empty.
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.