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

POST/api/webhook/leadsAll plans

Create 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

POST/api/webhook/leads/{api_key}All plans

Same 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:

  1. Have collected explicit, unchecked-by-default consent on your form (and retain proof — Twilio may request it during a campaign audit)
  2. Send sms_consent: true in the request body
  3. Include a sms_consent_source describing 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.

EventDescription
client.createdClient Created
client.updatedClient Updated
work_order.createdWork Order Created
work_order.status_changedWork Order Status Changed
invoice.createdInvoice Created
invoice.sentInvoice Sent
invoice.paidInvoice Paid
estimate.createdEstimate Created
estimate.sentEstimate Sent
estimate.acceptedEstimate Accepted
estimate.rejectedEstimate Rejected
estimate.convertedEstimate Converted

Getting Started

1

Generate an API key

Go to Organization Settings and click "Generate API Key". Copy and store it securely.

2

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.

3

Send your first lead

POST a JSON payload with name, email, phone, and optional service/notes fields.

4

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.