{"openapi":"3.0.3","info":{"title":"LeadTable External API v4","version":"4.0.2","description":"REST API for agencies to manage customers, tables (campaigns), leads, webhooks, files, pages, and WhatsApp.\n\n**Authentication:** every route needs the headers `x-api-key` and `email`.\n\n**IDs:** MongoDB ObjectId strings. Timestamps are ISO-8601 unless noted.\n\n**Tables vs campaigns:** a campaign is a table. v4 uses `/table` paths. Request fields that still say `campaignID` accept a table ID.\n\n**Lead profile (`leadData`):** `GET /lead/{leadID}` adds `lead.leadData` with `mappedData` (column name → answer), `rawData` (question title → answer), and `dataArray` (`{ title, value }[]`). List and search routes do not add `leadData`.\n\n**Webhooks:** topics are `newLead`, `changeStatus`, `updateLead`, `deleteLead`, `whatsappMessage`. Layers are `agency`, `customer`, `table`. Live deliveries use a JSON body with `event`, IDs, contact fields, and `leadData` (`mappedData` + `rawData`, no `dataArray`). `GET /pollWebhook/{campaignID}/{topic}` returns one shortened test sample, not the full live body.\n\n**Files:** upload as `multipart/form-data` with `file` and `id` (lead ID).  Download links are **only valid for 5 minutes**"},"servers":[{"url":"https://api.lead-table.com/api/v4/"}],"security":[{"ApiKeyAuth":[],"EmailHeader":[]}],"tags":[{"name":"Auth","description":"Check API credentials."},{"name":"Agency","description":"Read the authenticated agency."},{"name":"Customers","description":"Create and list customers."},{"name":"Tables","description":"Create and list tables (campaigns) and templates."},{"name":"Status","description":"List reusable status templates and their statuses."},{"name":"Leads","description":"Create, read, update, move, list, and search leads. Upload and list lead files."},{"name":"Webhooks","description":"Attach, remove, and poll webhooks. Live payloads use the same `leadData.mappedData` / `rawData` meaning as GET lead (no `dataArray`)."},{"name":"WhatsApp","description":"Send WhatsApp text messages."},{"name":"Pages","description":"List page templates and attach or remove customer pages."}],"paths":{"/auth":{"get":{"tags":["Auth"],"summary":"Check authentication","description":"Returns `{ auth: true }` when the API key and email are valid.","operationId":"getAuth","responses":{"200":{"description":"Authenticated","content":{"application/json":{"schema":{"type":"object","required":["auth"],"properties":{"auth":{"type":"boolean","enum":[true]}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/agency":{"get":{"tags":["Agency"],"summary":"Get the authenticated agency","description":"Returns the agency that belongs to the API key, including aggregated stats.","operationId":"getAgency","responses":{"200":{"description":"Agency","content":{"application/json":{"schema":{"type":"object","required":["agency"],"properties":{"agency":{"$ref":"#/components/schemas/Agency"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/customer/list":{"get":{"tags":["Customers"],"summary":"List customers","description":"Paginated list of customers in the agency. Each item includes `stats`.","operationId":"listCustomers","parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Paginated customers","content":{"application/json":{"schema":{"type":"object","required":["pagination","customers"],"properties":{"pagination":{"$ref":"#/components/schemas/Pagination"},"customers":{"type":"array","items":{"$ref":"#/components/schemas/CustomerListItem"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/customer/create":{"post":{"tags":["Customers"],"summary":"Create a customer","description":"Creates a customer if the agency has a free slot. Optional: increment slots, create customer logins, and attach pages.","operationId":"createCustomer","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerCreateRequest"}}}},"responses":{"200":{"description":"Customer created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerCreateResponse"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/customer/{customerID}":{"get":{"tags":["Customers"],"summary":"Get a customer","description":"Returns one customer you can access, including `stats`.","operationId":"getCustomer","parameters":[{"$ref":"#/components/parameters/customerID"}],"responses":{"200":{"description":"Customer","content":{"application/json":{"schema":{"type":"object","required":["customer"],"properties":{"customer":{"$ref":"#/components/schemas/Customer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/customer/addPage":{"post":{"tags":["Pages"],"summary":"Add a page to a customer","description":"Copies a page instance from a page template onto a customer. Send a value for every required page variable.","operationId":"addCustomerPage","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerAddPageRequest"}}}},"responses":{"200":{"description":"Page added","content":{"application/json":{"schema":{"type":"object","required":["pageInstanceID"],"properties":{"pageInstanceID":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/customer/{customerID}/page/{pageInstanceID}":{"delete":{"tags":["Pages"],"summary":"Remove a page from a customer","description":"Deletes the page instance. The page must belong to the customer and to your agency.","operationId":"removeCustomerPage","parameters":[{"$ref":"#/components/parameters/customerID"},{"$ref":"#/components/parameters/pageInstanceID"}],"responses":{"200":{"description":"Page removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerRemovePageResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/table/list/{customerID}":{"get":{"tags":["Tables"],"summary":"List tables of a customer","description":"Paginated tables (campaigns) of a customer. Limit is capped at 100. Each item includes `stats`, `totalLeads`, `leadsCount`, and `webhookToken`.","operationId":"listTablesByCustomer","parameters":[{"$ref":"#/components/parameters/customerID"},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limitMax100"}],"responses":{"200":{"description":"Paginated tables","content":{"application/json":{"schema":{"type":"object","required":["pagination","campaigns"],"properties":{"pagination":{"$ref":"#/components/schemas/Pagination"},"campaigns":{"type":"array","items":{"$ref":"#/components/schemas/TableListItem"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/table/{tableID}":{"get":{"tags":["Tables"],"summary":"Get a table","description":"Returns one table you can access, including `stats` and `webhookToken`.","operationId":"getTable","parameters":[{"$ref":"#/components/parameters/tableID"}],"responses":{"200":{"description":"Table","content":{"application/json":{"schema":{"type":"object","required":["table"],"properties":{"table":{"$ref":"#/components/schemas/Table"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/table/{tableID}/leads":{"get":{"tags":["Leads"],"summary":"List leads of a table","description":"Paginated leads of a table. Limit is capped at 200. Entries do not include `leadData`. Use GET `/lead/{leadID}` for profile data.","operationId":"listLeadsByTable","parameters":[{"$ref":"#/components/parameters/tableID"},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limitMax200"}],"responses":{"200":{"description":"Paginated leads","content":{"application/json":{"schema":{"type":"object","required":["pages","leads"],"properties":{"pages":{"$ref":"#/components/schemas/LeadPages"},"leads":{"type":"array","items":{"$ref":"#/components/schemas/LeadListItem"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/table/create":{"post":{"tags":["Tables"],"summary":"Create a table","description":"Creates a table for a customer you can access. You can set statuses, a status template, pre-qualification, and lead-delete visibility. A table is saved only after a status template exists. Send `status` to create a new template, or `statusTemplateID` to use an existing one. If you send neither, the first template of the agency is used. If the agency has no template, the response is 404: create at least one status template (https://help.lead-table.com/de/articles/9739351-status-felder-und-statusvorlagen). Returns a `webhookToken`. To create a lead, send an HTTP POST with JSON to `https://api.lead-table.com/api/webhook/{webhookToken}`.","operationId":"createTable","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TableCreateRequest"}}}},"responses":{"200":{"description":"Table created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TableCreateResponse"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/table/create/{templateID}":{"post":{"tags":["Tables"],"summary":"Create a table from a template","description":"Creates a table from an agency table template. The template sets status, views, and automations. The customer must belong to the agency and must be active. Returns a `webhookToken`. To create a lead, send an HTTP POST with JSON to `https://api.lead-table.com/api/webhook/{webhookToken}`.","operationId":"createTableFromTemplate","parameters":[{"$ref":"#/components/parameters/templateID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["customerID","occupation"],"properties":{"customerID":{"type":"string"},"occupation":{"type":"string","maxLength":100}}}}}},"responses":{"200":{"description":"Table created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TableCreateFromTemplateResponse"}}}},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/table/templates/list":{"get":{"tags":["Tables"],"summary":"List table templates","description":"Agency table templates, newest first. Each item has `id`, `name`, and `icon` only.","operationId":"listTableTemplates","parameters":[{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"},{"$ref":"#/components/parameters/search"}],"responses":{"200":{"description":"Paginated templates","content":{"application/json":{"schema":{"type":"object","required":["templates","pagination"],"properties":{"templates":{"type":"array","items":{"$ref":"#/components/schemas/TableTemplate"}},"pagination":{"$ref":"#/components/schemas/Pagination"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/status/templates/list":{"get":{"tags":["Status"],"summary":"List status templates","description":"Reusable status templates for the agency, sorted by name. Campaign copies are not included. Use a status `id` with `POST /lead/changeStatus`. `leadInboxStatus` is the inbox status id, or `null`.","operationId":"listStatusTemplates","responses":{"200":{"description":"Status templates","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StatusTemplateList"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/lead/create":{"post":{"tags":["Leads"],"summary":"Create a lead","description":"Creates a lead in a table. Each `data` item is a question (`key`) and answer (`value`). Set `notifications` to `true` to run automations.","operationId":"createLead","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadCreateRequest"}}}},"responses":{"200":{"description":"Lead created","content":{"application/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/lead/{leadID}":{"get":{"tags":["Leads"],"summary":"Get a lead","description":"Returns the lead including `lead.leadData` (`mappedData`, `rawData`, `dataArray`) when the table exists.","operationId":"getLead","parameters":[{"$ref":"#/components/parameters/leadID"},{"$ref":"#/components/parameters/plainDescription"}],"responses":{"200":{"description":"Lead","content":{"application/json":{"schema":{"type":"object","required":["lead"],"properties":{"lead":{"$ref":"#/components/schemas/Lead"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"tags":["Leads"],"summary":"Create or update a lead field","description":"Creates or updates one profile field. If the question does not exist, the API adds it. Set `setVisibleInProfile` to `true` to show the field in the lead profile.","operationId":"upsertLeadField","parameters":[{"$ref":"#/components/parameters/leadID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateLeadFieldRequest"}}}},"responses":{"200":{"description":"Lead updated","content":{"application/json":{"schema":{"type":"object","required":["success","lead"],"properties":{"success":{"type":"boolean","enum":[true]},"lead":{"$ref":"#/components/schemas/LeadWrite"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/lead/{leadID}/description":{"put":{"tags":["Leads"],"summary":"Update lead description","description":"Overwrites the lead description. Maximum length is 25000 characters after HTML sanitizing.","operationId":"updateLeadDescription","parameters":[{"$ref":"#/components/parameters/leadID"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["description"],"properties":{"description":{"type":"string","maxLength":25000}}}}}},"responses":{"200":{"description":"Lead updated","content":{"application/json":{"schema":{"type":"object","required":["lead"],"properties":{"lead":{"$ref":"#/components/schemas/LeadWrite"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/lead/{leadID}/files":{"get":{"tags":["Leads"],"summary":"List files of a lead","description":"Returns the files uploaded to a lead: `filename`, `filesize`, `mimetype` and `blobURL`. Use `blobURL` as the file id to download a file with `GET /lead/{leadID}/files/{fileID}/content`.\n\n`downloadURL` is an older, ready-made link that is valid for about 55 minutes and does not name the file. Prefer the download endpoint.\n\n### Example: download all files of a lead\n\nSet `API_KEY`, `AGENCY_EMAIL` and `LEAD_ID`. The files land in `lead-files/<LEAD_ID>/`.\n\n**Node.js** (18 or newer), save as `download.mjs` and run `node download.mjs`\n\n```js\nimport { createWriteStream } from \"node:fs\";\nimport { mkdir } from \"node:fs/promises\";\nimport { basename, join } from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\n\nconst { API_BASE = \"https://api.lead-table.com/api/v4\", API_KEY, AGENCY_EMAIL, LEAD_ID } = process.env;\nconst headers = { \"x-api-key\": API_KEY, email: AGENCY_EMAIL };\nconst dir = join(\"lead-files\", LEAD_ID);\nawait mkdir(dir, { recursive: true });\n\nconst listing = await fetch(`${API_BASE}/lead/${LEAD_ID}/files`, { headers });\nif (!listing.ok) throw new Error(`Listing failed: HTTP ${listing.status}`);\nconst { files } = await listing.json();\n\nfor (const file of files) {\n  const res = await fetch(`${API_BASE}/lead/${LEAD_ID}/files/${file.blobURL}/content`, { headers });\n  if (!res.ok) throw new Error(`${file.filename}: HTTP ${res.status}`);\n  await pipeline(Readable.fromWeb(res.body), createWriteStream(join(dir, basename(file.filename))));\n  console.log(\"ok \", file.filename);\n}\n```\n\n**Python** (3.8 or newer), save as `download.py` and run `python3 download.py`\n\n```python\nimport json, os, shutil, urllib.request\n\nAPI_BASE = os.environ.get(\"API_BASE\", \"https://api.lead-table.com/api/v4\")\nlead_id = os.environ[\"LEAD_ID\"]\nheaders = {\"x-api-key\": os.environ[\"API_KEY\"], \"email\": os.environ[\"AGENCY_EMAIL\"]}\nout = os.path.join(\"lead-files\", lead_id)\nos.makedirs(out, exist_ok=True)\n\n\ndef get(path):\n    return urllib.request.urlopen(urllib.request.Request(f\"{API_BASE}{path}\", headers=headers))\n\n\nwith get(f\"/lead/{lead_id}/files\") as res:\n    files = json.load(res)[\"files\"]\n\nfor f in files:\n    target = os.path.join(out, os.path.basename(f[\"filename\"]))\n    with get(f\"/lead/{lead_id}/files/{f['blobURL']}/content\") as res, open(target, \"wb\") as fh:\n        shutil.copyfileobj(res, fh)\n    print(\"ok \", f[\"filename\"])\n```","operationId":"listLeadFiles","parameters":[{"$ref":"#/components/parameters/leadID"}],"responses":{"200":{"description":"Files","content":{"application/json":{"schema":{"type":"object","required":["files"],"properties":{"files":{"type":"array","items":{"$ref":"#/components/schemas/LeadFile"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/lead/{leadID}/files/{fileID}/content":{"get":{"tags":["Leads"],"summary":"Download a file of a lead","description":"Redirects (`302`) to a download link that is valid for 60 seconds. Follow the redirect and save the body. The response carries the real `Content-Type` and a `Content-Disposition` header with the file name. Send your `x-api-key` and `email` headers with this request only. Most HTTP clients follow the redirect by default.","operationId":"downloadLeadFile","parameters":[{"$ref":"#/components/parameters/leadID"},{"name":"fileID","in":"path","required":true,"description":"The `blobURL` of the file from the listing.","schema":{"type":"string"}}],"responses":{"302":{"description":"Redirect to the file. The `Location` header holds the 60 second link.","headers":{"Location":{"schema":{"type":"string"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/lead/addFile":{"post":{"tags":["Leads"],"summary":"Upload a file to a lead","description":"Uploads one file and attaches it to the lead.","operationId":"addFileToLead","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file","id"],"properties":{"file":{"type":"string","format":"binary"},"id":{"type":"string","description":"Lead ID"}}}}}},"responses":{"200":{"description":"File uploaded","content":{"application/json":{"schema":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean"},"message":{"type":"string"},"data":{"type":"object","description":"Upload metadata"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/lead/changeStatus":{"post":{"tags":["Leads"],"summary":"Change lead status","description":"Sets a new status. The status must belong to the table status template. Set `sendMails` to `true` to run automations.","operationId":"changeLeadStatus","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadStatusChangeRequest"}}}},"responses":{"200":{"description":"Status changed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/lead/move":{"post":{"tags":["Leads"],"summary":"Move or copy leads","description":"Moves or copies one or more leads to another table. `toCustomerID` must match the target table owner. You can map statuses or set one `newStatus`.","operationId":"moveLeads","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LeadMoveRequest"}}}},"responses":{"200":{"description":"Leads moved or copied","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/lead/search/byEmail/{email}":{"get":{"tags":["Leads"],"summary":"Search leads by email","description":"Paginated search across agency customers. The email is URL-encoded in the path and matched in lowercase. Results do not include `leadData`.","operationId":"searchLeadByEmail","parameters":[{"$ref":"#/components/parameters/email"},{"$ref":"#/components/parameters/page"},{"$ref":"#/components/parameters/limit"}],"responses":{"200":{"description":"Paginated leads","content":{"application/json":{"schema":{"type":"object","required":["pagination","leads"],"properties":{"pagination":{"$ref":"#/components/schemas/Pagination"},"leads":{"type":"array","items":{"$ref":"#/components/schemas/LeadSearchItem"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/attachWebhook":{"post":{"tags":["Webhooks"],"summary":"Attach a webhook","description":"Registers a URL for a topic and layer. For `table` send `campaignID`. For `customer` send `customerID`. Store `externalHookId` so you can remove the webhook later. `layer` defaults to `table`.","operationId":"attachWebhook","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachWebhookRequest"}}}},"responses":{"200":{"description":"Webhook attached","content":{"application/json":{"schema":{"type":"object","required":["externalHookId"],"properties":{"message":{"type":"string"},"externalHookId":{"type":"string"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/removeWebhook":{"delete":{"tags":["Webhooks"],"summary":"Detach a webhook","description":"Removes a webhook. Prefer `externalHookId` from attach. Without it, send `url`, `layer`, and `campaignID` (table) or `customerID` (customer). Use `Content-Type: application/json`. Some HTTP clients drop DELETE bodies.","operationId":"removeWebhook","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveWebhookRequest"}}}},"responses":{"200":{"description":"Webhook removed","content":{"application/json":{"schema":{"type":"object","required":["removed"],"properties":{"removed":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/pollWebhook/{campaignID}/{topic}":{"get":{"tags":["Webhooks"],"summary":"Poll a webhook test sample","description":"Returns a one-element array with a shortened test payload. This is not the full live webhook body.","operationId":"pollWebhook","parameters":[{"$ref":"#/components/parameters/campaignID"},{"$ref":"#/components/parameters/topic"}],"responses":{"200":{"description":"Array with one test payload","content":{"application/json":{"schema":{"type":"array","minItems":1,"maxItems":1,"items":{"$ref":"#/components/schemas/WebhookTestEvent"}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/whatsapp/message/send":{"post":{"tags":["WhatsApp"],"summary":"Send a WhatsApp message","description":"Sends a text message to a lead or an unassigned conversation. Send either `leadID` or `unassignedConversationID`. The customer must have a WhatsApp number. The 24-hour window and DSGVO rules still apply.","operationId":"sendWhatsappMessage","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhatsappSendRequest"}}}},"responses":{"200":{"description":"Message sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/pages/templates/list":{"get":{"tags":["Pages"],"summary":"List page templates","description":"Agency page templates with page instance IDs and required variables. Use these IDs in `POST /customer/addPage` or in `pages` on customer create.","operationId":"listPortalTemplates","responses":{"200":{"description":"Portal templates","content":{"application/json":{"schema":{"type":"object","required":["templates"],"properties":{"templates":{"type":"array","items":{"$ref":"#/components/schemas/PortalTemplate"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}},"components":{"securitySchemes":{"ApiKeyAuth":{"type":"apiKey","in":"header","name":"x-api-key"},"EmailHeader":{"type":"apiKey","in":"header","name":"email"}},"parameters":{"leadID":{"name":"leadID","in":"path","required":true,"schema":{"type":"string"}},"tableID":{"name":"tableID","in":"path","required":true,"schema":{"type":"string"},"description":"Table (campaign) ID"},"campaignID":{"name":"campaignID","in":"path","required":true,"schema":{"type":"string"},"description":"Table (campaign) ID"},"customerID":{"name":"customerID","in":"path","required":true,"schema":{"type":"string"}},"templateID":{"name":"templateID","in":"path","required":true,"schema":{"type":"string"}},"pageInstanceID":{"name":"pageInstanceID","in":"path","required":true,"schema":{"type":"string"}},"email":{"name":"email","in":"path","required":true,"schema":{"type":"string"},"description":"Email address, URL-encoded"},"topic":{"name":"topic","in":"path","required":true,"schema":{"type":"string","enum":["newLead","changeStatus","updateLead","whatsappMessage"]},"description":"Test topic for pollWebhook"},"page":{"name":"page","in":"query","schema":{"type":"integer","minimum":1,"default":1}},"limit":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"default":50}},"limitMax100":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100,"default":50}},"limitMax200":{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},"search":{"name":"search","in":"query","schema":{"type":"string","default":""}},"plainDescription":{"name":"plainDescription","in":"query","description":"If `true`, HTML is stripped from `description`.","schema":{"type":"boolean","default":false}}},"responses":{"BadRequest":{"description":"Bad request"},"Unauthorized":{"description":"Unauthorized"},"Forbidden":{"description":"Forbidden"},"NotFound":{"description":"Not found"},"InternalServerError":{"description":"Internal server error"}},"schemas":{"SuccessResponse":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","enum":[true]}}},"Pagination":{"type":"object","required":["totalItems","totalPages","currentPage","itemsPerPage"],"properties":{"totalItems":{"type":"integer"},"totalPages":{"type":"integer"},"currentPage":{"type":"integer"},"itemsPerPage":{"type":"integer"}}},"LeadPages":{"type":"object","required":["totalLeads","totalPages","currentPage","leadsPerPage"],"properties":{"totalLeads":{"type":"integer"},"totalPages":{"type":"integer"},"currentPage":{"type":"integer"},"leadsPerPage":{"type":"integer"}}},"LastLead":{"type":"object","properties":{"_id":{"type":"string"},"createdAt":{"type":"string","format":"date-time"}}},"LeadStats":{"type":"object","properties":{"totalLeads":{"type":"integer"},"newLeads":{"type":"integer"},"leadsLast30Days":{"type":"integer"},"lastLead":{"$ref":"#/components/schemas/LastLead"},"lastChange":{"type":"string","format":"date-time"},"unusedLeads":{"type":"integer"},"oldLeads":{"type":"integer"}}},"StatusStats":{"type":"object","properties":{"counts":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"count":{"type":"integer"}}}}}},"WhatsAppStats":{"type":"object","properties":{"unreadMessages":{"type":"integer"},"totalMessages":{"type":"integer"},"lastMessageDate":{"type":"string","format":"date-time"}}},"CustomerWhatsAppStats":{"allOf":[{"$ref":"#/components/schemas/WhatsAppStats"},{"type":"object","properties":{"unassignedMessages":{"type":"integer"}}}]},"AgencyStats":{"type":"object","properties":{"agencyCampaignsStats":{"type":"object","properties":{"campaignCount":{"type":"integer"},"campaignLast30Days":{"type":"integer"}}},"agencyCustomersStats":{"type":"object","properties":{"customerCount":{"type":"integer"},"customerLast30Days":{"type":"integer"}}},"statusStats":{"$ref":"#/components/schemas/StatusStats"},"leadsStats":{"$ref":"#/components/schemas/LeadStats"},"whatsAppStats":{"$ref":"#/components/schemas/CustomerWhatsAppStats"}}},"CustomerStats":{"type":"object","properties":{"statusStats":{"$ref":"#/components/schemas/StatusStats"},"leadsStats":{"$ref":"#/components/schemas/LeadStats"},"campaignCount":{"type":"integer"},"whatsAppStats":{"$ref":"#/components/schemas/CustomerWhatsAppStats"}}},"TableStats":{"type":"object","properties":{"nextRefresh":{"type":"string","format":"date-time","nullable":true},"leadsStats":{"$ref":"#/components/schemas/LeadStats"},"leadsStatsCustomerLogin":{"$ref":"#/components/schemas/LeadStats"},"statusStats":{"$ref":"#/components/schemas/StatusStats"},"whatsAppStats":{"$ref":"#/components/schemas/WhatsAppStats"},"whatsAppStatsCustomerLogin":{"$ref":"#/components/schemas/WhatsAppStats"}}},"Agency":{"type":"object","required":["_id","email","domain","createdAt","stats"],"properties":{"_id":{"type":"string"},"email":{"type":"string","format":"email"},"domain":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"stats":{"$ref":"#/components/schemas/AgencyStats"}}},"CustomerListItem":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"archived":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"stats":{"$ref":"#/components/schemas/CustomerStats"}},"additionalProperties":true},"Customer":{"type":"object","required":["_id","name","description","archived","inactive","createdAt","ownerAgency","stats"],"properties":{"_id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"archived":{"type":"boolean"},"inactive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"ownerAgency":{"type":"string"},"stats":{"$ref":"#/components/schemas/CustomerStats"}}},"CustomerCreateRequest":{"type":"object","required":["name"],"properties":{"name":{"type":"string","maxLength":255},"description":{"type":"string","maxLength":255,"default":""},"allowAutomaticSlotIncrement":{"type":"boolean","default":false,"description":"If no slot is free, upgrade the agency to the next slot increment."},"customerLogins":{"type":"array","items":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}}},"subuserAccess":{"type":"object","required":["all"],"properties":{"all":{"type":"boolean"},"subusers":{"type":"array","items":{"type":"string"}}}},"pages":{"type":"array","items":{"type":"object","required":["pageInstanceID","variables"],"properties":{"pageInstanceID":{"type":"string"},"variables":{"type":"object","additionalProperties":{"type":"string"}}}}}}},"PageHelperResultData":{"type":"object","required":["pageInstanceID"],"properties":{"pageInstanceID":{"type":"string"}}},"CustomerLoginHelperResultData":{"type":"object","required":["email","magicLink","id"],"properties":{"email":{"type":"string","format":"email"},"magicLink":{"type":"string"},"id":{"type":"string"}}},"PageHelperResult":{"type":"object","required":["success","data"],"description":"Result of attaching one page during customer create. On failure, `data` is null and `errorMessage` is set.","properties":{"success":{"type":"boolean"},"data":{"allOf":[{"$ref":"#/components/schemas/PageHelperResultData"}],"nullable":true},"errorMessage":{"type":"string"},"statusCode":{"type":"integer"}}},"CustomerLoginHelperResult":{"type":"object","required":["success","data"],"description":"Result of creating one customer login during customer create. On failure, `data` is null and `errorMessage` is set.","properties":{"success":{"type":"boolean"},"data":{"allOf":[{"$ref":"#/components/schemas/CustomerLoginHelperResultData"}],"nullable":true},"errorMessage":{"type":"string"},"statusCode":{"type":"integer"}}},"CustomerCreateResponse":{"type":"object","required":["id","name","createdAt"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"description":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"pages":{"type":"array","items":{"$ref":"#/components/schemas/PageHelperResult"}},"customerLogins":{"type":"array","items":{"$ref":"#/components/schemas/CustomerLoginHelperResult"}},"upgradeResult":{"type":"object","properties":{"previousSlotCount":{"type":"integer"},"newSlotCount":{"type":"integer"}}}}},"TableListItem":{"type":"object","properties":{"_id":{"type":"string"},"occupation":{"type":"string"},"archived":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"lastChange":{"type":"string","format":"date-time"},"stats":{"$ref":"#/components/schemas/TableStats"},"totalLeads":{"type":"integer"},"leadsCount":{"type":"integer"},"webhookToken":{"type":"string","description":"Inbound webhook token."}},"additionalProperties":true},"Table":{"type":"object","required":["_id","occupation","archived","inactive","createdAt","ownerCustomer","ownerAgency","stats","webhookToken"],"properties":{"_id":{"type":"string"},"occupation":{"type":"string"},"archived":{"type":"boolean"},"inactive":{"type":"boolean"},"createdAt":{"type":"string","format":"date-time"},"ownerCustomer":{"type":"string"},"ownerAgency":{"type":"string"},"stats":{"$ref":"#/components/schemas/TableStats"},"webhookToken":{"type":"string","description":"Inbound webhook token."}}},"TableCreateRequest":{"type":"object","required":["customerID","occupation"],"properties":{"customerID":{"type":"string","maxLength":100},"occupation":{"type":"string","maxLength":100},"funnelLink":{"type":"string"},"preQualify":{"type":"boolean"},"hiddenStatuses":{"type":"array","items":{"type":"string"},"default":[]},"deleteLeads":{"type":"boolean","default":true,"description":"Whether users can delete leads in this table."},"status":{"type":"array","description":"Creates or reuses these statuses on a new status template. The table is saved after that template exists. This field takes priority over `statusTemplateID`.","items":{"type":"object","required":["title","color"],"properties":{"title":{"type":"string"},"color":{"type":"string"}}}},"statusTemplateID":{"type":"string","description":"Id of a status template that belongs to this agency. Used when `status` is omitted. An unknown or invalid id returns 404 `Status template not found`."}}},"TableCreateResponse":{"type":"object","required":["id","occupation","customerID","webhookToken","createdAt"],"properties":{"id":{"type":"string"},"occupation":{"type":"string"},"customerID":{"type":"string"},"customerName":{"type":"string"},"webhookToken":{"type":"string","description":"Inbound webhook token."},"createdAt":{"type":"string","format":"date-time"}}},"TableCreateFromTemplateResponse":{"type":"object","required":["name","tableID","webhookToken"],"properties":{"name":{"type":"string"},"tableID":{"type":"string"},"webhookToken":{"type":"string","description":"Inbound webhook token."}}},"TableTemplate":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"icon":{"type":"string"}}},"StatusTemplateStatus":{"type":"object","required":["id","name","color"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"color":{"type":"string"}}},"StatusTemplate":{"type":"object","required":["id","name","leadInboxStatus","statuses"],"properties":{"id":{"type":"string"},"name":{"type":"string"},"leadInboxStatus":{"type":"string","nullable":true},"statuses":{"type":"array","items":{"$ref":"#/components/schemas/StatusTemplateStatus"}}}},"StatusTemplateList":{"type":"object","required":["templates"],"properties":{"templates":{"type":"array","items":{"$ref":"#/components/schemas/StatusTemplate"}}}},"ProfileFieldRow":{"type":"object","properties":{"title":{"type":"string"},"value":{"type":"string"}}},"LeadLeadData":{"type":"object","description":"Derived views of the lead profile. Present on GET `/lead/{leadID}` only.","properties":{"mappedData":{"type":"object","additionalProperties":{"type":"string"},"description":"Table column name → merged answer"},"rawData":{"type":"object","additionalProperties":{"type":"string"},"description":"Question title → answer"},"dataArray":{"type":"array","items":{"$ref":"#/components/schemas/ProfileFieldRow"}}}},"Lead":{"type":"object","properties":{"_id":{"type":"string"},"ownerAgency":{"type":"string"},"ownerCustomer":{"type":"string"},"ownerTable":{"type":"string"},"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string","format":"email"},"createdAt":{"type":"string","format":"date-time"},"lastChange":{"type":"string","format":"date-time"},"source":{"type":"string"},"status":{"type":"string"},"description":{"type":"string"},"funnelData":{"type":"object"},"history":{"type":"array","items":{"type":"object"}},"leadData":{"$ref":"#/components/schemas/LeadLeadData"}},"additionalProperties":true},"LeadWrite":{"type":"object","description":"Lead after a write. No `leadData`. Includes `archived` and `inactive` (always false today).","properties":{"_id":{"type":"string"},"ownerAgency":{"type":"string"},"ownerCustomer":{"type":"string"},"ownerTable":{"type":"string"},"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string","format":"email"},"createdAt":{"type":"string","format":"date-time"},"source":{"type":"string"},"status":{"type":"string"},"description":{"type":"string"},"funnelData":{"type":"object"},"history":{"type":"array","items":{"type":"object"}},"archived":{"type":"boolean"},"inactive":{"type":"boolean"}},"additionalProperties":true},"LeadListItem":{"type":"object","properties":{"_id":{"type":"string"},"ownerCustomer":{"type":"string"},"ownerTable":{"type":"string"},"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string","format":"email"},"createdAt":{"type":"string","format":"date-time"},"source":{"type":"string"},"status":{"type":"string"},"funnelData":{"type":"object"},"history":{"type":"array","items":{"type":"object"}},"archived":{"type":"boolean"},"inactive":{"type":"boolean"}},"additionalProperties":true},"LeadSearchItem":{"type":"object","properties":{"_id":{"type":"string"},"ownerCustomer":{"type":"string"},"ownerTable":{"type":"string"},"name":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string","format":"email"},"createdAt":{"type":"string","format":"date-time"},"source":{"type":"string"},"status":{"type":"string"},"description":{"type":"string"}},"additionalProperties":true},"LeadCreateRequest":{"type":"object","required":["campaignID","data"],"properties":{"campaignID":{"type":"string","maxLength":255,"description":"Table ID"},"data":{"type":"array","items":{"type":"object","required":["key"],"properties":{"key":{"type":"string"},"value":{"type":"string","nullable":true}}}},"notifications":{"type":"boolean","description":"If true, run automations after create."}}},"UpdateLeadFieldRequest":{"type":"object","required":["question","answer"],"properties":{"question":{"type":"string","maxLength":300},"answer":{"type":"string","maxLength":2000},"setVisibleInProfile":{"type":"boolean"}}},"LeadStatusChangeRequest":{"type":"object","required":["leadID","campaignID","statusID"],"properties":{"leadID":{"type":"string"},"campaignID":{"type":"string","description":"Table ID of the lead"},"statusID":{"type":"string"},"sendMails":{"type":"boolean","default":false,"description":"If true, run automations after the status change."}}},"LeadMoveRequest":{"type":"object","required":["mode","leadsIDs","fromCampaignID","toCampaignID","toCustomerID"],"properties":{"mode":{"type":"string","enum":["move","copy"]},"leadsIDs":{"type":"array","minItems":1,"items":{"type":"string"}},"fromCampaignID":{"type":"string"},"toCampaignID":{"type":"string"},"toCustomerID":{"type":"string"},"sendMails":{"type":"boolean","default":false},"newStatus":{"type":"string"},"statusReplacement":{"type":"array","items":{"type":"object","required":["oldStatus","newStatus"],"properties":{"oldStatus":{"type":"string"},"newStatus":{"type":"string"}}}}}},"LeadFile":{"type":"object","required":["filename","filesize","mimetype","blobURL","downloadURL"],"properties":{"filename":{"type":"string"},"filesize":{"type":"number"},"mimetype":{"type":"string"},"blobURL":{"type":"string"},"createdAt":{"type":"string","format":"date-time"},"downloadURL":{"type":"string","description":"Read-only link, valid for about 55 minutes after the listing call. Needs no authentication headers."}}},"WebhookTopic":{"type":"string","enum":["newLead","changeStatus","updateLead","deleteLead","whatsappMessage"]},"WebhookLayer":{"type":"string","enum":["agency","customer","table"]},"AttachWebhookRequest":{"type":"object","required":["url","topic"],"properties":{"url":{"type":"string","maxLength":255},"topic":{"$ref":"#/components/schemas/WebhookTopic"},"layer":{"allOf":[{"$ref":"#/components/schemas/WebhookLayer"}],"description":"Defaults to `table`."},"campaignID":{"type":"string","description":"Required when layer is `table`."},"customerID":{"type":"string","description":"Required when layer is `customer`."}}},"RemoveWebhookRequest":{"type":"object","description":"Send `externalHookId`, or send `url` + `layer` + the matching owner ID.","properties":{"externalHookId":{"type":"string"},"url":{"type":"string"},"layer":{"$ref":"#/components/schemas/WebhookLayer"},"campaignID":{"type":"string","description":"Table ID when layer is `table`."},"customerID":{"type":"string","description":"Customer ID when layer is `customer`."}}},"WebhookTestEvent":{"oneOf":[{"$ref":"#/components/schemas/WebhookTestNoLead"},{"$ref":"#/components/schemas/WebhookTestNewLead"},{"$ref":"#/components/schemas/WebhookTestChangeStatus"},{"$ref":"#/components/schemas/WebhookTestUpdateLead"},{"$ref":"#/components/schemas/WebhookTestWhatsapp"}]},"WebhookTestNoLead":{"type":"object","properties":{"event":{"type":"string","enum":["noLead"]},"campaignID":{"type":"string"},"campaignName":{"type":"string"},"customerID":{"type":"string"},"customerName":{"type":"string"},"timestamp":{"type":"integer","format":"int64"}}},"WebhookTestNewLead":{"type":"object","required":["id"],"properties":{"id":{"type":"string"}}},"WebhookTestChangeStatus":{"type":"object","properties":{"leadID":{"type":"string"},"campaignID":{"type":"string"},"campaignName":{"type":"string"},"customerID":{"type":"string"},"customerName":{"type":"string"},"oldStatus":{"type":"string"},"oldStatusID":{"type":"string"},"newStatus":{"type":"string"},"newStatusID":{"type":"string"},"status":{"type":"string"},"statusID":{"type":"string"},"leadEmail":{"type":"string"},"leadPhone":{"type":"string"},"leadName":{"type":"string"},"responsible":{"type":"string"},"message":{"type":"string"}}},"WebhookTestUpdateLead":{"type":"object","properties":{"event":{"type":"string","enum":["updateDescription"]},"leadID":{"type":"string"},"campaignID":{"type":"string"},"campaignName":{"type":"string"},"customerID":{"type":"string"},"customerName":{"type":"string"},"status":{"type":"string"},"statusID":{"type":"string"},"leadEmail":{"type":"string"},"leadPhone":{"type":"string"},"leadName":{"type":"string"},"lastChange":{"type":"integer","format":"int64"},"createdAt":{"type":"string","format":"date-time"},"timestamp":{"type":"integer","format":"int64"}}},"WebhookTestWhatsapp":{"type":"object","properties":{"leadID":{"type":"string"},"campaignID":{"type":"string"},"campaignName":{"type":"string"},"customerID":{"type":"string"},"customerName":{"type":"string"},"leadEmail":{"type":"string"},"leadPhone":{"type":"string"},"leadName":{"type":"string"},"message":{"type":"string"}}},"WhatsappSendRequest":{"type":"object","required":["message"],"properties":{"message":{"type":"string","minLength":1,"maxLength":4096},"leadID":{"type":"string"},"unassignedConversationID":{"type":"string"}}},"CustomerAddPageRequest":{"type":"object","required":["customerID","pageInstanceID","variables"],"properties":{"customerID":{"type":"string"},"pageInstanceID":{"type":"string"},"variables":{"type":"object","additionalProperties":{"type":"string"}}}},"CustomerRemovePageResponse":{"type":"object","properties":{"pageInstancesToDelete":{"type":"array","items":{"type":"object","properties":{"_id":{"type":"string"},"ownerCustomer":{"type":"string"}}}},"removeAllInstances":{"type":"boolean"}}},"PortalTemplate":{"type":"object","properties":{"templateID":{"type":"string"},"name":{"type":"string"},"pages":{"type":"array","items":{"type":"object","properties":{"pageInstanceID":{"type":"string"},"name":{"type":"string"},"variables":{"type":"object","additionalProperties":{"type":"string"}}}}}}}}}}