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

# Quickstart

> Get from zero to first SPV request in under 10 minutes.

<Note>
  The examples below use `http://localhost:8080` as the API base URL for local development against the open-source reference implementation. Production base URLs (sandbox + live) are communicated during partner onboarding.
</Note>

This walkthrough creates an SPV request end to end using the sandbox environment. By the end you will have:

1. Exchanged a partner JWT for an access token.
2. Listed available contract templates.
3. Submitted an SPV creation request with `Idempotency-Key`.
4. Verified the resulting `spv_request.created` webhook.

<Note>
  You need a sandbox `client_id`, the matching EdDSA private key, and a registered webhook URL + signing secret. Email `sara@zestholdco.com` to provision.
</Note>

## 1. Mint a JWT assertion

Build a JWT with the following claims:

| Claim       | Value                                |
| ----------- | ------------------------------------ |
| `iss`       | Your API base URL                    |
| `sub`       | Your `client_id`                     |
| `aud`       | `https://sandbox-api.zestequity.com` |
| `client_id` | Your `client_id`                     |
| `iat`       | now (unix seconds)                   |
| `exp`       | now + 60                             |

Sign it with EdDSA using the private key registered with Zest.

<Note>
  New to JWT assertions? See [Curity's JWT-assertion walkthrough](https://curity.io/resources/learn/jwt-assertion/) for an end-to-end primer on the grant type.
</Note>

## 2. Exchange it for an access token

```bash theme={null}
curl -X POST https://sandbox-api.zestequity.com/v1/oauth2/tokens \
  -H "Accept: application/json" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \
  -d "assertion=<JWT>"
```

Response:

```json theme={null}
{
  "accessToken": "eyJ...",
  "refreshToken": "eyJ...",
  "tokenType": "Bearer",
  "expiresIn": 3600,
  "refreshExpiresIn": 2592000
}
```

## 3. Submit an SPV request

<Note>
  Your `templateId` is assigned by Zest during partner onboarding. Use the value you were given for `<your_template_id>` below — see [Contract templates](/concepts/spv-requests#contract-templates) for details.
</Note>

```bash theme={null}
curl -X POST https://sandbox-api.zestequity.com/v1/spv-requests \
  -H "Accept: application/json" \
  -H "Authorization: Bearer eyJ..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "templateId": "<your_template_id>",
    "templateVersion": "1.0.0",
    "attributes": {
      "deal_name": "Acme Holdings Series A Syndicate",
      "deal_slug": "acme-series-a-2026",
      "company_id": "<your_company_id>",
      "deal_type": "<your_deal_type>",
      "security_type": "Equity",
      "currency": "USD",
      "price_per_share": {"currency": "USD", "value": "36.97"},
      "is_fixed_price": true,
      "minimum_ticket_size": {"currency": "USD", "value": "1000"},
      "start_date": "2026-06-01T00:00:00Z",
      "status_tag": "Live",
      "deal_stage": "executing",
      "investment_summary": "Subscription into Acme Holdings Series A shares.",
      "deal_documents_title": "Acme Series A Deck",
      "deal_documents_url": "https://example.com/acme-deck.pdf",
      "deal_documents_media_type": "pdf",
      "share_class_name": "Class A",
      "share_class_slug": "acme-class-a",
      "link_enabled": true,
      "private_code": "ACME01"
    }
  }'
```

Successful response (`201 Created`):

```json theme={null}
{
  "spvRequestSlug": "svr_a1b2c3",
  "status": "pending-review",
  "templateId": "<your_template_id>",
  "templateVersion": "1.0.0",
  "tenantSlug": "acme",
  "clientId": "client_xyz",
  "attributes": { "...": "..." },
  "createdAt": "2026-05-07T12:00:00Z",
  "updatedAt": "2026-05-07T12:00:00Z"
}
```

## 4. Same call in Python

```python theme={null}
import os
import uuid
import time
import jwt
import requests

CLIENT_ID = os.environ["ZEST_CLIENT_ID"]
PRIVATE_KEY = open(os.environ["ZEST_PRIVATE_KEY_PATH"]).read()
BASE_URL = "https://sandbox-api.zestequity.com"

now = int(time.time())
assertion = jwt.encode(
    {
        "iss": "https://your-api.example.com",
        "sub": CLIENT_ID,
        "aud": BASE_URL,
        "client_id": CLIENT_ID,
        "iat": now,
        "exp": now + 60,
    },
    PRIVATE_KEY,
    algorithm="EdDSA",
)

token = requests.post(
    f"{BASE_URL}/v1/oauth2/tokens",
    data={
        "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
        "assertion": assertion,
    },
).json()["accessToken"]

resp = requests.post(
    f"{BASE_URL}/v1/spv-requests",
    headers={
        "Authorization": f"Bearer {token}",
        "Idempotency-Key": str(uuid.uuid4()),
    },
    json={
        "templateId": "<your_template_id>",
        "templateVersion": "1.0.0",
        "attributes": {
            "deal_name": "Acme Holdings Series A Syndicate",
            "deal_slug": "acme-series-a-2026",
            "company_id": "<your_company_id>",
            "deal_type": "<your_deal_type>",
            "security_type": "Equity",
            "currency": "USD",
            "price_per_share": {"currency": "USD", "value": "36.97"},
            "is_fixed_price": True,
            "minimum_ticket_size": {"currency": "USD", "value": "1000"},
            "start_date": "2026-06-01T00:00:00Z",
            "status_tag": "Live",
            "deal_stage": "executing",
            "investment_summary": "Subscription into Acme Holdings Series A shares.",
            "deal_documents_title": "Acme Series A Deck",
            "deal_documents_url": "https://example.com/acme-deck.pdf",
            "deal_documents_media_type": "pdf",
            "share_class_name": "Class A",
            "share_class_slug": "acme-class-a",
            "link_enabled": True,
            "private_code": "ACME01",
        },
    },
)
print(resp.status_code, resp.json())
```

## 5. Same call in Node.js

```js theme={null}
import { SignJWT, importPKCS8 } from "jose";
import { randomUUID } from "node:crypto";

const CLIENT_ID = process.env.ZEST_CLIENT_ID;
const PRIVATE_KEY_PEM = process.env.ZEST_PRIVATE_KEY;
const BASE_URL = "https://sandbox-api.zestequity.com";

async function run() {
  const privateKey = await importPKCS8(PRIVATE_KEY_PEM, "EdDSA");
  const now = Math.floor(Date.now() / 1000);

  const assertion = await new SignJWT({ client_id: CLIENT_ID })
    .setProtectedHeader({ alg: "EdDSA" })
    .setIssuer("https://your-api.example.com")
    .setSubject(CLIENT_ID)
    .setAudience(BASE_URL)
    .setIssuedAt(now)
    .setExpirationTime(now + 60)
    .sign(privateKey);

  const tokenRes = await fetch(`${BASE_URL}/v1/oauth2/tokens`, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
      assertion,
    }),
  });
  const { accessToken } = await tokenRes.json();

  const res = await fetch(`${BASE_URL}/v1/spv-requests`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${accessToken}`,
      "Idempotency-Key": randomUUID(),
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      templateId: "<your_template_id>",
      templateVersion: "1.0.0",
      attributes: {
        deal_name: "Acme Holdings Series A Syndicate",
        deal_slug: "acme-series-a-2026",
        company_id: "<your_company_id>",
        deal_type: "<your_deal_type>",
        security_type: "Equity",
        currency: "USD",
        price_per_share: { currency: "USD", value: "36.97" },
        is_fixed_price: true,
        minimum_ticket_size: { currency: "USD", value: "1000" },
        start_date: "2026-06-01T00:00:00Z",
        status_tag: "Live",
        deal_stage: "executing",
        investment_summary: "Subscription into Acme Holdings Series A shares.",
        deal_documents_title: "Acme Series A Deck",
        deal_documents_url: "https://example.com/acme-deck.pdf",
        deal_documents_media_type: "pdf",
        share_class_name: "Class A",
        share_class_slug: "acme-class-a",
        link_enabled: true,
        private_code: "ACME01",
      },
    }),
  });
  console.log(res.status, await res.json());
}

run();
```

## 6. Verify the webhook

You should receive a `POST` to your webhook URL with body:

```json theme={null}
{
  "eventId": "wde_...",
  "eventType": "spv_request.created",
  "occurredAt": "2026-05-07T12:00:00Z",
  "data": {
    "spvRequestSlug": "svr_a1b2c3",
    "templateId": "<your_template_id>",
    "templateVersion": "1.0.0",
    "tenantSlug": "acme",
    "attributes": { "...": "..." },
    "status": "pending-review"
  }
}
```

Verify the `Zest-Signature` header before processing — see [Verification](/webhooks/verification).

## Next steps

* [Authentication](/authentication) — JWT-bearer details and common pitfalls.
* [SPV Requests](/concepts/spv-requests) — full lifecycle.
* [Webhooks Overview](/webhooks/overview) — event shape, ordering, dedup.
