A subscription is a single investor’s commitment to deploy capital into a single SPV at a specified share class. Partners create subscriptions in bulk per SPV viaDocumentation Index
Fetch the complete documentation index at: https://docs.zestequity.com/llms.txt
Use this file to discover all available pages before exploring further.
POST /v1/spvs/{slug}/subscriptions.
Internal model
Internally, every subscription auto-creates a Bid row withsource=partner-api. There is no separate “Subscription” collection on the Zest side — the partner-visible projection is curated from the Bid.
This matters because:
- Zest’s internal investment workflow already operates on Bids; partner-sourced subscriptions inherit that machinery for free.
- Admin-side actions (e.g. mark-as-completed) are Bid actions that emit subscription-shaped webhooks (
subscription.completed).
subscriptionSlug is stable across the lifecycle.
Server-side projections
Each row in a successfulPOST /v1/spvs/{slug}/subscriptions call produces three internal rows as part of the same request:
| Row | Visible to partner? | Purpose |
|---|---|---|
Bid (source = partner-api) | Yes (as the subscription). | Drives the existing internal investment workflow. |
Shadow OpportunityBidder (status: partner-managed) | No. | Lets the existing Zest admin UI list partner-sourced subscriptions alongside Zest-native bidders for the same Opportunity. Read-only; partner actions still flow through the v1 endpoints. |
| Stored payload metadata on the Bid (partner ids, share class) | Echoed in webhooks. | Lets webhook envelopes carry partnerSubscriptionId / partnerInvestorId without an extra lookup. |
Lifecycle
subscription.completed delivery
subscription.completed is eventually consistent:
- When a Zest admin marks the underlying Bid
Completed, the system attempts to emitsubscription.completedsynchronously. - If that synchronous emit was skipped (admin path bypassed the helper, queue back-pressure, transient error), a reconciler actor runs on a 60-second cadence and emits the webhook for any partner-sourced Bid whose
partner_webhook_subscription_completed_fired_atflag is still unset.
- You will receive exactly one
subscription.completedper subscription, even if the synchronous path drops it. - The webhook may arrive up to ~60 seconds after the admin action, not instantly.
- Idempotency is enforced via the flag — the reconciler never double-fires.
Strict order: forms before fundings
POST .../fundings returns 409 conflict when called before POST .../forms succeeds. This enforces compliance: Zest must hold the signed subscription contract before any wire-transfer money is associated with the subscription.
If you orchestrate uploads in parallel, gate the funding upload on a successful signed-form response (or on the corresponding webhook).
Per-row payload
Each row in the bulk request carries:| Field | Required | Notes |
|---|---|---|
partnerSubscriptionId | optional | Echoed in the response and on every downstream webhook for correlation. |
zestPersonId | required | Returned from POST /v1/investors. |
lumpSum | required | { "currency": "USD", "value": "50000.00" }. Currency is ISO 4217. |
shareClassSlug | required | Slug of the share class on the SPV. |
lumpSum.value is a decimal string — never a float — to avoid binary floating-point drift on monetary amounts.
Idempotency
Idempotency-Key is optional on POST /v1/spvs/{slug}/subscriptions. Replays under the same key + same body return the cached response (24h TTL). See Idempotency.
Upload constraints
Bothforms and fundings endpoints accept multipart uploads with:
- Max size: 10 MB.
- Allowed content types:
application/pdf,image/jpeg,image/png,image/webp.
413 payload_too_large. Wrong content-type → 415 unsupported_media_type.