# Send a lead from your own code > Post a lead you collected somewhere else into the same list, with the same CRM routes and webhooks. Source: https://welcomeai.dev/docs/leads-api Category: Integrations Updated: 2026-09-11 --- ## What it does `POST /api/v1/leads` puts a lead into the workspace's leads list from anywhere: a form on your own site, a checkout, a call somebody typed up afterwards. It lands in the same place a widget's lead lands, so the same CRM routes fire, the same outgoing webhooks fire, and it shows up in the same list and the same export. ## What it needs before you start - An API key with the **`leads:write`** scope. It cannot read the leads list; that is `leads:read`. - A workspace on a plan that includes the API. ## Authentication `Authorization: Bearer wai_live_…`, over HTTPS. The key is shown once when you create it. ## The request ```bash curl -X POST https://app.welcomeai.dev/api/v1/leads \ -H "Authorization: Bearer wai_live_…" \ -H "Content-Type: application/json" \ -d '{ "email": "ada@example.com", "name": "Ada Lovelace", "phone": "+34 600 111 222", "page_url": "https://acme.com/pricing", "fields": { "company": "Analytical Engines", "budget": "5000" }, "attribution": { "utm_source": "newsletter", "utm_campaign": "spring", "gclid": "Cj0KC…" } }' ``` | Field | | |---|---| | `email`, `phone` | **At least one is required.** A lead nobody can be reached at is a row, not a lead, and every CRM destination refuses one anyway. | | `name` | Split into first and last exactly as a submitted form splits it. Send `first_name` / `last_name` instead if you already have them apart. | | `fields` | Your form's own answers. Any keys you like; they appear on the lead and can be mapped onto a CRM field. | | `page_url` | Must be an `http` or `https` URL. | | `attribution` | UTM parameters, ad click ids, `referrer`, `landing_page`. Anything that is not one of the keys we know is dropped rather than refused. | | `widget_id`, `form_config_id` | Optional. Ignored if they do not belong to your workspace. | ## The response ```json { "ok": true, "lead_id": "ld_9Kq2…", "recurring": false, "submissions": 1 } ``` **`201` for somebody new, `200` for somebody who came back.** One email address is one lead per workspace: posting the same person again updates the lead you already have rather than making a second, `submissions` counts how many times they have been in touch, and the leads list marks them **Returning**. The merge fills gaps and never blanks anything. If they gave a phone last week and only an email this time, the phone stays. A newer answer to the same question wins; an older answer to a question you did not send again survives. The lead's **status and notes are never touched** -- somebody may have moved it to Contacted and written on it, and a returning visitor is not a reason to undo that. ## Common errors **`400 A lead needs an email or a phone number.`** Send one. **`400 page_url: must be an http or https URL.`** The field is rendered as a link in the leads list, so only those two schemes are accepted. **`403 This key does not have the "leads:write" scope.`** Scopes are chosen when a key is created and cannot be added afterwards. Create a new key. **`429`** The plan's daily API budget. It is a rolling 24-hour window per key. ## What people use it for A contact form that already exists and that nobody wants to rebuild. A phone enquiry logged by hand so it reaches the CRM with the rest. A checkout that should create a lead as well as an order. Bringing an older list in once, so the CRM routes run over it. ## How to check it is working 1. Post one and read the `lead_id` back. 2. Leads shows it, with **API** as the trigger. 3. If the workspace has a CRM route, the dispatch log has a row for it within a few seconds. 4. Post the same address again: the response is `200` with `"recurring": true`, and the list still has one lead with a **Returning** chip.