API reference

Leads

Public endpoints for website forms and callback requests, and the endpoints to read, update, export and delete the leads they create.

A lead is what a visitor sends through a website form: a contact request, a demo request, a newsletter sign-up, a callback request and so on. Three public endpoints accept submissions from a website without a login. The other endpoints let a workspace list, update, export and erase its leads, and switch on "call visitors back".

OperationMethod and pathMinimum role
Get a form tokenGET /public/form-tokennone
Submit a leadPOST /public/leadsnone
Request a callbackPOST /public/callbacknone
List leadsGET /leadsviewer
Export leads as CSVGET /leads/export.csvviewer
Get a leadGET /leads/{lead_id}viewer
Update a leadPATCH /leads/{lead_id}member
Delete a leadDELETE /leads/{lead_id}member
Get lead settingsGET /leads/settingsviewer
Update lead settingsPATCH /leads/settingsadmin

Warning: POST /public/callback can place a real phone call to the number it is given when a workspace has switched on calls to visitors and telephony is live. Do not test it with numbers you do not control.

How public submissions are protected

The public endpoints take no credentials, so each submission passes these checks. The limits are operator-configured; the defaults are shown.

ProtectionHow it works
Per-address limit60 public requests per client address per hour, valid or not. The token endpoint allows four times that.
Per-email limit5 submissions per email address per day.
Per-phone limitOne callback request per phone number per day.
Form tokenEvery submission carries a token from GET /public/form-token. It is refused if sent sooner than 2 seconds after it was issued, if older than 2 hours, if forged, or if already used. An invalid submission does not use up its token.
HoneypotA submission with a non-empty website field is answered 201 with a made-up id and stored nowhere. Keep that field hidden and empty.
Strict fieldsUnknown fields are refused. Control characters are refused. Text is stored HTML-escaped and returned as stored.
Consentconsent must be true.

The visitor's address is never stored, only a keyed hash of it. A website on another origin can call these endpoints only if the operator has allowed that origin (CORS).

Who owns a lead

  1. If the submission carries a valid site_key, the lead belongs to the workspace that key belongs to. Each workspace has one publishable site key, shown by GET /leads/settings.
  2. Otherwise it belongs to the operator's designated site-owner account, if one is configured.
  3. Otherwise it is stored without an owner. Leads without an owner are visible to the first account created on the platform and to the admins of its organisation.

Public endpoints

Get a form token

GET /public/form-token

Returns a signed token to include in a form submission. Fetch it when the form is shown, not when it is sent, so that the minimum fill time has passed.

Auth: none.

Request

bash
curl https://api.sevennadi.si/public/form-token

Response 200 OK

json
{"token": "1760340000.Zm9ybS1ub25jZQ.5b0c2f0e9d7a4c1b8e6f3a2d1c0b9a8f7e6d5c4b3a2918070605040302010f0e", "min_seconds": 2}

min_seconds is how long to wait before submitting. The token's format is not part of the contract; send it back unchanged.

Errors

StatusMessage
429Too many requests from this address. Try again later.

Submit a lead

POST /public/leads

Stores a lead from any website form. If the operator configured a leads webhook, the new lead is also POSTed there once.

Auth: none.

ParameterInTypeRequiredDescription
kindbodystringyescontact, demo, newsletter, callback, pilot, careers or builder.
consentbodybooleanyesMust be true.
tokenbodystringyesFrom GET /public/form-token.
pagebodystringyesThe site path the form is on, for example /contact. Up to 300 characters.
emailbodystringyes, except for callbackUp to 254 characters.
phonebodystringfor callbackE.164. Spaces, hyphens, dots and brackets are removed first.
namebodystringnoUp to 120 characters.
companybodystringnoUp to 160 characters.
messagebodystringnoUp to 4000 characters; may contain new lines and tabs.
utmbodyobjectnoAny of utm_source, utm_medium, utm_campaign, utm_term, utm_content, referrer. Values up to 200 characters.
detailsbodyobjectnoUp to 12 short answers specific to the form. Keys are lower-case identifiers (letters, digits, _, up to 40 characters); values up to 500 characters.
site_keybodystringnoA workspace's site key. See Who owns a lead.
websitebodystringnoThe honeypot. Leave it empty.
Idempotency-KeyheaderstringnoMakes a retry safe: the same key with the same request returns the first response. See Idempotency.

Request

bash
curl -X POST https://api.sevennadi.si/public/leads \
  -H "Content-Type: application/json" \
  -d '{
    "kind": "demo",
    "name": "Meera Iyer",
    "email": "meera@example.test",
    "company": "Example Bank",
    "message": "We would like a demo for our collections team.",
    "page": "/contact",
    "utm": {"utm_source": "newsletter"},
    "consent": true,
    "token": "TOKEN_FROM_FORM_TOKEN"
  }'

Response 201 Created

json
{"id": "a3f1c2d4-5b6a-4c7d-8e9f-0a1b2c3d4e5f"}

Errors

StatusMessage
400The request body must be a JSON object
400Unknown field budget
400kind must be one of: contact, demo, newsletter, callback, pilot, careers, builder
400consent is required: please agree to be contacted
400email is required
400email is not a valid email address
400phone is required (for callback)
400phone must be in E.164 format, e.g. +919876543210
400page must be the site path the form is on, e.g. /contact
400name must be at most 120 characters (and the same for other fields)
400name contains control characters
400details: at most 12 entries
400details: unknown or invalid key (also for utm)
400The form token is missing or invalid. Reload the page and try again.
400The form was sent too quickly. Please wait a moment and try again.
400The form has expired. Reload the page and try again.
400The form was already sent. Reload the page to send another.
400site_key is not valid
429Too many requests from this address. Try again later.
429Too many requests for this email address. Try again tomorrow.

Request a callback

POST /public/callback

Records a lead of kind callback and, if the owning workspace allows it, queues a call from that workspace's chosen agent to the visitor.

Auth: none.

ParameterInTypeRequiredDescription
phonebodystringyesE.164.
consentbodybooleanyesMust be true.
tokenbodystringyesFrom GET /public/form-token.
namebodystringnoUp to 120 characters. Passed to the agent as a call variable.
languagebodystringnoStored in the lead's details and passed to the agent.
agentbodystringnoStored in the lead's details.
pagebodystringnoDefault /.
utmbodyobjectnoAs in Submit a lead.
site_keybodystringnoA workspace's site key.
websitebodystringnoThe honeypot. Leave it empty.
Idempotency-KeyheaderstringnoMakes a retry safe: the same key with the same request returns the first response. See Idempotency.

Request

bash
curl -X POST https://api.sevennadi.si/public/callback \
  -H "Content-Type: application/json" \
  -d '{"phone": "+919876500000", "name": "Meera", "language": "en", "consent": true, "token": "TOKEN_FROM_FORM_TOKEN"}'

Response 201 Created

json
{"id": "a3f1c2d4-5b6a-4c7d-8e9f-0a1b2c3d4e5f", "queued": false}

The lead is always stored. queued is true only when a call was placed in the queue. A call is queued only when every check below passes; otherwise the reason is in the lead's callback_state and the team follows up by hand.

Checkcallback_state when it fails
The number is not on the workspace's do-not-call listsuppressed
The workspace switched on calls to visitorsnot_enabled
The operator's telephony is live (not mock)telephony_not_live
An agent is chosen and still existsno_agent
The number is verified, if the workspace only calls verified numbersnot_verified
The daily attempt cap for the number is not reachedfrequency_cap
Now is inside the agent's calling hours (9:00 to 21:00 in the number's time zone if the agent sets none)outside_hours

A request outside calling hours is not scheduled for later. A queued call is an ordinary execution in the workspace's call history, with user_data.source set to website_callback; the lead's callback_state is queued and execution_id is set.

Errors

The validation, token and address-limit errors of Submit a lead apply, plus:

StatusMessage
400Unknown field email (only the fields above are accepted)
429A call to this number was already requested today.

Workspace endpoints

List leads

GET /leads

Lists the leads the caller's account may see, newest first, with counts by status.

Auth: API key or session token. Role: viewer and up.

ParameterInTypeRequiredDescription
kindquerystringnoOne lead kind.
statusquerystringnonew, contacted, qualified or closed.
date_fromquerystringnoA date (YYYY-MM-DD) or ISO 8601 timestamp; leads created at or after it.
date_toquerystringnoA date or ISO 8601 timestamp. A bare date includes that whole day.
searchquerystringnoMatches name, email, phone, company or message. Up to 100 characters.
page_numberqueryintegernoFrom 1. Default 1.
page_sizequeryintegerno1 to 100. Default 20.

Request

bash
curl "https://api.sevennadi.si/leads?status=new&page_size=20" \
  -H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"

Response 200 OK

json
{
  "page_number": 1,
  "page_size": 20,
  "total": 1,
  "has_more": false,
  "by_status": {"new": 1, "contacted": 0, "qualified": 0, "closed": 0},
  "data": [
    {
      "id": "a3f1c2d4-5b6a-4c7d-8e9f-0a1b2c3d4e5f",
      "kind": "demo",
      "status": "new",
      "name": "Meera Iyer",
      "email": "meera@example.test",
      "phone": null,
      "company": "Example Bank",
      "message": "We would like a demo for our collections team.",
      "page": "/contact",
      "utm": {"utm_source": "newsletter"},
      "details": {},
      "consent": true,
      "consent_at": "2026-10-13T09:00:00Z",
      "notes": null,
      "callback_state": null,
      "execution_id": null,
      "owned": true,
      "created_at": "2026-10-13T09:00:00Z",
      "updated_at": "2026-10-13T09:00:00Z",
      "humanized_created_at": "just now"
    }
  ]
}

by_status counts all visible leads, not only the filtered ones. owned is false for a lead stored without an owner. Text fields are HTML-escaped as stored (& is returned as &); unescape them before display.

Errors

StatusMessage
400page_number must be >= 1 and page_size between 1 and 100
400kind must be one of: contact, demo, newsletter, callback, pilot, careers, builder
400status must be one of: new, contacted, qualified, closed
400from must be a date (YYYY-MM-DD) or an ISO 8601 timestamp (for date_from; to must be ... for date_to)

Export leads as CSV

GET /leads/export.csv

Returns the matching leads as a CSV file, newest first, up to 10,000 rows. The filters are the same as for List leads, without paging.

Auth: API key or session token. Role: viewer and up.

ParameterInTypeRequiredDescription
kind, status, date_from, date_to, searchquerystringnoAs in List leads.

Request

bash
curl -o leads.csv "https://api.sevennadi.si/leads/export.csv?status=new" \
  -H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"

Response 200 OK with Content-Type: text/csv

text
id,created_at,kind,status,name,email,phone,company,message,page,details,utm,consent,callback_state,notes
a3f1c2d4-5b6a-4c7d-8e9f-0a1b2c3d4e5f,2026-10-13T09:00:00Z,demo,new,Meera Iyer,meera@example.test,,Example Bank,We would like a demo for our collections team.,/contact,,utm_source=newsletter,yes,,

In the CSV, text is unescaped back to plain text, details and utm are written as key=value pairs separated by ;, and a cell that would start with a formula character (=, +, -, @) gets a leading apostrophe. Phone numbers are left as they are.

Errors

The filter errors of List leads.

Get a lead

GET /leads/{lead_id}

Returns one lead.

Auth: API key or session token. Role: viewer and up.

ParameterInTypeRequiredDescription
lead_idpathstring (UUID)yesThe lead's id.

Request

bash
curl https://api.sevennadi.si/leads/a3f1c2d4-5b6a-4c7d-8e9f-0a1b2c3d4e5f \
  -H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"

Response 200 OK

One lead object, as in List leads.

Errors

StatusMessage
404Lead not found

Update a lead

PATCH /leads/{lead_id}

Changes a lead's status or notes. Nothing else can be changed.

Auth: API key or session token. Role: member and up.

ParameterInTypeRequiredDescription
lead_idpathstring (UUID)yesThe lead's id.
statusbodystringnonew, contacted, qualified or closed.
notesbodystringnoUp to 4000 characters.

Request

bash
curl -X PATCH https://api.sevennadi.si/leads/a3f1c2d4-5b6a-4c7d-8e9f-0a1b2c3d4e5f \
  -H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef" \
  -H "Content-Type: application/json" \
  -d '{"status": "contacted", "notes": "Called back, demo booked for Friday."}'

Response 200 OK

The updated lead object.

Errors

StatusMessage
400Unknown field email: only status and notes can be changed
400status must be one of: new, contacted, qualified, closed
400notes must be at most 4000 characters
404Lead not found

Delete a lead

DELETE /leads/{lead_id}

Erases the lead for good. A lead is personal data, and a visitor may ask for it to be removed. This cannot be undone.

Auth: API key or session token. Role: member and up.

ParameterInTypeRequiredDescription
lead_idpathstring (UUID)yesThe lead's id.

Request

bash
curl -X DELETE https://api.sevennadi.si/leads/a3f1c2d4-5b6a-4c7d-8e9f-0a1b2c3d4e5f \
  -H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"

Response 200 OK

json
{"message": "success", "state": "deleted"}

Errors

StatusMessage
404Lead not found

Lead settings

Get lead settings

GET /leads/settings

Returns the workspace's "call visitors back" setting, whether it would really place calls, and the workspace's site key.

Auth: API key or session token. Role: viewer and up.

Request

bash
curl https://api.sevennadi.si/leads/settings \
  -H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"

Response 200 OK

json
{
  "callback_enabled": false,
  "callback_agent_id": null,
  "telephony_live": false,
  "calls_visitors": false,
  "is_site_owner": false,
  "site_owner_configured": false,
  "site_key": "site_0123456789abcdef0123456789abcdef0123456789ab"
}
FieldMeaning
callback_enabledWhether the workspace switched on calls to visitors.
callback_agent_idThe agent that calls visitors back.
telephony_liveWhether the operator's telephony is live. With mock telephony no visitor is called.
calls_visitorstrue only when the setting is on, an agent is chosen and telephony is live.
is_site_ownerWhether this account receives leads that carry no site key.
site_owner_configuredWhether the operator designated a site-owner account.
site_keysite_ + 44 hex characters. Publishable: put it in your website's forms to file leads under this workspace.

Update lead settings

PATCH /leads/settings

Chooses the callback agent and switches calls to visitors on or off. Because this decides whether real calls go out to website visitors, it needs the admin role.

Auth: API key or session token. Role: admin and up.

ParameterInTypeRequiredDescription
callback_enabledbodybooleannoSwitch calls to visitors on or off. Needs an agent to be chosen.
callback_agent_idbodystring (UUID) or nullnoAn agent of this account. null or an empty string clears it.

Request

bash
curl -X PATCH https://api.sevennadi.si/leads/settings \
  -H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef" \
  -H "Content-Type: application/json" \
  -d '{"callback_agent_id": "3c9d2f8e-1a2b-4c3d-8e4f-5a6b7c8d9e0f", "callback_enabled": true}'

Response 200 OK

The settings, as in Get lead settings.

Errors

StatusMessage
400Unknown setting foo
400callback_enabled must be true or false
400Choose a demo agent before switching on calls to visitors
403Only an admin can change the calls-to-visitors setting
404Agent not found

Leads webhook

If the operator sets a leads webhook URL, every new lead is POSTed to it once as {"event": "lead.created", "lead": {...}}, with the header X-Leads-Signature holding the hex HMAC-SHA256 of the body made with the platform's secret key. A failed delivery is logged and not retried; the lead is kept. This is an operator-level setting, not a per-workspace one.