API reference
A small, predictable REST API and signed outbound webhooks for connecting WyllFlow to your own tools, scripts, and automations like Zapier. Everything is scoped to a single workspace and available on the Pro plan.
Base URL
https://www.wyllflow.com/api/v1
Authentication
Create an API key in Settings → Developer → API keys. The full key is shown once
at creation — store it somewhere safe. Keys are prefixed wf_ and carry either a
read or write scope (a write key can also read).
Send the key as a bearer token on every request. Requests without a valid key return
401; a workspace whose plan no longer includes API access returns 403.
curl https://app.WyllFlow.com/api/v1/me \
-H "Authorization: Bearer wf_live_your_key_here"
Conventions
Rate limiting. Requests are limited to 120 per 60 seconds per key (sliding
window). Over the limit returns 429 — back off and retry.
Money. All amounts are integer minor units encoded as strings — "150000"
means 1,500.00. Never floats; large values are never truncated. Send money the same
way on writes (a string or a safe integer).
Pagination. List endpoints are cursor-based. Pass ?limit= (default 50, max 100)
and ?cursor=. Each response includes a nextCursor, or null on the last page.
Envelopes. A single record reads as { "data": { … } }; a list reads as
{ "data": [ … ], "nextCursor": … }; a successful write returns 201 with the
created record under data.
curl "https://app.WyllFlow.com/api/v1/invoices?limit=25&cursor=clx123" \
-H "Authorization: Bearer wf_live_your_key_here"
Endpoints
Reads cover clients, leads, invoices, estimates, and projects. Writes are limited to
creating clients, leads, draft invoices, and webhook subscriptions — each requires a
write-scope key.
- GET
/meIdentify the workspace (id, name, plan) behind a key. - GET
/clientsList clients. - GET
/clients/:idFetch a single client. - POST
/clientswriteCreate a client. - GET
/leadsList leads. - POST
/leadswriteCreate a lead (e.g. from Facebook Lead Ads via Zapier). - GET
/invoicesList invoices. - GET
/invoices/:idFetch a single invoice. - POST
/invoiceswriteCreate a draft invoice. - GET
/estimatesList estimates. - GET
/estimates/:idFetch a single estimate. - GET
/projectsList projects. - GET
/projects/:idFetch a single project. - POST
/webhookswriteSubscribe an endpoint to an event. - DELETE
/webhooks/:idwriteRemove a webhook subscription.
Objects
Every object exposes ids and safe scalars only — never internal columns or secrets. Timestamps are ISO 8601 strings; money fields are string minor units.
Client
| Field | Type | Notes |
|---|---|---|
id | string | Unique client id. |
name | string | Display name. |
type | string | company or individual. |
email · phone · address | string | null | Optional contact fields. |
defaultCurrency | string | null | ISO 4217 code. |
createdAt | string | ISO 8601. |
Lead
| Field | Type | Notes |
|---|---|---|
id | string | Unique lead id. |
name | string | Contact name. |
email · phone | string | null | Optional contact fields. |
source | string | null | Where the lead came from, e.g. facebook. |
message | string | null | Optional free text. |
status | string | new, contacted, qualified, unqualified, converted. |
createdAt | string | ISO 8601. |
Invoice
| Field | Type | Notes |
|---|---|---|
id · number | string | Id and human number (e.g. INV-0001). |
status | string | draft, sent, partial, paid, overdue, void. |
currency | string | ISO 4217 code. |
subtotal · total · amountPaid · balance | string | Minor units. |
clientId | string | Owning client. |
issueAt · dueAt | string | null | ISO 8601. |
createdAt | string | ISO 8601. |
Estimate
| Field | Type | Notes |
|---|---|---|
id · number | string | Id and human number. |
status | string | Lifecycle status. |
currency | string | ISO 4217 code. |
subtotal · total | string | Minor units. |
clientId | string | Owning client. |
validUntil | string | null | ISO 8601. |
Project
| Field | Type | Notes |
|---|---|---|
id · name | string | Id and name. |
status | string | active, on_hold, completed, archived. |
clientId | string | null | Linked client, if any. |
budgetAmount | string | null | Minor units. |
currency | string | null | ISO 4217 code. |
startAt · endAt | string | null | ISO 8601. |
Creating records
Create a client
Only name is required. type defaults to company.
curl -X POST https://app.WyllFlow.com/api/v1/clients \
-H "Authorization: Bearer wf_live_your_write_key" \
-H "Content-Type: application/json" \
-d '{"name":"Acme Digital Agency","type":"company","email":"ops@acme.co"}'
Create a lead
Only name is required. This is the endpoint behind the Facebook Lead Ads → Zapier
capture path: wire a Zap to POST here on every new Lead Ad submission, and WyllFlow
handles the auto-reply and follow-up from there — exactly as it would for a lead
captured through your own site form.
curl -X POST https://app.WyllFlow.com/api/v1/leads \
-H "Authorization: Bearer wf_live_your_write_key" \
-H "Content-Type: application/json" \
-d '{"name":"Jordan Lee","email":"jordan@example.com","source":"facebook"}'
Create a draft invoice
Requires clientId, a supported currency, and at least one line item. Money fields
(unitPrice, discount, installment amount) are minor units. Dates are ISO 8601.
The invoice is created as a draft — sending it stays a workspace action.
{
"clientId": "clx_client_id",
"currency": "USD",
"dueAt": "2026-08-01",
"lineItems": [
{ "description": "Design retainer", "qty": 1, "unitPrice": "150000" }
]
}
Optional fields include taxInclusive, withholdingRateBps, issueAt, templateId,
notes, terms, invoice-level discount, and installments. Invalid bodies return
422 with the first validation message.
Webhooks
Register endpoints in Settings → Developer → Webhooks (or via the API — see Zapier
below). When a subscribed event fires, WyllFlow POSTs a JSON payload to your URL. The
signing secret is shown once when you create the endpoint.
Events
invoice.paidAn invoice was fully paid.invoice.createdAn invoice was created.estimate.acceptedA client accepted an estimate.estimate.declinedA client declined an estimate.contract.signedA contract was signed.contract.declinedA contract was declined.deal.wonA pipeline deal was marked won.deal.lostA pipeline deal was marked lost.payment.refundedA payment was refunded.client.createdA client was created.lead.created
Delivery. Each delivery is recorded and retried with exponential backoff (up to 5 attempts) on any non-2xx response or timeout — so your endpoint should be idempotent. Every request carries these headers:
X-WyllFlow-Event— the event nameX-WyllFlow-Delivery— a unique delivery id (use it to dedupe)X-WyllFlow-Timestamp— unix secondsX-WyllFlow-Signature— the HMAC signature (below)
A payload looks like:
{ "event": "invoice.paid", "data": { "id": "clx_invoice_id", "status": "paid" } }
Verifying the signature
The signature header is Stripe-style: t=<unix_seconds>,v1=<hex>, where v1 is the
HMAC-SHA256 of "{t}.{rawBody}" using your endpoint secret. Verify with a constant-time
compare and reject timestamps older than 5 minutes.
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=")));
const t = Number(parts.t);
if (!Number.isFinite(t) || !parts.v1) return false;
if (Math.abs(Math.floor(Date.now() / 1000) - t) > toleranceSec) return false;
const expected = createHmac("sha256", secret)
.update(`${t}.${rawBody}`)
.digest("hex");
const a = Buffer.from(parts.v1, "hex");
const b = Buffer.from(expected, "hex");
return a.length === b.length && timingSafeEqual(a, b);
}
Zapier
WyllFlow speaks the REST Hooks pattern Zapier expects, so triggers fire without polling. Use a write-scope key.
- Authenticate. Zapier validates the key against
GET /me, which returns your workspace identity. - Subscribe. When a Zap turns on, Zapier calls
POST /webhookswith aneventand atargetUrl, and stores the returnedid. - Unsubscribe. When the Zap turns off, Zapier calls
DELETE /webhooks/:id. - Receive. Each matching event POSTs the same signed payload to the Zap's
targetUrl.
curl -X POST https://app.WyllFlow.com/api/v1/webhooks \
-H "Authorization: Bearer wf_live_your_write_key" \
-H "Content-Type: application/json" \
-d '{"event":"invoice.paid","targetUrl":"https://hooks.zapier.com/…"}'
Errors
Errors use conventional HTTP status codes and a consistent JSON body:
{ "error": { "code": "unauthorized", "message": "Invalid API key." } }
| Status | Meaning |
|---|---|
401 | Missing, malformed, or revoked key. |
403 | Plan lacks API access, or a read-only key hit a write route. |
404 | Record not found in your workspace. |
422 | Invalid JSON body or failed validation. |
429 | Rate limited. |
Ready to build? Create your first key in Developer settings.