SaarFlow by Techsaar

SaarFlow API

Let your own software send WhatsApp on your own number — your billing system, your booking site, your school dashboard — without it ever holding a Meta token.

What this is

SaarFlow already holds your WhatsApp connection: the number, the approved templates, the webhooks, the inbox your team answers from. This API lets another system of yours ask SaarFlow to send one of those approved templates, and read back what happened to each message.

It is send-only, deliberately. Replies from customers arrive in the SaarFlow inbox where a person can answer them; nothing is pushed back to your software. That keeps one record of every conversation instead of two halves that disagree.

Why not call Meta directly?

Because then your software needs a copy of the token that can send as your business, create and delete your templates, and change where your webhooks point. One copy, on our server, is easier to protect than two — and messages sent this way still appear in your inbox thread, so whoever answers the reply can see what was sent.

Get a key

In SaarFlow: Settings → API keys → Create key. The key is shown once, starts with sf_live_, and only its hash is stored — we cannot show it to you again, and a copy of our database cannot send messages as you. Revoke it from the same screen at any time.

Keep it on your server. A key in browser JavaScript is a key anyone can read.

Authentication

Every request carries the key in a header. Base URL:

# Base URL
https://saarflow.com/api/v1/ext

# Every request
X-API-Key: sf_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json

List your templates

GET /templates The approved templates you may send, and how many variables each takes.
curl -s https://saarflow.com/api/v1/ext/templates \
  -H "X-API-Key: $SAARFLOW_API_KEY"
{
  "data": [
    {
      "name": "fee_pending_reminder",
      "language": "hi",
      "category": "UTILITY",
      "variables": 0,
      "preview": "प्रिय अभिभावक / Dear Parents, …"
    }
  ]
}

variables is how many {{n}} the body contains. Send exactly that many values, in order. Only APPROVED templates are listed — a draft or rejected one is not sendable.

Which number it sends from

GET /number The WhatsApp number these messages will come from.
{ "phoneNumber": "917581010627", "displayName": "Mahaveer Jain National School" }

Send to one person

POST /messages/template One approved template to one number.
curl -s -X POST https://saarflow.com/api/v1/ext/messages/template \
  -H "X-API-Key: $SAARFLOW_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "to": "9685171608",
    "template": "fee_pending_reminder",
    "reference": "STU-1042"
  }'
FieldMeaning
torequiredAny shape: 9685171608, 919685171608, +91 96851 71608.
templaterequiredIts name from /templates.
languageoptionalOnly needed if you have the same template name in two languages.
variablesoptionalArray of strings, in {{1}}, {{2}}… order.
referenceoptionalYours — a student id, an invoice number. It comes back in the report.
{ "id": "9b88915c-…", "status": "QUEUED",
  "message": "Queued. Poll /ext/broadcasts/{id} for the delivery report." }

202 Accepted: the reply is immediate and the sending happens in the background. Keep the id — it is how you read the report.

Send to a list

POST /messages/bulk One template to many people, each with their own values. Up to 20,000 per call.
curl -s -X POST https://saarflow.com/api/v1/ext/messages/bulk \
  -H "X-API-Key: $SAARFLOW_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{
    "template": "payment_reminder",
    "name": "October defaulters",
    "recipients": [
      { "to": "9685171608", "reference": "STU-1042", "variables": ["Rahul", "2"] },
      { "to": "9826000000", "reference": "STU-1043", "variables": ["Priya", "1"] }
    ],
    "ratePerSecond": 10,
    "dailyCap": 900
  }'
{ "id": "9b88915c-…", "status": "QUEUED", "total": 2,
  "unusableNumbers": 0, "ratePerSecond": 10, "dailyCap": 900 }
FieldMeaning
templaterequiredMust be APPROVED.
recipientsrequiredArray of { to, variables?, reference? }. Maximum 20,000.
nameoptionalWhat to call this send in SaarFlow.
ratePerSecondoptionalDefault 10, maximum 40.
dailyCapoptionalDefault 900. It never raises WhatsApp's own limit, only keeps you below it.

Read the delivery report

GET /broadcasts/{id}?recipients=true Counts, and one line per person.
{
  "id": "9b88915c-…", "name": "October defaulters", "status": "SENDING",
  "total": 2333,
  "counts": { "pending": 1433, "sent": 900, "skipped": 0, "failed": 0,
              "delivered": 812, "read": 466, "undelivered": 88 },
  "pausedReason": null, "resumeAfter": null,
  "recipients": [
    { "to": "919685171608", "reference": "STU-1042", "state": "READ", "sentAt": "…" },
    { "to": "91900000",    "reference": "STU-1044", "state": "FAILED",
      "error": "Not a usable phone number" }
  ]
}

Leave ?recipients=true off for the counts alone — lighter when you are polling.

stateMeans
PENDINGQueued, not sent yet
SENTWhatsApp accepted it
DELIVEREDIt reached the phone
READThey opened it
FAILEDCould not be sent — error says why
SKIPPEDDuplicate in the list, or the number opted out

Poll every 10–15 seconds while a send is running. There is no webhook back to you by design.

Recent sends

GET /broadcasts The last sends from this account, newest first.

Stop a send

POST /broadcasts/{id}/cancel Stops whatever has not gone out yet. Messages already delivered cannot be recalled.

Pacing and the daily limit

WhatsApp limits how many people a number may start a conversation with in 24 hours — commonly 1,000 for a new number, rising as it keeps a good quality rating. A list of 2,333 is therefore not one send.

A broadcast counts everything that number has already sent in the last 24 hours, and when the allowance is gone it stops by itself:

{ "status": "PAUSED_DAILY_CAP",
  "pausedReason": "The number's daily allowance (900) is used up. Sending resumes automatically.",
  "resumeAfter": "2026-10-04T18:30:00.000Z" }

It continues the next morning with nobody pressing anything. Show pausedReason to your user and a three-day send explains itself. The same happens if WhatsApp starts rate-limiting the number.

What it does for you

  • The same number twice in one list is sent once. A parent with two children is not told twice; the duplicate comes back as SKIPPED.
  • A number that cannot be a phone number is reported as FAILED in the report rather than failing your whole request.
  • Anyone who opted out is never sent to, whoever asks.
  • Every message lands in that person's inbox thread, so your team sees what was sent before they answer the reply.

Errors

CodeMeaning
401The key is missing, wrong or revoked
402The SaarFlow account's plan has lapsed — the reply carries who to call
400Missing template or recipients, or the template is not approved
404No template by that name on this account

Every error is { "error": "…", "message": "…" }, and message is written to be shown to a person as it is.

Help

Stuck, or want an endpoint that is not here? Write to support@techsaar.com or message +91 96851 71608 — you will reach the people who built it.

Not a developer?

You do not need this. SaarFlow's own Campaigns screen sends the same messages to your contacts, your ad leads, a CRM stage or an uploaded list — with the same pacing and the same reports. See what SaarFlow costs.