Launch offer: 25% off Solo & Pro monthly for your first 3 monthsSee pricing →

Developer platform · Pro

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

FieldTypeNotes
idstringUnique client id.
namestringDisplay name.
typestringcompany or individual.
email · phone · addressstring | nullOptional contact fields.
defaultCurrencystring | nullISO 4217 code.
createdAtstringISO 8601.

Lead

FieldTypeNotes
idstringUnique lead id.
namestringContact name.
email · phonestring | nullOptional contact fields.
sourcestring | nullWhere the lead came from, e.g. facebook.
messagestring | nullOptional free text.
statusstringnew, contacted, qualified, unqualified, converted.
createdAtstringISO 8601.

Invoice

FieldTypeNotes
id · numberstringId and human number (e.g. INV-0001).
statusstringdraft, sent, partial, paid, overdue, void.
currencystringISO 4217 code.
subtotal · total · amountPaid · balancestringMinor units.
clientIdstringOwning client.
issueAt · dueAtstring | nullISO 8601.
createdAtstringISO 8601.

Estimate

FieldTypeNotes
id · numberstringId and human number.
statusstringLifecycle status.
currencystringISO 4217 code.
subtotal · totalstringMinor units.
clientIdstringOwning client.
validUntilstring | nullISO 8601.

Project

FieldTypeNotes
id · namestringId and name.
statusstringactive, on_hold, completed, archived.
clientIdstring | nullLinked client, if any.
budgetAmountstring | nullMinor units.
currencystring | nullISO 4217 code.
startAt · endAtstring | nullISO 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 name
  • X-WyllFlow-Delivery — a unique delivery id (use it to dedupe)
  • X-WyllFlow-Timestamp — unix seconds
  • X-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.

  1. Authenticate. Zapier validates the key against GET /me, which returns your workspace identity.
  2. Subscribe. When a Zap turns on, Zapier calls POST /webhooks with an event and a targetUrl, and stores the returned id.
  3. Unsubscribe. When the Zap turns off, Zapier calls DELETE /webhooks/:id.
  4. 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." } }
StatusMeaning
401Missing, malformed, or revoked key.
403Plan lacks API access, or a read-only key hit a write route.
404Record not found in your workspace.
422Invalid JSON body or failed validation.
429Rate limited.

Ready to build? Create your first key in Developer settings.