API keys
API keys authenticate requests to the Lighthouse public API, and to the MCP server, which reuses the same keys. Keys live in Settings → API & Agents → API keys.
Creating a key
On the API keys tab, click Create personal key, or, as an org admin, create one from the Shared API keys section below it. A shared key asks for:
- 1Name: Required, up to 100 characters. This is just a label; it isn't part of the key itself.
- 2Expiration: Optional. Leave it blank for a key that doesn't expire.
- 3Capabilities: Pick at least one. These decide what the key is allowed to do (see the table below). RUN_METRIC is selected by default.
- 4Allow-lists: Optional, per entity type: metrics, data sources, data sets, and Slack (communication) sources. Leave a list empty to allow all entities of that type in the org.
lh_a1b2c3d4e5, an 11-character prefix). Lighthouse only stores a SHA-256 hash of the full secret, so a lost key can't be recovered; you'd need to revoke it and create a new one.Capabilities
A key can only call the endpoints and MCP tools its capabilities cover. Capabilities with no REST endpoint below are used by MCP tools. Capabilities are additive: pick every action the key needs.
Metrics
| Capability | Allows |
|---|---|
| LIST_METRICS | List metrics |
| GET_METRIC | Read a single metric and its configuration |
| CREATE_METRIC | Create a new metric, and test a not-yet-saved configuration |
| UPDATE_METRIC | Update an existing metric, and test a not-yet-saved configuration |
| DELETE_METRIC | Permanently delete a metric and its history |
| DUPLICATE_METRIC | Copy a metric as a new draft |
| RUN_METRIC | Trigger a metric run, and check a run's status |
| READ_METRIC_RESULTS | Search past metric runs and segment results |
| INTERACT_METRIC | Acknowledge, resolve, or mark a segment result as hit |
| SEND_TEST_NOTIFICATION | Send a real test message to the places a metric notifies |
Data sources
| Capability | Allows |
|---|---|
| LIST_DATA_SOURCES | List data sources |
| GET_DATA_SOURCE | Read a single data source |
| QUERY_DATA_SOURCE | Run read-only SQL on a data source and get back up to 100 rows |
| SETUP_DATA_SOURCE | Start connecting a data source or replacing its credentials, through a short-lived link you open in Lighthouse |
Data sets
| Capability | Allows |
|---|---|
| LIST_DATA_SETS | List data sets |
| GET_DATA_SET | Read a single data set, including column metadata |
| CREATE_DATA_SET | Create data sets from tables of a data source |
| UPDATE_DATA_SET | Change a data set's name, description, status, default date column, and usable columns |
Communication sources and integrations
| Capability | Allows |
|---|---|
| LIST_COMMUNICATION_SOURCES | List communication sources |
| GET_COMMUNICATION_SOURCE | Read a single communication source, including its channels and users |
| SETUP_COMMUNICATION_SOURCE | Start connecting Slack or email, through a short-lived link you open in Lighthouse |
| SETUP_INTEGRATION_ACTION_SOURCE | Start connecting an integration action source or replacing its credentials, through a short-lived link |
Administration (org admins only)
| Capability | Allows |
|---|---|
| LIST_BUSINESS_GROUPS | List business groups and their members, and find users by name or email |
| CREATE_BUSINESS_GROUP | Create a business group with its members |
| UPDATE_BUSINESS_GROUP | Rename a business group, and add or remove its members |
| GET_ORGANIZATION_SETTINGS | Read the organization's time zone, AI model, and notification settings |
| UPDATE_ORGANIZATION_SETTINGS | Change the organization's time zone, error notifications, and automatic metric suggestions |
| MANAGE_MEMBERS | List, invite, change the role of, and remove members |
RUN_METRIC and a metric allow-list of two metric IDs can only run those two, even though the capability itself is org-wide. Writes made by a key run on behalf of the user who created it, unless the request sends an x-on-behalf-of-user-id header.Managing keys
The keys table shows, per key:
| Column | What it shows |
|---|---|
| Created By | The member who created the key. Shared keys are created by org admins. The personal keys table leaves this column out, because every key there is yours |
| Created At | Creation timestamp |
| Expires | The expiration date, or blank if it never expires |
| Last Used | The most recent successful call |
| Status | Active, Expired, or Revoked |
Click View History on a key to see its full call log: occurred-at timestamp, endpoint, metric, on-behalf-of user, outcome, HTTP status, and source IP.
Authenticating requests
Send the key in an x-api-key header, not Authorization: Bearer. Requests go to the Lighthouse API host, https://api.lighthouseai.co, not the app URL you sign in to, under /public-api/v1.
x-api-key: lh_... x-on-behalf-of-user-id: <optional user id>
Endpoints
- GET
- Path/metrics
- CapabilityLIST_METRICS
- GET
- Path/metrics/:metricId
- CapabilityGET_METRIC
- GET
- Path/metrics/:metricId/configuration
- CapabilityGET_METRIC
- POST
- Path/metrics
- CapabilityCREATE_METRIC
- PATCH
- Path/metrics/:metricId
- CapabilityUPDATE_METRIC
- POST
- Path/metrics/test
- CapabilityCREATE_METRIC or UPDATE_METRIC
- POST
- Path/metric-configuration-examples/search
- CapabilityCREATE_METRIC or UPDATE_METRIC
- POST
- Path/metrics/:metricId/run
- CapabilityRUN_METRIC
- GET
- Path/metric-runs/:metricRunId
- CapabilityRUN_METRIC
- POST
- Path/metric-runs/search
- CapabilityREAD_METRIC_RESULTS
- POST
- Path/segment-results/search
- CapabilityREAD_METRIC_RESULTS
- POST
- Path/segment-results/:segmentRunId/actions
- CapabilityINTERACT_METRIC
- GET
- Path/data-sources
- CapabilityLIST_DATA_SOURCES
- GET
- Path/data-sources/:id
- CapabilityGET_DATA_SOURCE
- GET
- Path/data-sets
- CapabilityLIST_DATA_SETS
- GET
- Path/data-sets/:id
- CapabilityGET_DATA_SET
- GET
- Path/communication-sources
- CapabilityLIST_COMMUNICATION_SOURCES
- GET
- Path/communication-sources/:id
- CapabilityGET_COMMUNICATION_SOURCE
IDs can be either the UUID or the sequential integer ID shown in the app. GET /data-sets/:id includes column metadata; GET /communication-sources/:id includes channels and users, and accepts its own searchText/skip/limit to filter that directory. Full request and response bodies for every endpoint are in the reference section below.
Response shape
Every response, success or failure, is one of three shapes:
- Entity
- Used bySingle-resource GET, create, update
- Structure{ "success": true, "entity": { ... } }
- Search
- Used byList GETs and the search POSTs
- Structure{ "success": true, "total": number, "entities": [...] }
- Error
- Used byAny 4xx/5xx
- Structure{ "success": false, "error": { "errorCode", "statusCode", "message", "path" } }
relatedEntities field. What you see in entities is the complete record. There's no nextCursor or hasMore; see Pagination below for how to page through results.Pagination
The list endpoints (/metrics, /data-sources, /data-sets, /communication-sources) and the search POSTs all use the same offset pagination: a query or body of skip/limit, not a cursor.
- List endpoints
- Default limit50
- Max limit100
- NotesAlso takes optional searchText
- /metric-runs/search, /segment-results/search
- Default limit50
- Max limit100
- Notesskip defaults to 0
- /metric-configuration-examples/search
- Default limit10
- Max limit50 (hard clamp)
- NotesNo skip; this one doesn't paginate past the clamp
hasMore field. Compare the running total of items you've fetched against the response's total, and keep incrementing skip by your limit while it's still below total.Errors
All errors share the shape above. Real examples:
- 401
- errorCodeUNAUTHORIZED
- MessageInvalid API key
- CauseMissing, invalid, or revoked key
- 403
- errorCodeFORBIDDEN
- Message-
- CauseMetric not on the key's allow-list, or capability missing
- 404
- errorCodeENTITY_NOT_FOUND
- MessageEntity METRIC with id <uuid> not found
- CauseUnknown metric, run, or segment result ID
- 400
- errorCodeBAD_REQUEST
- MessageValidation error text, or "Metric configuration is invalid"
- CauseInvalid create/update body, or an invalid action on segment-results/actions
- 429
- errorCodeRATE_LIMITED
- MessageRate limit exceeded
- CauseMore than 60 calls in 60 seconds
POST /metrics/test is the one exception: an invalid metric configuration is still a 200, with { "valid": false, "errors": [...] } in the body instead of the error envelope. Check valid, not just the HTTP status, when calling that endpoint.Rate limits
Each key is limited to 60 calls per 60 seconds. Going over returns the 429/RATE_LIMITED error above.
Endpoint reference
Request and response bodies for every write and search endpoint, taken directly from the API's validation schemas and test fixtures.
Create a metric: POST /metrics
The body is validated as a metric configuration once flattened; you can send either a flat object or nested form JSON (a scheduleDefinition object gets unpacked automatically). type, name, status, and severity are always required.
- type
- RequiredYes
- Notes"CUSTOM" or "RAW_SQL"
- name
- RequiredYes
- Notes-
- status
- RequiredYes
- Notes"DRAFT", "LIVE", "PAUSED", or "INVALID"
- severity
- RequiredYes
- Notes"S0" through "S5"
- dataSetId
- RequiredIf CUSTOM and status ≠ DRAFT
- Notes-
- sql, sqlColumns
- RequiredIf RAW_SQL and status ≠ DRAFT
- NotesNon-draft RAW_SQL is live-tested against the warehouse before saving
- kpis
- RequiredNo
- NotesDefaults to []
- enableSegments
- RequiredNo
- NotesDefaults to false
- scheduleConfigurationType
- RequiredNo
- NotesDefaults to "MANUAL"
POST https://api.lighthouseai.co/public-api/v1/metrics
x-api-key: lh_...
{
"type": "RAW_SQL",
"name": "Failed query count",
"status": "DRAFT",
"severity": "S2",
"dataSourceId": "<data-source-uuid>",
"sql": "SELECT COUNT(*) AS count FROM snowflake.account_usage.query_history WHERE execution_status IN ('FAIL','INCIDENT')",
"metricColumnName": "count",
"sqlColumns": [
{ "columnName": "count", "dataType": "NUMBER", "roles": ["METRIC"] }
]
}{
"type": "CUSTOM",
"name": "Row count",
"status": "DRAFT",
"severity": "S2",
"dataSourceId": "<data-source-uuid>",
"dataSetId": "<data-set-uuid>"
}{
"success": true,
"entity": {
"entityId": "<uuid>",
"sequentialIdentifier": 12,
"name": "Failed query count",
"description": null,
"type": "RAW_SQL",
"status": "DRAFT",
"severity": "S2",
"ownerId": "<user-uuid>",
"disabled": false,
"dataSourceId": "<uuid>",
"dataSetId": null,
"createdAt": "2026-09-14T12:00:00.000Z",
"updatedAt": "2026-09-14T12:00:00.000Z",
"metricRunStatus": null,
"lastExecutionTime": null,
"nextExecutionTime": null
}
}sql or full configuration. Fetch GET /metrics/:metricId/configuration for that. A metric outside the key's allow-list returns 403.Update a metric: PATCH /metrics/:metricId
Every field is optional: send only what's changing. metricId in the body is ignored if present; the URL param is what's used.
PATCH https://api.lighthouseai.co/public-api/v1/metrics/12
x-api-key: lh_...
{ "name": "Public updated name" }{
"success": true,
"entity": { "...": "...", "name": "Public updated name" }
}Allow-list miss → 403. Unknown metric → 404.
Test a metric: POST /metrics/test
Validates and previews a metric configuration without saving it. Send either a flat payload with an optional metricId, or { "metricId", "configuration": {...} }.
{ "type": "CUSTOM", "name": "Missing severity", "status": "PAUSED" }{
"success": true,
"valid": false,
"errors": ["... severity is required ..."]
}{
"success": true,
"valid": true,
"preview": {
"columnNames": ["count"],
"rows": [{ "count": "42" }],
"isTruncatedByRowLimit": false
}
}Search example configurations: POST /metric-configuration-examples/search
- searchText
- RequiredNo
- NotesFree-text search
- limit
- RequiredNo
- NotesDefault 10, clamped to 50 max
- peekExampleIds
- RequiredNo
- NotesArray of example IDs to return full configuration for (max 5)
{
"searchText": "sql threshold",
"peekExampleIds": ["raw-sql-threshold"]
}{
"success": true,
"total": 3,
"examples": [
{ "id": "raw-sql-threshold", "title": "...", "description": "...", "tags": ["sql", "threshold"] }
],
"peeked": [
{
"id": "raw-sql-threshold",
"title": "...",
"description": "...",
"tags": ["sql", "threshold"],
"configuration": { "type": "RAW_SQL", "...": "..." }
}
]
}peekExampleIds come back with a configuration body, inside peeked. The examples array is metadata only; this endpoint uses examples/peeked instead of the usual entities field.Run a metric: POST /metrics/:metricId/run
The most common call: trigger a run and get back an ID to poll.
POST https://api.lighthouseai.co/public-api/v1/metrics/{metricId}/run
x-api-key: lh_...{
"success": true,
"metricRunId": "b3f2c9a0-..."
}Search metric runs: POST /metric-runs/search
- metricIds
- RequiredYes (min 1)
- NotesUUID or sequential IDs
- skip
- RequiredNo
- NotesDefault 0
- limit
- RequiredNo
- NotesDefault 50, max 100
- status
- RequiredNo
- Notes"PENDING", "IN_PROGRESS", "DONE", or "ERROR"
- triggerType
- RequiredNo
- Notes"SCHEDULED", "MANUAL", or "API_KEY"
{ "metricIds": ["<uuid>"] }{
"success": true,
"total": 1,
"entities": [
{
"metricRunId": "<uuid>",
"metricId": "<uuid>",
"sequentialIdentifier": 12,
"metricName": "Failed query count",
"status": "PENDING",
"triggerType": "API_KEY",
"runTime": "2026-09-14T12:00:00.000Z",
"error": null,
"isAcknowledged": false
}
]
}Search segment results: POST /segment-results/search
- metricIds
- RequiredYes (min 1)
- Notes-
- skip / limit
- RequiredNo
- NotesDefault 0 / 50, max 100
- metricRunId
- RequiredNo
- NotesNarrow to one run
- isThresholdMet, isHit, isAcknowledged, isMarkedAsResolved
- RequiredNo
- NotesBooleans
{ "metricIds": ["12"] }{
"success": true,
"total": 1,
"entities": [
{
"segmentRunId": "<uuid>",
"metricRunId": "<uuid>",
"metricId": "<uuid>",
"sequentialIdentifier": 12,
"metricName": "Failed query count",
"segment": "WAREHOUSE_A",
"value": 17,
"isThresholdMet": true,
"triggerResult": "...",
"isHit": false,
"isAcknowledged": false,
"isResolved": false,
"severity": "S2",
"runTime": "2026-09-14T12:00:00.000Z"
}
]
}Act on a segment result: POST /segment-results/:segmentRunId/actions
Six valid values for action, each with an undo:
| Action | Undo |
|---|---|
| ACKNOWLEDGE | UNDO_ACKNOWLEDGE |
| MARK_AS_RESOLVED | UNDO_MARK_AS_RESOLVED |
| MARK_HIT | UNDO_MARK_HIT |
POST https://api.lighthouseai.co/public-api/v1/segment-results/{segmentRunId}/actions
x-api-key: lh_...
{ "action": "ACKNOWLEDGE" }{
"success": true,
"entity": {
"action": "ACKNOWLEDGE",
"segmentResult": { "segmentRunId": "...", "isAcknowledged": true }
}
}action returns 400.GET and list endpoints
GET /metrics/:metricId, /data-sources/:id, /data-sets/:id, and /communication-sources/:id return the Entity shape ({ success, entity }); their plural forms return the Search shape ({ success, total, entities }), paginated as described above. GET /metrics/:metricId/configuration returns the full configuration the slim metric entity omits (the same detail the create/update body accepts).
@ValidateIf rules) that would need to be fully typed and annotated first. This page is the source of truth for request and response shapes until that changes.