Developers

Unfold Equity API & Webhooks

A versioned REST API for grants, people, plans, awards, Section 16, ASC 718, and tax data — plus HMAC-signed webhooks for real-time events. Everything is tenant-scoped to your API key.

Machine-readable spec: /api/v1/openapi.json

Authentication

Create a personal API key in Settings → Security (shown once). Send it as a Bearer token on every request. Keys are scoped to your active company — all data you read or write belongs to that tenant.

curl https://www.unfoldingvalues.com/api/v1/grants \
  -H "Authorization: Bearer ue_live_xxxxxxxxxxxx"

Base URL, versioning & rate limits

Response envelope

Success responses wrap data + meta; errors use a consistent error object.

// success
{ "data": { ... }, "meta": { "request_id": "...", "version": "v1", "timestamp": "..." } }

// error
{ "error": { "code": "validation_failed", "message": "...", "details": { ... }, "request_id": "..." } }

Endpoints

MethodPathDescription
GET/api/v1/grantsList grants (filter by state, plan_id, recipient_id, dates)
POST/api/v1/grantsPropose a grant (full compliance + approval workflow)
GET/api/v1/grants/{id}Get a single grant
GET/api/v1/peopleList people (filter by classification, insider)
POST/api/v1/peopleCreate a person
GET/api/v1/plansList active plans
GET/api/v1/plans/{id}Get a plan + its sub-buckets
GET/api/v1/bucketCurrent bucket-balance roll-up
GET/api/v1/awardsList awards (filter by status, plan_id)
GET/api/v1/awards/{id}Get an award + vest schedule + issuance events
GET/api/v1/section-16/reviewsList Section 16 reviews
GET/api/v1/section-16/reviews/{id}Get a Section 16 review
GET/api/v1/section-16/filingsList filed Form 3/4/5
GET/api/v1/section-16/filings/{id}Get a filing (includes form_4_xml)
GET/api/v1/asc-718/expenseASC 718 expense rows (period_start, period_end)
GET/api/v1/tax-eventsTaxable equity events (year)
GET/api/v1/audit-logImmutable audit trail (super_admin keys)
GET/api/v1/webhooksList webhook subscriptions (super_admin)
POST/api/v1/webhooksCreate a subscription (secret shown once)
GET/api/v1/webhooks/deliveriesRecent webhook deliveries (debugging)

Webhooks

Subscribe an https endpoint to events in Settings → Webhooks. Each delivery is a JSON POST signed with HMAC-SHA256. Retries use exponential backoff (2, 4, 8, 16, 32 minutes); 10 consecutive failures auto-disable the endpoint.

Event types

grant.proposed Grant proposed
grant.approved Grant approved (all approvers cleared)
grant.granted Grant granted (award created)
grant.rejected Grant rejected
grant.withdrawn Grant withdrawn
award.issued Shares issued
award.vested Shares vested
section_16.review_created Section 16 review created
section_16.filing_completed Section 16 filing completed
document.generated Document generated
83b.deadline_approaching 83(b) election deadline approaching
webhook.test Test event (sent from the dashboard)

Delivery headers

Sample payload

{
  "event_id": "b3f1c2a4-...-...",
  "event_type": "grant.granted",
  "timestamp": "2026-05-28T14:03:11.482Z",
  "company_id": "1111....-1111-1111-1111-............",
  "data": {
    "grant_id": "....",
    "award_id": "....",
    "recipient_id": "....",
    "shares": 12500,
    "award_type": "RSU"
  }
}

Verify the signature — Node.js

import crypto from "node:crypto";

// Express example. Capture the RAW body (do not re-serialize before verifying).
function verify(rawBody, signatureHeader, secret) {
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(rawBody, "utf8")
    .digest("hex");
  const a = Buffer.from(signatureHeader);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/webhooks/ue", express.raw({ type: "application/json" }), (req, res) => {
  const sig = req.get("X-UE-Signature");
  if (!verify(req.body, sig, process.env.UE_WEBHOOK_SECRET)) {
    return res.status(401).send("bad signature");
  }
  const event = JSON.parse(req.body);
  // event.event_id is an idempotency key — dedupe on it.
  res.sendStatus(200);
});

Verify the signature — Python

import hmac, hashlib

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header or "")

# Flask example
@app.post("/webhooks/ue")
def ue_webhook():
    if not verify(request.get_data(), request.headers.get("X-UE-Signature"), UE_WEBHOOK_SECRET):
        return ("bad signature", 401)
    event = request.get_json()
    # event["event_id"] is an idempotency key — dedupe on it.
    return ("", 200)

Machine-readable reference

The complete OpenAPI 3.0 specification is served at /api/v1/openapi.json. Import it into Postman, Insomnia, Scalar, or any OpenAPI client to generate an SDK or an interactive explorer. (We serve the raw spec rather than embedding a third-party renderer to keep the Content-Security-Policy strict — no external script origins.)