Lumenbase API Reference
The Lumenbase API is organized around REST. Our API has predictable resource-oriented URLs, accepts JSON request bodies, returns JSON-encoded responses, and uses standard HTTP response codes, authentication, and verbs.
You use API keys from your workspace to authenticate requests. Create and manage keys in Settings -> Company Settings -> Integrations -> API. The API key determines which workspace the request applies to.
Full CRUD is available for Contacts, Companies, Deals, Tasks, Invoices, and Activities.
Base URL & authentication
https://api.lumenbase.ioAll requests require:
Authorization: Bearer <YOUR_API_KEY>
Content-Type: application/jsonOptional: X-Idempotency-Key to prevent duplicate processing on retries; X-Request-Id for tracing.
Response envelope: Success responses return { success: true, data: { ... }, meta: { request_id, api_version, timestamp } }. Errors return { success: false, error: { code, message, details? }, meta: { ... } }.
Setup
- Go to Settings -> Company Settings -> Integrations -> API. Click "Create API Key" and select the scopes you need.
- Copy the key immediately: it won't be shown again.
- Optionally create a Connector to map incoming fields to CRM fields and set matching rules (for ingest endpoints).
Authentication
Every request carries a workspace API key in the Authorization header. There is no other accepted form.
Request header
Authorization: Bearer lmb_0f2a...- A key is the prefix lmb_ followed by 64 hexadecimal characters.
- Keys are stored only as a SHA-256 hash. We cannot recover one for you, so save it when it is created.
- Revoking a key takes effect immediately. Create a second key before rotating so the changeover has no gap.
A key carries scopes. Read the Known limitations section before choosing them, because two endpoints do not accept the granular names today.
API key scopes
Each API key has one or more scopes that control what it can access. Assign only the scopes you need.
| Field | Type | Description |
|---|---|---|
| contacts:read | scope | Read and list contacts |
| contacts:write | scope | Create, update, and delete contacts |
| companies:read | scope | Read and list companies |
| companies:write | scope | Create, update, and delete companies |
| deals:read | scope | Read and list deals |
| deals:write | scope | Create, update, and delete deals |
| tasks:read | scope | Read and list tasks |
| tasks:write | scope | Create, update, and delete tasks |
| invoices:read | scope | Read and list invoices |
| invoices:write | scope | Create, update, and delete invoices |
| activities:read | scope | Read and list activities |
| activities:write | scope | Create and delete activities |
| read | scope | Read access to all entity types |
| admin | scope | Full access to all endpoints |
The legacy scopes contact_ingest and activity_ingest still work, and a key holding one is accepted where the matching read or write scope is required. The reverse is not true: a key holding only contacts:write is not accepted where contact_ingest is required. See Known limitations.
Pagination
All list endpoints support pagination via query parameters:
| Field | Type | Description |
|---|---|---|
| page | integer | Page number (default: 1) |
| per_page | integer | Items per page, max 100 (default: 25) |
| sort_by | string | Field to sort by (varies per entity) |
| sort_order | string | "asc" or "desc" (default: "desc") |
| search | string | Free-text search across key fields |
Paginated response shape:
{
"success": true,
"data": {
"items": [ ... ],
"pagination": {
"page": 1,
"per_page": 25,
"total": 142,
"total_pages": 6
}
},
"meta": { "request_id": "...", "api_version": "1", "timestamp": "..." }
}POST: Create / Ingest Contact
Create or update contacts from forms, landing pages, or third-party systems. Matches by email; optionally creates or links companies.
https://api.lumenbase.io/functions/v1/v1-public-contactsScope:
Request body:
{
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"phone": "+1-555-123-4567",
"title": "Product Manager",
"company_name": "Acme Inc",
"company_domain": "acme.com",
"source": "website_form",
"signal_strength": 75,
"message": "Interested in your enterprise plan"
}| Field | Type | Description |
|---|---|---|
| first_name | string | Contact first name |
| last_name | string | Contact last name |
| string | Email (used for matching) | |
| phone | string | Phone number |
| title | string | Job title |
| company_name | string | Company name (creates if new) |
| company_domain | string | Company domain for matching |
| source | string | Lead source |
| signal_strength | number | Intent signal 0-100 |
| message | string | Notes |
201 when new contact created; 200 when existing contact updated.
POST: Batch Ingest Contacts
Ingest up to 100 contacts in one request. Same field schema as Contact Ingest.
https://api.lumenbase.io/functions/v1/v1-public-contacts-batchScope:
{
"contacts": [
{ "email": "a@example.com", "first_name": "Alice" },
{ "email": "b@example.com", "first_name": "Bob" }
],
"options": { "stop_on_error": false }
}201 all succeed; 207 mixed; 400 all fail. Max 100 contacts, 500 KB payload.
GET: Retrieve / List Contacts
https://api.lumenbase.io/functions/v1/v1-public-contacts-crud?id={uuid}Retrieve a single contact by ID.
https://api.lumenbase.io/functions/v1/v1-public-contacts-crud?page=1&per_page=25List contacts with pagination.
Scope:
Filters:
| Field | Type | Description |
|---|---|---|
| string | Filter by exact email | |
| company_id | uuid | Filter by company |
| status | string | Filter by status |
| search | string | Search first_name, last_name, email |
Sort by: created_at, updated_at, first_name, last_name, email
PATCH: Update Contact
https://api.lumenbase.io/functions/v1/v1-public-contacts-crud?id={uuid}Scope:
{
"title": "Senior Product Manager",
"phone": "+1-555-999-0000"
}Updatable fields: first_name, last_name, email, phone, title, company_id, source, status.
DELETE: Delete Contact
https://api.lumenbase.io/functions/v1/v1-public-contacts-crud?id={uuid}Scope:
{ "success": true, "data": { "deleted": true, "id": "uuid" } }POST: Create Company
https://api.lumenbase.io/functions/v1/v1-public-companiesScope:
Upserts by domain if provided. Creates new if no match.
{
"name": "Acme Inc",
"domain": "acme.com",
"industry": "Technology",
"size": "51-200",
"website": "https://acme.com",
"city": "San Francisco",
"country": "US"
}| Field | Type | Description |
|---|---|---|
| name | string | Company name |
| domain | string | Company domain (used for matching) |
| industry | string | Industry |
| size | string | Company size range |
| website | string | Website URL |
| phone | string | Phone number |
| city | string | City |
| state | string | State/region |
| country | string | Country code |
| status | string | Status |
GET: Retrieve / List Companies
https://api.lumenbase.io/functions/v1/v1-public-companies?id={uuid}https://api.lumenbase.io/functions/v1/v1-public-companies?page=1&per_page=25Scope:
Filters:
| Field | Type | Description |
|---|---|---|
| domain | string | Filter by exact domain |
| industry | string | Filter by industry |
| search | string | Search name, domain |
Sort by: created_at, updated_at, name, domain
PATCH: Update Company
https://api.lumenbase.io/functions/v1/v1-public-companies?id={uuid}Scope:
{ "industry": "SaaS", "size": "201-500" }DELETE: Delete Company
https://api.lumenbase.io/functions/v1/v1-public-companies?id={uuid}Scope:
POST: Create Deal
https://api.lumenbase.io/functions/v1/v1-public-dealsScope:
{
"title": "Enterprise License: Acme",
"value": 50000,
"currency": "USD",
"stage": "Proposal",
"expected_close_date": "2026-04-15",
"company_id": "uuid",
"contact_id": "uuid"
}| Field | Type | Description |
|---|---|---|
| title | string (required) | Deal title |
| value | number | Deal value |
| currency | string | Currency code (default: USD) |
| stage | string | Pipeline stage |
| status | string | Deal status |
| probability | number | Win probability 0-100 |
| expected_close_date | date | Expected close date |
| company_id | uuid | Linked company |
| contact_id | uuid | Linked contact |
| pipeline_id | uuid | Pipeline ID |
| assigned_to | uuid | Assigned user ID |
GET: Retrieve / List Deals
https://api.lumenbase.io/functions/v1/v1-public-deals?id={uuid}https://api.lumenbase.io/functions/v1/v1-public-deals?page=1&per_page=25Scope:
Filters:
| Field | Type | Description |
|---|---|---|
| stage | string | Filter by stage |
| status | string | Filter by status |
| company_id | uuid | Filter by company |
| pipeline_id | uuid | Filter by pipeline |
| search | string | Search title |
Sort by: created_at, updated_at, title, value, expected_close_date
PATCH: Update Deal
https://api.lumenbase.io/functions/v1/v1-public-deals?id={uuid}Scope:
{ "stage": "Negotiation", "probability": 80 }DELETE: Delete Deal
https://api.lumenbase.io/functions/v1/v1-public-deals?id={uuid}Scope:
POST: Create Task
https://api.lumenbase.io/functions/v1/v1-public-tasksScope:
{
"title": "Follow up with Acme",
"description": "Send proposal by Friday",
"priority": "high",
"due_date": "2026-03-01",
"assigned_to": "uuid",
"contact_id": "uuid",
"deal_id": "uuid"
}| Field | Type | Description |
|---|---|---|
| title | string (required) | Task title |
| description | string | Task description |
| status | string | Task status |
| priority | string | Priority: low, medium, high, urgent |
| due_date | date | Due date |
| assigned_to | uuid | Assigned user ID |
| contact_id | uuid | Linked contact |
| company_id | uuid | Linked company |
| deal_id | uuid | Linked deal |
GET: Retrieve / List Tasks
https://api.lumenbase.io/functions/v1/v1-public-tasks?id={uuid}https://api.lumenbase.io/functions/v1/v1-public-tasks?page=1&per_page=25Scope:
Filters:
| Field | Type | Description |
|---|---|---|
| status | string | Filter by status |
| priority | string | Filter by priority |
| assigned_to | uuid | Filter by assignee |
| contact_id | uuid | Filter by contact |
| company_id | uuid | Filter by company |
| deal_id | uuid | Filter by deal |
| search | string | Search title |
Sort by: created_at, updated_at, title, due_date, priority
PATCH: Update Task
https://api.lumenbase.io/functions/v1/v1-public-tasks?id={uuid}Scope:
{ "status": "completed", "description": "Proposal sent" }DELETE: Delete Task
https://api.lumenbase.io/functions/v1/v1-public-tasks?id={uuid}Scope:
POST: Create Invoice
https://api.lumenbase.io/functions/v1/v1-public-invoicesScope:
{
"invoice_number": "INV-2026-001",
"status": "draft",
"issue_date": "2026-02-19",
"due_date": "2026-03-19",
"subtotal": 5000,
"tax_amount": 500,
"total": 5500,
"currency": "USD",
"company_id": "uuid",
"contact_id": "uuid",
"notes": "Net 30"
}| Field | Type | Description |
|---|---|---|
| invoice_number | string | Invoice number |
| status | string | draft, sent, paid, overdue, cancelled |
| issue_date | date | Issue date |
| due_date | date | Due date |
| subtotal | number | Subtotal amount |
| tax_amount | number | Tax amount |
| total | number | Total amount |
| currency | string | Currency code |
| company_id | uuid | Linked company |
| contact_id | uuid | Linked contact |
| deal_id | uuid | Linked deal |
| notes | string | Notes |
| line_items | json | Line items array |
GET: Retrieve / List Invoices
https://api.lumenbase.io/functions/v1/v1-public-invoices?id={uuid}https://api.lumenbase.io/functions/v1/v1-public-invoices?page=1&per_page=25Scope:
Filters:
| Field | Type | Description |
|---|---|---|
| status | string | Filter by status |
| company_id | uuid | Filter by company |
| contact_id | uuid | Filter by contact |
| search | string | Search invoice_number |
Sort by: created_at, updated_at, invoice_number, due_date, total, issue_date
PATCH: Update Invoice
https://api.lumenbase.io/functions/v1/v1-public-invoices?id={uuid}Scope:
{ "status": "sent", "due_date": "2026-04-01" }DELETE: Delete Invoice
https://api.lumenbase.io/functions/v1/v1-public-invoices?id={uuid}Scope:
POST: Create / Ingest Activity
https://api.lumenbase.io/functions/v1/v1-public-activitiesScope:
Record activities from Jira, support tools, webhooks, or any external source. Links to contacts/companies by email or domain.
{
"type": "jira_issue_created",
"entity_email": "john.doe@example.com",
"entity_domain": "acme.com",
"title": "PROJ-123: New Feature Request",
"description": "Customer requested dashboard export",
"timestamp": "2026-01-15T10:30:00Z",
"data": { "issue_key": "PROJ-123", "priority": "high" }
}Activity types:
Entity linking:
entity_email→ Contact by emailentity_domain→ Company by domainentity_id→ Deal by ID
GET: Retrieve / List Activities
https://api.lumenbase.io/functions/v1/v1-public-activities-crud?id={uuid}https://api.lumenbase.io/functions/v1/v1-public-activities-crud?page=1&per_page=25Scope:
Filters:
| Field | Type | Description |
|---|---|---|
| type | string | Filter by activity type |
| contact_id | uuid | Filter by contact |
| company_id | uuid | Filter by company |
| deal_id | uuid | Filter by deal |
| search | string | Search title |
Sort by: created_at, type, title
DELETE: Delete Activity
https://api.lumenbase.io/functions/v1/v1-public-activities-crud?id={uuid}Scope:
Field mapping (Connectors)
Connectors map incoming JSON fields to CRM fields. Use dot notation for nested JSON, e.g. user.profile.email -> Email. Connectors apply to the ingest endpoints (Contact Ingest, Activity Ingest).
Matching rules
Contact: email (recommended), phone, or ID. Company: domain (recommended), name, or ID. Deal: ID or title. If no match and required data is provided, a new record is created.
Error handling
| Field | Type | Description |
|---|---|---|
| 401 | UNAUTHORIZED | Invalid or missing API key; use Bearer <key> |
| 403 | FORBIDDEN | API key lacks required scope |
| 400 | VALIDATION_ERROR | Invalid request payload or missing required fields |
| 404 | NOT_FOUND | Resource not found |
| 405 | METHOD_NOT_ALLOWED | HTTP method not supported for this endpoint |
| 413 | PAYLOAD_TOO_LARGE | Request body exceeds max size (50 KB single, 500 KB batch) |
| 429 | RATE_LIMIT_EXCEEDED | Too many requests; check X-RateLimit-* and Retry-After |
| 500 | INTERNAL_ERROR | Server error |
| 503 | SERVICE_UNAVAILABLE | Rate-limit counter unavailable; the request was not processed, retry is safe |
| 207 | MULTI_STATUS | Batch partially succeeded; read results[].success per item rather than the status alone |
| 410 | GONE | Endpoint retired past its sunset date; the body names its successor |
Branch on error.code, not on the message. Messages are written for people and can change; codes are part of the contract.
Error response body:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Human-readable message",
"details": { }
},
"meta": { "request_id": "...", "api_version": "1", "timestamp": "..." }
}Rate limits
Limits are per workspace, in a fixed one-minute window, counted per bucket rather than per endpoint.
- Contact reads and writes: 100 per minute, 10,000 per day.
- Activity reads and writes: 100 per minute, 50,000 per day.
- Batch contact ingest: 5 per minute. Each call carries up to 100 contacts.
Every response carries
X-RateLimit-Limit: requests allowed in the current window.X-RateLimit-Remaining: requests left in it.X-RateLimit-Reset: when the window resets.X-DailyLimit-Remaining: present on buckets with a daily cap.Retry-After: on a 429 only, the wait in seconds.
A 503 with code SERVICE_UNAVAILABLE means the limit counter itself could not be read. Your request was not processed, so retrying is safe and will not double-write.
Idempotency
Send X-Idempotency-Key so a retry after a timeout cannot create a duplicate. Replaying the same key inside 24 hours returns the original result instead of writing again.
This is implemented on two endpoints only: POST /v1-public-contacts and POST /v1-public-activities. The other endpoints ignore the header. On those, make a retry safe by reading the record first, or by using an update rather than a create.
X-Idempotency-Key: 7d3f9c1e-2b84-4a17-9f0d-6c5e8a2b1d34Use a value unique to the operation you are retrying, 8 to 128 characters. A UUID per logical write works well.
Keys are scoped to your workspace and the endpoint, and expire after 24 hours.
Versioning
The version is in the path and echoed in every response as meta.api_version and the X-API-Version header.
New fields can appear in a response at any time and are not treated as a breaking change. Parse defensively and ignore fields you do not recognise, rather than validating against an exact shape.
A breaking change means a new path prefix. The existing one keeps working through the deprecation process below.
The application version shown elsewhere in the product is a release date, not an API version. The two move independently.
Deprecation policy
When an endpoint is retiring, it says so in its own responses before it stops working.
A retiring endpoint returns
Deprecation: trueSunset: the date it stops answering, as an HTTP date.Link: the replacement, with rel pointing at the successor version.
The sunset date is at least six months after the deprecation is announced. After it passes the endpoint returns 410 Gone, naming its successor.
Currently deprecated
POST /public-contacts, superseded by POST /v1-public-contacts. Sunset 26 November 2026.
Log these headers. They are the earliest warning you will get, and they arrive on responses that are otherwise succeeding.
OpenAPI description
The full contract is published as an OpenAPI 3.1 document. Import it into Postman or Insomnia, generate a client, or diff it between releases to see exactly what changed.
Document URL
https://lumenbase.io/api/openapi.jsonFetch it
curl -s https://lumenbase.io/api/openapi.json -o lumenbase-openapi.jsonIt is generated from the same source the endpoints are checked against, so it describes what the API actually does rather than what was intended.
Known limitations
Behaviour that does not match what the rest of this reference would lead you to expect. These are being fixed. They are listed here because discovering them at runtime is worse.
- Deal endpoints return 429 on every request, whatever your volume. Contacts, companies, tasks, invoices and activities are unaffected.
- The company, task and invoice endpoints check contacts scopes rather than their own. A key needs contacts:read or contacts:write to use them.
- POST /v1-public-contacts, the batch endpoint and POST /v1-public-activities accept only the legacy scope names contact_ingest and activity_ingest, or admin. A key issued with contacts:write alone is refused with 403.
Until these are fixed
Give integration keys the admin scope, or the legacy contact_ingest and activity_ingest scopes, and avoid the deal endpoints. We would rather tell you this than have you debug it.
Best practices
- Use separate connectors per source to track origin and customize mapping.
- Include email for contact matching (most reliable).
- Send X-Idempotency-Key when retrying a contact or activity ingest. The other endpoints ignore it; see Idempotency.
- Rate limit: 100 requests/minute per workspace; daily caps 10K contacts and 50K activities. Check X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and Retry-After on 429.
- Read Known limitations before choosing scopes. Granular scopes are the right default everywhere except the ingest endpoints, which currently require the legacy names or admin.
- For bulk operations, use the batch endpoint to reduce API calls.
- Log meta.request_id from every response. It identifies the request in support.
