Skip to content

API reference

The authoritative reference is generated from the code, so it is always in sync with what is deployed:

  • Interactive Swagger UI: /api/docs/ (try requests with your own key)
  • Raw spec: /api/openapi.yaml (OpenAPI 3, for client generators and tooling)

Authentication

Every request carries an organization-scoped bearer key:

Authorization: Bearer lmt_...

Keys are created in Settings, API keys and shown once; only a hash is stored. Revoking a key takes effect immediately. Keys come in two scopes: full (everything below) and ingest-only (record runs and list automation slugs, nothing else; deploy these inside runners and scripts so a leaked key cannot read your ledger). A missing or invalid key gets 401; all error responses are {"error": "..."} with a message written for a human.

Rate limits

Requests are counted per organization, across all of its keys and every API surface (REST, MCP, OTLP traces, inbound webhooks), in fixed one-minute windows:

Plan Limit
Free 60 requests / minute
Team & MSP 300 requests / minute
Business 1,000 requests / minute
Enterprise custom (1,000 by default)

Over the limit, requests receive 429 with a Retry-After header giving the seconds until the window resets; back off that long and retry. A rate-limit 429 always carries Retry-After; the monthly event-cap 429 (Recording runs) never does: that one means the plan's ingestion ceiling was reached, not that you should slow down.

Historical loads belong on the backfill endpoint: a whole batch is one request processed as a background job, so imports do not burn the live window.

JSON or XML (content negotiation)

The API speaks JSON by default and XML on request, on the same endpoints. The response format is chosen by content negotiation:

  • Accept header (preferred): it sets the response format. Send Accept: application/xml for XML, or Accept: application/json for JSON. An explicit Accept always wins, even over the request's Content-Type.
  • No Accept header (or Accept: */*): the response mirrors the request's Content-Type. An XML request body (Content-Type: application/xml) gets an XML reply; otherwise the reply is JSON.
  • Request bodies: send Content-Type: application/xml and an XML body; otherwise the body is parsed as JSON.

In short: set Accept to pin the response format; omit it and the response follows your Content-Type. JSON is the default when neither indicates XML.

The XML convention is simple: an object is child elements keyed by field name, a list is repeated <item> elements, and responses are wrapped in a <response> root. So this JSON run:

{ "automation": "os-patching", "status": "success" }

is equivalent to this XML body (with Content-Type: application/xml):

<run><automation>os-patching</automation><status>success</status></run>

and Accept: application/xml returns the result, and errors, as XML (<response><error>...</error></response>). Webhook deliveries can be XML too; see Webhooks.

Endpoint map

Base URL: /api/v1/.

Endpoint What it does
POST /runs Record one run event (details)
GET /runs List runs, newest first; filters: automation, status, since, limit
POST /runs/backfill Bulk-import historical runs, CSV or JSONL (details)
POST /mcp The MCP server (stateless Streamable HTTP; see AI workloads)
POST /otel/v1/traces OTLP/HTTP gen_ai trace ingest (AI workloads)
GET /automations List automations and their slugs
POST /automations Create an automation (plan limits apply, exactly as in the UI)
GET /automations/{slug} Full detail: baseline, costs, status, freshness
PATCH /automations/{slug} Partial update; send only the fields you change
GET /automations/{slug}/cost-components List itemized costs
POST /automations/{slug}/cost-components Add an itemized cost (five kinds; see Concepts)
GET /automations/{slug}/baseline-steps List the manual process's steps
POST /automations/{slug}/baseline-steps Add a baseline step (per-role, per-occurrence; see Concepts)
GET /automations/{slug}/oversight-steps List the per-run oversight steps
POST /automations/{slug}/oversight-steps Add an oversight step (human time still spent per run)
GET /automations/{slug}/value-streams List value beyond labor (deflection, downtime, ...)
POST /automations/{slug}/value-streams Add a value stream (details)
GET /automations/{slug}/projection-versions The automation's frozen forecast history
GET /summary Portfolio value summary, the dashboard's numbers; optional workspace filter
GET /analytics Chart-ready series, the same data the analytics page draws
GET /workspaces List workspaces
POST /workspaces Create a workspace
GET /roles Job roles and loaded hourly rates
POST /roles Create a job role
GET /periods Closed (frozen) months
GET /report-links List shareable report links
POST /report-links Create a no-login, print-ready value report URL
POST /report-links/{id}/revoke (rpt_...) Kill a shared report URL immediately
GET /webhooks List webhook endpoints (details)
POST /webhooks Register a signed webhook endpoint
DELETE /webhooks/{id} (whk_...) Retire a webhook endpoint; deliveries stop
GET /msp/clients List managed client organizations (MSP plan; details)
POST /msp/clients Create a managed client organization (MSP plan)
POST /msp/clients/{id}/api-keys Mint an API key for a managed client (shown once; default ingest-only)

Analytics series

GET /api/v1/analytics?chart=... returns the same series the in-app analytics page draws. chart is one of monthly_value, cumulative_value, projected_vs_realized, reliability, volume_by_workspace, workspace_totals, run_heatmap, value_by_class. Filters: workspace (wsp_... id), automation (slug), from / to (ISO dates), months (1 to 36). Held runs never count, and months without runs come back as null in rate series rather than a misleading zero.

What the API deliberately does not do

Everything the UI can do is exposed over the API unless there is a specific reason not to. The recorded exclusions:

  • API key management. Credentials minting credentials weakens revocation.
  • Member management. Invites belong to email and role review in the UI.
  • Billing. Checkout is Stripe-hosted and cannot be meaningfully driven by an API key.
  • DELETE endpoints. Destructive actions are role-gated in the UI; org-scoped keys carry no role, so deletion stays a human decision. (Two exceptions that prove the rule: revoking a report link and deleting a webhook endpoint are exposed, because a kill switch for a shared URL or a compromised endpoint must be automatable.)

Warehouse extracts (Business and Enterprise)

For BI and warehouse loading, two bulk endpoints stream the system of record with a stable schema and forward cursor:

  • GET /api/v1/extracts/ledger: every ledger row, in stable order. Page with after=<the next_after cursor you received> until next_after is null (up to 5,000 rows per page); cursors are opaque tokens, so store and echo them verbatim. updated_since=<ISO 8601> returns only rows recomputed since that watermark, so nightly incremental syncs re-pull at least (and only around) what changed; upserts by id (an opaque led_... identifier) make re-pulls harmless.
  • GET /api/v1/extracts/adjustments: every closed-period adjustment, same cursor and watermark semantics, so the warehouse can rebuild as-restated = ledger + adjustments.

Amounts and quantities are decimal strings, never floats. Both endpoints require a full-access key.

Sync contract. Record your watermark BEFORE a sync begins and use it as the next sync's updated_since, so rows recomputed mid-sync are re-pulled next time. Deletions are not tombstoned: rows disappear when an automation is deleted or an open-month bucket no longer applies, so schedule a periodic full resync (weekly is plenty) to drop ghost rows; upserts by id make it cheap.

Versioning

The API is versioned in the path (/api/v1/). Additive changes (new fields, new endpoints) happen within a version; breaking changes get a new version prefix.