Skip to content

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.

NoteThere are two kinds of key. Every member can create personal keys, which act as that member and can never do more than their own role allows. Org admins can also create shared keys for integrations that act for the whole org. There is no plan gate. Most AI tools don't need a key at all: they connect by signing in.

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:

  1. 1Name: Required, up to 100 characters. This is just a label; it isn't part of the key itself.
  2. 2Expiration: Optional. Leave it blank for a key that doesn't expire.
  3. 3Capabilities: Pick at least one. These decide what the key is allowed to do (see the table below). RUN_METRIC is selected by default.
  4. 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.
ImportantThe secret is shown exactly once, right after creation: “This key will not be shown again. Store it securely.” The keys table only ever displays the prefix (e.g. 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

CapabilityAllows
LIST_METRICSList metrics
GET_METRICRead a single metric and its configuration
CREATE_METRICCreate a new metric, and test a not-yet-saved configuration
UPDATE_METRICUpdate an existing metric, and test a not-yet-saved configuration
DELETE_METRICPermanently delete a metric and its history
DUPLICATE_METRICCopy a metric as a new draft
RUN_METRICTrigger a metric run, and check a run's status
READ_METRIC_RESULTSSearch past metric runs and segment results
INTERACT_METRICAcknowledge, resolve, or mark a segment result as hit
SEND_TEST_NOTIFICATIONSend a real test message to the places a metric notifies

Data sources

CapabilityAllows
LIST_DATA_SOURCESList data sources
GET_DATA_SOURCERead a single data source
QUERY_DATA_SOURCERun read-only SQL on a data source and get back up to 100 rows
SETUP_DATA_SOURCEStart connecting a data source or replacing its credentials, through a short-lived link you open in Lighthouse

Data sets

CapabilityAllows
LIST_DATA_SETSList data sets
GET_DATA_SETRead a single data set, including column metadata
CREATE_DATA_SETCreate data sets from tables of a data source
UPDATE_DATA_SETChange a data set's name, description, status, default date column, and usable columns

Communication sources and integrations

CapabilityAllows
LIST_COMMUNICATION_SOURCESList communication sources
GET_COMMUNICATION_SOURCERead a single communication source, including its channels and users
SETUP_COMMUNICATION_SOURCEStart connecting Slack or email, through a short-lived link you open in Lighthouse
SETUP_INTEGRATION_ACTION_SOURCEStart connecting an integration action source or replacing its credentials, through a short-lived link

Administration (org admins only)

CapabilityAllows
LIST_BUSINESS_GROUPSList business groups and their members, and find users by name or email
CREATE_BUSINESS_GROUPCreate a business group with its members
UPDATE_BUSINESS_GROUPRename a business group, and add or remove its members
GET_ORGANIZATION_SETTINGSRead the organization's time zone, AI model, and notification settings
UPDATE_ORGANIZATION_SETTINGSChange the organization's time zone, error notifications, and automatic metric suggestions
MANAGE_MEMBERSList, invite, change the role of, and remove members
NoteAllow-lists apply on top of capabilities, per entity type: a key with 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:

ColumnWhat it shows
Created ByThe 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 AtCreation timestamp
ExpiresThe expiration date, or blank if it never expires
Last UsedThe most recent successful call
StatusActive, 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.

NoteThere's no rotate action, only Revoke. Revoking asks you to confirm (“Existing integrations that use this key will stop working”) and takes effect immediately. To rotate, create a new key and revoke the old one once traffic has moved over.

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.

every request
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" } }
NotePublic list/search responses never include a 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
NoteThere's no page-count or 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
ImportantPOST /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"
request: RAW_SQL, minimal draft
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"] }
  ]
}
request: CUSTOM, minimal draft
{
  "type": "CUSTOM",
  "name": "Row count",
  "status": "DRAFT",
  "severity": "S2",
  "dataSourceId": "<data-source-uuid>",
  "dataSetId": "<data-set-uuid>"
}
201 response
{
  "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
  }
}
NoteThe response entity is the same slim shape returned everywhere else; it does not include the 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.

request
PATCH https://api.lighthouseai.co/public-api/v1/metrics/12
x-api-key: lh_...

{ "name": "Public updated name" }
200 response
{
  "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": {...} }.

request: invalid config
{ "type": "CUSTOM", "name": "Missing severity", "status": "PAUSED" }
200 response: invalid
{
  "success": true,
  "valid": false,
  "errors": ["... severity is required ..."]
}
200 response: valid
{
  "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)
request
{
  "searchText": "sql threshold",
  "peekExampleIds": ["raw-sql-threshold"]
}
200 response
{
  "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", "...": "..." }
    }
  ]
}
NoteOnly items listed in 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.

request
POST https://api.lighthouseai.co/public-api/v1/metrics/{metricId}/run
x-api-key: lh_...
200 response
{
  "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"
request
{ "metricIds": ["<uuid>"] }
200 response
{
  "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
request
{ "metricIds": ["12"] }
200 response
{
  "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:

ActionUndo
ACKNOWLEDGEUNDO_ACKNOWLEDGE
MARK_AS_RESOLVEDUNDO_MARK_AS_RESOLVED
MARK_HITUNDO_MARK_HIT
request
POST https://api.lighthouseai.co/public-api/v1/segment-results/{segmentRunId}/actions
x-api-key: lh_...

{ "action": "ACKNOWLEDGE" }
200 response
{
  "success": true,
  "entity": {
    "action": "ACKNOWLEDGE",
    "segmentResult": { "segmentRunId": "...", "isAcknowledged": true }
  }
}
NoteThese are the same six values as the Slack alert buttons in Alert buttons, just spelled as constants instead of button labels. An unrecognized 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).

NoteEntities can gain fields over time. Read the fields you need and ignore the rest.
NoteThere's no OpenAPI spec or Postman collection, and adding one isn't a quick follow-up: the create/update/test bodies are untyped JSON validated against large, conditional DTOs (300+ lines with branching @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.