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".
| Operation | Method and path | Minimum role |
|---|---|---|
| Get a form token | GET /public/form-token | none |
| Submit a lead | POST /public/leads | none |
| Request a callback | POST /public/callback | none |
| List leads | GET /leads | viewer |
| Export leads as CSV | GET /leads/export.csv | viewer |
| Get a lead | GET /leads/{lead_id} | viewer |
| Update a lead | PATCH /leads/{lead_id} | member |
| Delete a lead | DELETE /leads/{lead_id} | member |
| Get lead settings | GET /leads/settings | viewer |
| Update lead settings | PATCH /leads/settings | admin |
Warning:
POST /public/callbackcan 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.
| Protection | How it works |
|---|---|
| Per-address limit | 60 public requests per client address per hour, valid or not. The token endpoint allows four times that. |
| Per-email limit | 5 submissions per email address per day. |
| Per-phone limit | One callback request per phone number per day. |
| Form token | Every 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. |
| Honeypot | A 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 fields | Unknown fields are refused. Control characters are refused. Text is stored HTML-escaped and returned as stored. |
| Consent | consent 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
- 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 byGET /leads/settings. - Otherwise it belongs to the operator's designated site-owner account, if one is configured.
- 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
curl https://api.sevennadi.si/public/form-tokenResponse 200 OK
{"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
| Status | Message |
|---|---|
| 429 | Too 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.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
kind | body | string | yes | contact, demo, newsletter, callback, pilot, careers or builder. |
consent | body | boolean | yes | Must be true. |
token | body | string | yes | From GET /public/form-token. |
page | body | string | yes | The site path the form is on, for example /contact. Up to 300 characters. |
email | body | string | yes, except for callback | Up to 254 characters. |
phone | body | string | for callback | E.164. Spaces, hyphens, dots and brackets are removed first. |
name | body | string | no | Up to 120 characters. |
company | body | string | no | Up to 160 characters. |
message | body | string | no | Up to 4000 characters; may contain new lines and tabs. |
utm | body | object | no | Any of utm_source, utm_medium, utm_campaign, utm_term, utm_content, referrer. Values up to 200 characters. |
details | body | object | no | Up 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_key | body | string | no | A workspace's site key. See Who owns a lead. |
website | body | string | no | The honeypot. Leave it empty. |
Idempotency-Key | header | string | no | Makes a retry safe: the same key with the same request returns the first response. See Idempotency. |
Request
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
{"id": "a3f1c2d4-5b6a-4c7d-8e9f-0a1b2c3d4e5f"}Errors
| Status | Message |
|---|---|
| 400 | The request body must be a JSON object |
| 400 | Unknown field budget |
| 400 | kind must be one of: contact, demo, newsletter, callback, pilot, careers, builder |
| 400 | consent is required: please agree to be contacted |
| 400 | email is required |
| 400 | email is not a valid email address |
| 400 | phone is required (for callback) |
| 400 | phone must be in E.164 format, e.g. +919876543210 |
| 400 | page must be the site path the form is on, e.g. /contact |
| 400 | name must be at most 120 characters (and the same for other fields) |
| 400 | name contains control characters |
| 400 | details: at most 12 entries |
| 400 | details: unknown or invalid key (also for utm) |
| 400 | The form token is missing or invalid. Reload the page and try again. |
| 400 | The form was sent too quickly. Please wait a moment and try again. |
| 400 | The form has expired. Reload the page and try again. |
| 400 | The form was already sent. Reload the page to send another. |
| 400 | site_key is not valid |
| 429 | Too many requests from this address. Try again later. |
| 429 | Too 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.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
phone | body | string | yes | E.164. |
consent | body | boolean | yes | Must be true. |
token | body | string | yes | From GET /public/form-token. |
name | body | string | no | Up to 120 characters. Passed to the agent as a call variable. |
language | body | string | no | Stored in the lead's details and passed to the agent. |
agent | body | string | no | Stored in the lead's details. |
page | body | string | no | Default /. |
utm | body | object | no | As in Submit a lead. |
site_key | body | string | no | A workspace's site key. |
website | body | string | no | The honeypot. Leave it empty. |
Idempotency-Key | header | string | no | Makes a retry safe: the same key with the same request returns the first response. See Idempotency. |
Request
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
{"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.
| Check | callback_state when it fails |
|---|---|
| The number is not on the workspace's do-not-call list | suppressed |
| The workspace switched on calls to visitors | not_enabled |
| The operator's telephony is live (not mock) | telephony_not_live |
| An agent is chosen and still exists | no_agent |
| The number is verified, if the workspace only calls verified numbers | not_verified |
| The daily attempt cap for the number is not reached | frequency_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:
| Status | Message |
|---|---|
| 400 | Unknown field email (only the fields above are accepted) |
| 429 | A 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.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
kind | query | string | no | One lead kind. |
status | query | string | no | new, contacted, qualified or closed. |
date_from | query | string | no | A date (YYYY-MM-DD) or ISO 8601 timestamp; leads created at or after it. |
date_to | query | string | no | A date or ISO 8601 timestamp. A bare date includes that whole day. |
search | query | string | no | Matches name, email, phone, company or message. Up to 100 characters. |
page_number | query | integer | no | From 1. Default 1. |
page_size | query | integer | no | 1 to 100. Default 20. |
Request
curl "https://api.sevennadi.si/leads?status=new&page_size=20" \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{
"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
| Status | Message |
|---|---|
| 400 | page_number must be >= 1 and page_size between 1 and 100 |
| 400 | kind must be one of: contact, demo, newsletter, callback, pilot, careers, builder |
| 400 | status must be one of: new, contacted, qualified, closed |
| 400 | from 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.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
kind, status, date_from, date_to, search | query | string | no | As in List leads. |
Request
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
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.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
lead_id | path | string (UUID) | yes | The lead's id. |
Request
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
| Status | Message |
|---|---|
| 404 | Lead 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.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
lead_id | path | string (UUID) | yes | The lead's id. |
status | body | string | no | new, contacted, qualified or closed. |
notes | body | string | no | Up to 4000 characters. |
Request
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
| Status | Message |
|---|---|
| 400 | Unknown field email: only status and notes can be changed |
| 400 | status must be one of: new, contacted, qualified, closed |
| 400 | notes must be at most 4000 characters |
| 404 | Lead 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.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
lead_id | path | string (UUID) | yes | The lead's id. |
Request
curl -X DELETE https://api.sevennadi.si/leads/a3f1c2d4-5b6a-4c7d-8e9f-0a1b2c3d4e5f \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{"message": "success", "state": "deleted"}Errors
| Status | Message |
|---|---|
| 404 | Lead 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
curl https://api.sevennadi.si/leads/settings \
-H "Authorization: Bearer vk-0123456789abcdef0123456789abcdef"Response 200 OK
{
"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"
}| Field | Meaning |
|---|---|
callback_enabled | Whether the workspace switched on calls to visitors. |
callback_agent_id | The agent that calls visitors back. |
telephony_live | Whether the operator's telephony is live. With mock telephony no visitor is called. |
calls_visitors | true only when the setting is on, an agent is chosen and telephony is live. |
is_site_owner | Whether this account receives leads that carry no site key. |
site_owner_configured | Whether the operator designated a site-owner account. |
site_key | site_ + 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.
| Parameter | In | Type | Required | Description |
|---|---|---|---|---|
callback_enabled | body | boolean | no | Switch calls to visitors on or off. Needs an agent to be chosen. |
callback_agent_id | body | string (UUID) or null | no | An agent of this account. null or an empty string clears it. |
Request
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
| Status | Message |
|---|---|
| 400 | Unknown setting foo |
| 400 | callback_enabled must be true or false |
| 400 | Choose a demo agent before switching on calls to visitors |
| 403 | Only an admin can change the calls-to-visitors setting |
| 404 | Agent 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.