ApiKeys

Create an API key

Creates a new API key for the authenticated project. The plaintext key is returned exactly once, in this response — it is never stored, logged, or retrievable again through any other endpoint. Store it immediately.

expires_at, when supplied, must be in the future; a past value is rejected with 400 rather than silently minting an already-dead key.

A key minted by a key can never outlive its creator. If this request is authenticated with an API key that itself has an expiry, an explicit expires_at later than that key's own is rejected with 400 (expires_at may not exceed the creating key's own expiry). The problem body carries max_expires_at, the latest value this caller may ask for, so a client can retry correctly on its first attempt rather than guessing. This cap does not apply to session-authenticated (dashboard) requests, or when the authenticating key itself never expires.

An omitted expires_at follows the same rule, and it is in force: under a creating key that itself expires, omitting the field inherits that key's own expiry rather than meaning "never". A short-lived key therefore cannot mint a permanent one, whether it omits the field or asks for too much.

Nothing here changes for the other callers: under a creating key with no expiry of its own, and for session-authenticated (dashboard) requests, an omitted expires_at still means "never".

A client that wants to choose a lifetime rather than inherit one should send an explicit expires_at and retry at max_expires_at if refused, which is what the MCP create_api_key tool does.

A key may not be given a higher scope than its creator either. The same shape as the expiry cap above: an explicit scope that outranks the authenticating key's own is rejected with 400 (scope may not exceed the creating key's own scope), and the problem body carries max_scope. An omitted scope is always write, which no key can fail to grant except a read key — and a read key cannot reach this operation at all.

post/api/v1/api-keys

Request

  • Base URL: https://app.lastping.dev
  • URL: https://app.lastping.dev/api/v1/api-keys
  • Auth: API key in header X-Api-Key

Request body

namestring required

Human-readable label for the key, e.g. "github-actions".

expires_atstring date-time

Optional RFC 3339 expiry. Must be in the future — a past expires_at is rejected with 400, since minting an already-dead key is always a caller error. Omit for a key that never expires.

Exception — key-authenticated callers only: if the request is authenticated with an API key that itself has an expiry, the new key can never outlive it. Omitting expires_at then inherits the authenticating key's own expiry instead of "never", and supplying a later expires_at is rejected with 400. A session-authenticated caller (the dashboard) and a key with no expiry of its own impose no such cap.

scope'read' | 'write' | 'admin' | 'ingest'

What the new key may do. read is every GET; write is everything except API key management; admin is everything, key management included.

ingest is not a tier above or below those three: an ingest key can send pings, traces, metrics and logs to the ingest host and cannot call this API at all (every route answers 403 INSUFFICIENT_SCOPE, whatever the deployment's scope-enforcement setting). It is the key to put in a dotfile or an exporter's config. Minting one through this route needs an admin key (the route is admin-only) or a signed-in session. A write key mints a monitor-bound one through POST /api/v1/checks/{id}/ingest-keys, which can mint nothing else.

Omitted means write, on every surface. That is the right tier for a credential handed to a CI job or an agent: it can do the work and cannot mint itself a replacement that survives the original's revocation.

A key may not be given a HIGHER scope than the key that creates it. Asking for one is rejected with 400 (scope may not exceed the creating key's own scope), and the problem body carries max_scope — the highest tier this caller may ask for — so a client can retry correctly on its first attempt. As with the expires_at cap, this does not apply to session-authenticated (dashboard) requests: the session is the account owner.

check_idstring uuid

Binds an ingest key to one monitor. Only allowed with scope: "ingest"; any other scope with a check_id is rejected with 400, and a monitor that is not in this project is 404.

A bound key narrows, never widens: telemetry it sends is always attributed to this monitor, and a payload naming a different lastping.monitor_id, or a monitor URL that is not this monitor, is refused with 400. Use it for an exporter that can set a header but not a resource attribute. Deleting the monitor deletes the key; regenerating the key keeps the binding.

Example request

{
  "name": "github-actions",
  "expires_at": "2026-12-31T00:00:00Z",
  "scope": "write",
  "check_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
}

Response

API key created. key is the plaintext credential — shown exactly once.

idstring uuid required

Unique API key identifier.

namestring required

Human-readable label for the key.

prefixstring required

First 10 characters of the key — a non-secret identifier shown in place of the full value.

created_atstring date-time required

RFC 3339 UTC timestamp when the key was created.

expires_atstring date-time

RFC 3339 UTC expiry timestamp, or omitted for a key that never expires.

last_used_atstring date-time

RFC 3339 UTC timestamp of the most recent request this key authenticated. Omitted if the key has never been used since this field was added — never rendered as a zero/epoch time.

last_used_surface'mcp' | 'terraform' | 'api' | 'ingest'

Which client surface last used this key, omitted if unknown. ingest means the latest use was sending telemetry (traces, metrics or logs); telemetry use is recorded about once a minute per key, so last_used_at can trail the newest export by up to a minute. For mcp, terraform and api the value is best-effort and spoofable: derived from the caller-controlled User-Agent header on the authenticating request, not from any cryptographic or protocol-level signal. Useful for an honest "did my client ever successfully authenticate?" indicator — never use it for authorization or access control.

scope'read' | 'write' | 'admin' | 'ingest' required

What this key is permitted to do. Always present. Unlike last_used_surface above — which is a spoofable hint derived from a caller-supplied header — this is the value the server actually checks. ingest sends telemetry only and cannot call this API.

check_idstring uuid

The one monitor an ingest key is bound to. Omitted for an unbound key and for every key of another scope.

created_by_key_idstring uuid

The API key that minted this one, omitted for a key created from the dashboard (which has no parent key) or whose parent has since been deleted.

Revoking a key revokes every key below it in this chain. A key minted by a key minted by the key you delete goes with it, all at once. A key created from the dashboard is never below anything and is unaffected.

keystring required

The plaintext API key. Store it immediately — it cannot be retrieved again.

Example response

{
  "id": "e1d2c3b4-0000-0000-0000-000000000001",
  "name": "github-actions",
  "prefix": "lp_Wwut111",
  "created_at": "2026-07-01T12:00:00Z",
  "expires_at": "2026-12-31T00:00:00Z",
  "last_used_at": "2026-08-01T09:30:00Z",
  "last_used_surface": "mcp",
  "scope": "write",
  "check_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
  "created_by_key_id": "e1d2c3b4-0000-0000-0000-000000000002",
  "key": "lp_Wwut111f_jHpogymaQLg5L19-MuWcVVY"
}

Changes

No recorded changes to this operation across all 2 revisions of this API.