API Documentation
Integrate external tools, capture leads from your website, and automate workflows with the Smarfle API.
Before you start
API Key Auth
Generate API keys in your organization settings. Include the key in theX-API-Keyheader.
Webhook Events
Receive real-time notifications when events happen in your account. Configure endpoints in settings.
Rate Limited
100 requests per minute per API key. All requests are logged and billed at $0.003/call on every plan.
REST Endpoints
/api/webhook/leadsAll plansCreate a lead from your own site's server. API key goes in the x-api-key header. The request (message, page, answers) shows at the top of the client page, and owners and managers get a bell, push and email.
Auth: x-api-key header
/api/webhook/leads/{api_key}All plansSame endpoint with the key in the URL, for form builders that cannot set custom headers. Name, email, phone, address and message fields are recognized; every other field shows in the Request card and fills a custom field with the same name.
Auth: Key in path
Example: Create a lead (no SMS)
curl -X POST https://www.smarfle.com/api/webhook/leads \
-H "Content-Type: application/json" \
-H "X-API-Key: your_api_key_here" \
-d '{
"first_name": "John",
"last_name": "Smith",
"email": "john@example.com",
"phone": "(407) 555-0123",
"address": "12 Oak St, Orlando, FL 32801",
"message": "Water heater is leaking. Can someone come this week?",
"source_page": "https://acme-plumbing.com/water-heater-repair/",
"page_title": "Water Heater Repair",
"custom_fields": { "Service needed": "Water heater repair" },
"details": { "Preferred time": "Mornings", "Referrer": "google.com" }
}'⚠️ SMS Consent (A2P 10DLC compliance)
Lead phones imported via this endpoint default to sms_consent: false. The Smarfle outbound-SMS engine will refuse to text any client whose sms_consent is not true, even if the org owner triggers a campaign or workflow against them. This protects every Smarfle business from TCPA + carrier-policy violations.
To mark the lead as SMS-opted-in, you must:
- Have collected explicit, unchecked-by-default consent on your form (and retain proof — Twilio may request it during a campaign audit)
- Send
sms_consent: truein the request body - Include a
sms_consent_sourcedescribing where consent was collected (e.g."ACME website /quote form, unchecked checkbox, IP 1.2.3.4")
Example: Create a lead WITH SMS consent
curl -X POST https://www.smarfle.com/api/webhook/leads \
-H "Content-Type: application/json" \
-H "X-API-Key: your_api_key_here" \
-d '{
"first_name": "John",
"last_name": "Smith",
"email": "john@example.com",
"phone": "(407) 555-0123",
"sms_consent": true,
"sms_consent_source": "https://acme.com/quote — checkbox unchecked by default, IP captured"
}'Request body
JSON. Two fields are required and the rest are optional. Anything not listed here is ignored rather than rejected.
Required
first_namestring1 to 200 characters.last_namestring1 to 200 characters.Optional
emailstringValid address, max 320. A duplicate returns 409 with the existing client_id; the request is saved as a note on that client.phonestringUS number: 10 digits, or 11 starting with 1. Any other number returns 400. Stored as digits.company_namestringMax 200. The client's company.messagestringMax 5000. What the visitor wrote; shown first in the Request card on the client page.source_pagestringMax 500. The page the form was sent from (full URL).page_titlestringMax 200. That page's name, e.g. "SEO in Orlando".custom_fieldsobjectUp to 30 { "Field name": value }. Fills the business's custom field with the same name (case ignored; a list field takes only one of its options). Every value also shows in the Request card.detailsobjectUp to 20 { "Label": value } rows shown in the Request card: answers, calculator values, referrer, ad click ids.addressstringMax 300. One-line address ("12 Oak St, Orlando, FL 32801"), split into street, city, state and ZIP when the parts are not sent.address_streetstringMax 500.address_unitstringMax 100. Apartment, suite or unit.address_citystringMax 200.address_statestringMax 100.address_zipstringMax 20.notesstringMax 5000. Free text, shown in the Request card. Prefer message + details.sms_consentbooleanDefaults to FALSE. Send true only if you collected consent yourself.sms_consent_sourcestringWhere that consent was collected. Appears in the consent evidence export.utm_sourcestringFirst-touch attribution, flat field.utm_mediumstringFirst-touch attribution, flat field.utm_campaignstringFirst-touch attribution, flat field.branch_iduuidMust belong to your organization and be active, or the call is rejected.Responses
200{ "success": true, "client_id": "uuid" }Client created.
400{ "error": "..." }Validation failed, client limit reached for the plan, or branch_id is not yours.
401{ "error": "Missing x-api-key header" }No key sent, or the key does not match an organization.
409{ "error": "A client with this email already exists", "client_id": "uuid", "request_saved": true }Duplicate email. The new request is saved as a note on the existing client and the business is alerted; the existing id is returned.
429{ "error": "Too many requests..." }Rate limited. Back off and retry.
500{ "error": "An unexpected error occurred" }Our side. Safe to retry.
Verifying a webhook
Every outgoing webhook carries an X-Webhook-Signature header, an HMAC-SHA256 of the raw body using the endpoint secret shown when you created it. Compare it against the body before trusting the payload, and compare in constant time.
import crypto from "crypto";
// body must be the RAW string, not a re-serialised object
function isValid(rawBody, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}The payload envelope is the same for every event.
{
"event": "invoice.paid",
"timestamp": "2026-09-29T14:05:00.000Z",
"data": { }
}Webhook Events
Configure a webhook URL in your organization settings to receive POST requests when these events occur.
| Event | Description |
|---|---|
client.created | Client Created |
client.updated | Client Updated |
work_order.created | Work Order Created |
work_order.status_changed | Work Order Status Changed |
invoice.created | Invoice Created |
invoice.sent | Invoice Sent |
invoice.paid | Invoice Paid |
estimate.created | Estimate Created |
estimate.sent | Estimate Sent |
estimate.accepted | Estimate Accepted |
estimate.rejected | Estimate Rejected |
estimate.converted | Estimate Converted |
Getting Started
Generate an API key
Go to Organization Settings and click "Generate API Key". Copy and store it securely.
Test the endpoint
POST a lead with first_name and last_name. A 200 returns the new client_id; a 401 means the key did not match.
Send your first lead
POST a JSON payload with name, email, phone, and optional service/notes fields.
Set up webhooks (optional)
Add your webhook URL in settings to receive event notifications in real time.
Ready to integrate?
API access is included on every plan. Start your free trial to get your API key.