{ } Luminaleads API https://luminnaleads.com.ng

API reference

One workspace's leads, lists, campaigns and pipelines, reachable from outside the dashboard — the same data under the same isolation.

Every request carries a key as a bearer token: Authorization: Bearer lum_…. Keys are created per user in the dashboard under Settings, shown once at creation, and stored only as a hash — so a key cannot be recovered, only replaced. A key reaches exactly one workspace and cannot create or revoke keys, which is what makes a leaked one containable by revoking it.

Open the dashboard

Authentication

Every request carries a key as a bearer token. Keys are created per user in Settings, shown once at creation, and stored only as a SHA-256 — so a key cannot be recovered, only replaced. Revoking takes effect immediately. A key reaches exactly one workspace: the one it was created in.

Check a key

GET /api/whoami

Answers with the workspace the key belongs to and how the request authenticated. Touches no data, so it is the safe way to confirm a key works before wiring anything to it.

Request

curl https://luminnaleads.com.ng/api/whoami \
  -H "Authorization: Bearer lum_..."

Response

{
  "tenantId": "0e2c…",
  "authenticatedVia": "api_key"
}

Leads

People, with the company they work at attached.

List leads

GET /api/leads

Newest first. Without filters this returns the most recent 200; the ceiling is 1000 per call.

Query parameters

listId
string
Only leads on this list.
status
string
Email status: valid, risky, invalid or unknown.
q
string
Matches name, email, job title, company name or company domain.
limit
number
Default 200, maximum 1000.

Request

curl "https://luminnaleads.com.ng/api/leads?status=valid&limit=50" \
  -H "Authorization: Bearer lum_..."

Response

[
  {
    "id": "…",
    "fullName": "Ada Okafor",
    "email": "ada@acme.com",
    "title": "Head of Operations",
    "emailStatus": "valid",
    "companyName": "Acme Ltd",
    "companyDomain": "acme.com",
    "listId": "…",
    "createdAt": "2026-09-21T09:12:00.000Z"
  }
]

Add a lead

POST /api/leads

Creates one lead and files it in the warehouse at the same time, under the company its address belongs to. An address that already exists answers 409 rather than merging quietly.

Body

emailrequired
string
The address. Must be valid.
firstName
string
Up to 120 characters.
lastName
string
Up to 120 characters.
title
string
Job title, up to 200 characters.
companyName
string
Used when the address gives no usable domain.
location
string
City.
country
string
Separate from location, which is a city — the warehouse filters on country.
linkedinUrl
string
The person’s own profile.
listId
string
Adds them to this list as well.

Request

curl -X POST https://luminnaleads.com.ng/api/leads \
  -H "Authorization: Bearer lum_..." \
  -H "Content-Type: application/json" \
  -d '{"email":"ada@acme.com","firstName":"Ada","title":"Head of Operations","companyName":"Acme Ltd","country":"NG"}'

Response

{ "id": "…", "email": "ada@acme.com", "fullName": "Ada" }

Lists

Named sets of leads. A campaign sends to lists, so this is how a batch becomes sendable.

List lists

GET /api/lead-lists

Newest first, each with how many leads it holds.

Request

curl https://luminnaleads.com.ng/api/lead-lists \
  -H "Authorization: Bearer lum_..."

Response

[
  { "id": "…", "name": "Apify Master List", "notes": null, "count": 6129 }
]

Create a list

POST /api/lead-lists

Returns 201 with the new list.

Body

namerequired
string
What the list is called.

Request

curl -X POST https://luminnaleads.com.ng/api/lead-lists \
  -H "Authorization: Bearer lum_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Nigeria — agencies"}'

Response

{ "id": "…", "name": "Nigeria — agencies" }

Warehouse search

The searchable store behind the Leads page: companies and the people at them, filterable on every attribute the dashboard offers.

Imports

Upload a file, confirm how its columns map, then start the import. Two steps on purpose: a mapping confirmed before 100,000 rows land beats one guessed at afterwards.

Upload a file

POST /api/warehouse/imports/upload

Multipart. Reads the header and a sample, and answers with a batch id and a suggested column mapping. Nothing is written to the warehouse yet.

Request

curl -X POST https://luminnaleads.com.ng/api/warehouse/imports/upload \
  -H "Authorization: Bearer lum_..." \
  -F "file=@leads.csv"

Response

{ "batchId": "…", "headers": ["Name","Email"], "mapping": { "Email": "contact_email" } }

Start the import

POST /api/warehouse/imports/{batchId}/start

Runs the batch with the mapping you confirm. At least one of company_name, domain or website_url must be mapped — rows need something to identify the business by. Duplicates are merged, not doubled: a company matches on domain, a person on their address.

Path parameters

batchIdrequired
string
From the upload step.

Body

mappingrequired
object
Column name to field name.
listId
string
Add everyone this file brings in to an existing list.
listName
string
Or to a new list of this name. Not both.

Request

curl -X POST https://luminnaleads.com.ng/api/warehouse/imports/BATCH_ID/start \
  -H "Authorization: Bearer lum_..." \
  -H "Content-Type: application/json" \
  -d '{"mapping":{"Email":"contact_email","Company":"company_name"},"listName":"March import"}'

Response

{ "batchId": "…", "jobId": "…" }

Campaigns

Sequences and how they are performing.

List campaigns

GET /api/campaigns

Every campaign in the workspace with its steps, most recently updated first.

Request

curl https://luminnaleads.com.ng/api/campaigns \
  -H "Authorization: Bearer lum_..."

Response

[
  { "id": "…", "name": "Q4 agencies", "status": "active", "dailyCap": 80, "steps": [ { "order": 0, "type": "email", "subject": "…" } ] }
]

Get a campaign

GET /api/campaigns/{id}

One campaign with its steps.

Path parameters

idrequired
string
Campaign id.

Request

curl https://luminnaleads.com.ng/api/campaigns/CAMPAIGN_ID \
  -H "Authorization: Bearer lum_..."

Campaign stats

GET /api/campaigns/{id}/stats

Sends, opens, clicks and replies for one campaign.

Path parameters

idrequired
string
Campaign id.

Request

curl https://luminnaleads.com.ng/api/campaigns/CAMPAIGN_ID/stats \
  -H "Authorization: Bearer lum_..."

Deliverability read

GET /api/campaigns/{id}/analysis

The same analysis the dashboard shows: what is helping or hurting this campaign’s delivery.

Path parameters

idrequired
string
Campaign id.

Request

curl https://luminnaleads.com.ng/api/campaigns/CAMPAIGN_ID/analysis \
  -H "Authorization: Bearer lum_..."

Create a campaign

POST /api/campaigns

Creates it as a draft. It does not send — see the note on launching below.

Body

namerequired
string
Up to 200 characters.
leadListIds
string[]
Lists to enrol from.
inboxIds
string[]
Mailboxes to send from. The daily cap is split across them.
steps
object[]
Up to 50. Each has type, subject, body and a delay.
dailyCap
number
1–2000 across the whole campaign.
sendWindowStart
string
"09:00".
sendWindowEnd
string
"17:00".
sendWindowTimezone
string
IANA zone. An unknown one is refused rather than silently becoming UTC.
sendDays
string[]
mon…sun.
stopOnReply
boolean
Stop mailing someone once they answer.

Request

curl -X POST https://luminnaleads.com.ng/api/campaigns \
  -H "Authorization: Bearer lum_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Q4 agencies","dailyCap":80,"sendWindowStart":"09:00","sendWindowEnd":"17:00","sendWindowTimezone":"Africa/Lagos"}'

Delete a campaign

DELETE /api/campaigns/{id}

Removes the campaign and everything recorded against it — its steps, its enrolled recipients and their send history. There is no undo and no archive: if you only want it to stop, pause it instead.

Path parameters

idrequired
string
Campaign id.

Request

curl -X DELETE https://luminnaleads.com.ng/api/campaigns/CAMPAIGN_ID \
  -H "Authorization: Bearer lum_..."

Response

{ "ok": true }

Launch a campaign

POST /api/campaigns/{id}/launch

Starts the campaign sending.

Not available with an API key. A key lives in a script, a CI config and a shell history, and one leaking should not include the ability to start mailing a workspace’s entire list from its own domain. Launch from the dashboard instead.

Request

# Sign in to the dashboard and press Launch.

Sequence templates

The saved email templates campaign steps are built from.

List templates

GET /api/email-templates

Newest first.

Request

curl https://luminnaleads.com.ng/api/email-templates \
  -H "Authorization: Bearer lum_..."

Response

[ { "id": "…", "name": "Agency intro", "subject": "quick question, {{first_name}}", "body": "…" } ]

Create a template

POST /api/email-templates

Bodies take the same variables the editor offers, so a template written here renders identically to one written in the dashboard: {{first_name}}, {{company}}, {{title}}, {{first_line}}. A variable with no value renders empty, so give it a fallback — {{title|there}}.

Body

namerequired
string
Up to 200 characters.
subjectrequired
string
Up to 500 characters.
bodyrequired
string
Up to 20,000 characters.

Request

curl -X POST https://luminnaleads.com.ng/api/email-templates \
  -H "Authorization: Bearer lum_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Agency intro","subject":"quick question, {{first_name}}","body":"Hi {{first_name}} — saw {{company}} …"}'

Delete a template

DELETE /api/email-templates/{id}

Removes the template. Campaign steps already written from it keep their own copy of the wording — a step holds text, not a reference — so deleting one changes nothing that is already scheduled or sent.

Path parameters

idrequired
string
Template id.

Request

curl -X DELETE https://luminnaleads.com.ng/api/email-templates/TEMPLATE_ID \
  -H "Authorization: Bearer lum_..."

Response

{ "ok": true }

Pipelines

The CRM boards. Cards move themselves — a lead that replies to a campaign is moved by the product — so reading a pipeline tells you where a contact currently sits.

List pipelines

GET /api/pipelines

Each pipeline with its stages and the cards on them.

Request

curl https://luminnaleads.com.ng/api/pipelines \
  -H "Authorization: Bearer lum_..."

Response

[ { "id": "…", "name": "Cold outreach", "stages": [ { "name": "Enrolled", "cards": [] } ] } ]

Pipeline templates

GET /api/pipelines/templates

The starting layouts a new pipeline can be created from.

Request

curl https://luminnaleads.com.ng/api/pipelines/templates \
  -H "Authorization: Bearer lum_..."

Create a pipeline

POST /api/pipelines

From a template, and optionally bound to a campaign so its cards follow that campaign’s leads.

Body

namerequired
string
Up to 120 characters.
template
string
A key from the templates endpoint.
campaignId
string
Bind the board to this campaign.

Request

curl -X POST https://luminnaleads.com.ng/api/pipelines \
  -H "Authorization: Bearer lum_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Cold outreach","template":"cold-outreach"}'

Delete a pipeline

DELETE /api/pipelines/{id}

Removes the board, its stages and every card on it. The leads themselves are untouched — a card is a position, not the person — so deleting a board loses where everyone stood, not who they are.

Path parameters

idrequired
string
Pipeline id.

Request

curl -X DELETE https://luminnaleads.com.ng/api/pipelines/PIPELINE_ID \
  -H "Authorization: Bearer lum_..."

Response

{ "ok": true }

ICPs & offers

The definitions the AI copy and the lead filters read. Changing one changes what later campaigns are written against.

List ICPs

GET /api/icps

Every saved ideal-customer profile.

Request

curl https://luminnaleads.com.ng/api/icps \
  -H "Authorization: Bearer lum_..."

Create an ICP

POST /api/icps

All fields optional except the name.

Body

name
string
What this profile is called.
industry
string
Free text.
companySize
string
For example "11-50".
geography
string
Where they are.
jobTitles
string
Who you are writing to.
painPoints
string[]
What they are stuck on.
goals
string[]
What they want.

Request

curl -X POST https://luminnaleads.com.ng/api/icps \
  -H "Authorization: Bearer lum_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"NG agencies","industry":"Marketing","companySize":"11-50","geography":"Nigeria"}'

Update an ICP

PATCH /api/icps/{id}

Same fields as creation. 404 when the id is not in this workspace.

Path parameters

idrequired
string
ICP id.

Request

curl -X PATCH https://luminnaleads.com.ng/api/icps/ICP_ID \
  -H "Authorization: Bearer lum_..." \
  -H "Content-Type: application/json" \
  -d '{"geography":"Nigeria and Ghana"}'

Delete an ICP

DELETE /api/icps/{id}

Removes the profile. Offers pointing at it are kept and unlinked rather than deleted with it — the response says how many — because an offer is a thing you sell and survives the audience definition it was written for.

Path parameters

idrequired
string
ICP id.

Request

curl -X DELETE https://luminnaleads.com.ng/api/icps/ICP_ID \
  -H "Authorization: Bearer lum_..."

Response

{ "ok": true, "unlinkedOffers": 2 }

List offers

GET /api/offers

Every saved offer.

Request

curl https://luminnaleads.com.ng/api/offers \
  -H "Authorization: Bearer lum_..."

Create an offer

POST /api/offers

What you are selling, to which ICP, and how the email asks for the meeting.

Body

name
string
What this offer is called.
icpId
string
The profile it is aimed at.
price
string
Free text.
whatItIs
string
One line.
valueProp
string
Why it matters to them.
socialProof
string
Evidence it works.
ctaText
string
The ask.

Request

curl -X POST https://luminnaleads.com.ng/api/offers \
  -H "Authorization: Bearer lum_..." \
  -H "Content-Type: application/json" \
  -d '{"name":"Warmup audit","whatItIs":"A 20-minute deliverability review","ctaText":"Worth a look?"}'

Update an offer

PATCH /api/offers/{id}

Same fields as creation. 404 when the id is not in this workspace.

Path parameters

idrequired
string
Offer id.

Request

curl -X PATCH https://luminnaleads.com.ng/api/offers/OFFER_ID \
  -H "Authorization: Bearer lum_..." \
  -H "Content-Type: application/json" \
  -d '{"price":"₦250,000"}'

Delete an offer

DELETE /api/offers/{id}

Removes the offer. The ICP it was aimed at is left alone, and campaigns already written against it keep their copy.

Path parameters

idrequired
string
Offer id.

Request

curl -X DELETE https://luminnaleads.com.ng/api/offers/OFFER_ID \
  -H "Authorization: Bearer lum_..."

Response

{ "ok": true }

Errors

Failures answer with a JSON body carrying error, a sentence meant to be shown to a person.

400
The request was understood and something in it is wrong. The body names what.
401
The key is missing, malformed, or has been revoked.
403
The key is valid and this endpoint is outside what keys may reach.
404
No such record in this workspace. Ids from another workspace look identical to ids that do not exist.
409
It already exists — a lead with that address, for instance.
429
Rate limited. Back off and retry.