---
title: "Create an agent"
method: POST
path: "/api/v1/agents"
tags: ["Agents"]
---

# Create an agent

`POST /api/v1/agents`

## Request

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

Creates a new agent for the authenticated project and returns 201.
There is no `slug` field in the request: the slug is always derived
from `name`, the same way for every creation path (this endpoint,
the dashboard onboarding wizard, and the MCP `register_agent` tool),
so the three surfaces can never produce a row shaped differently.

A brand-new agent owns no monitors: `status` is `idle`,
`monitor_count` is `0`, and `last_seen` is null.

## Request body

- AgentCreate: Body for creating an agent (POST /api/v1/agents). There is no `slug` field: the slug is always derived from `name` (lowercased, any run of characters outside `[a-z0-9]` collapsed to a single hyphen, leading/trailing hyphens trimmed), the same way for every creation path — this endpoint, the dashboard onboarding wizard, and the MCP `register_agent` tool — so none of them can produce a row shaped differently from another.
  - `name` string, required: Human-readable agent name. The slug is derived from this value. Surrounding whitespace is trimmed; at most 200 characters (not bytes) and no control or invisible characters (400 `INVALID_NAME`). Bytes that are not valid UTF-8 are replaced with U+FFFD when the request is decoded, and accepted.
  - `description` string: Optional free-text description.

## Response `201`

Agent created.

- Agent: An agent resource returned by the API. `status` is never stored — it is rolled up live from the monitors this agent owns, the same pure function the fleet dashboard and the MCP tools use, so the three surfaces cannot drift apart on what "healthy" means.
  - `id` string, uuid, required: Unique agent identifier.
  - `slug` string, required: Stable project-scoped identifier, derived from `name` at creation and immutable thereafter.
  - `name` string, required: Human-readable name.
  - `description` string, required: Free-text description (empty string if not set).
  - `status` 'down' | 'blocked' | 'late' | 'running' | 'up' | 'pending' | 'idle', required: Rolled up live from the monitors this agent owns, worst status first: down > blocked > late > running > up > pending > idle. Paused and in-maintenance monitors never contribute: muting a monitor must neither hide a broken agent nor drag a healthy one down. - `down` — at least one owned, active monitor is down. - `blocked` — no active monitor is down, but at least one owned monitor has a run that is blocked on a human right now. This is an alert-class state, not merely informational: it is excluded from the informational alert rate limit precisely so it cannot be starved by high-volume traffic. - `late` — no active monitor is down or blocked, but at least one is late. - `running` — no active monitor is down, blocked or late, but at least one owned monitor has a run currently in flight. - `up` — no active monitor is down, blocked, late or running, and at least one is up. - `pending` — the agent owns at least one monitor, but none of its active monitors has ever reported (only ever seen `new`). This usually means the wiring is wrong — the agent registered but never pinged. - `idle` — the agent owns no monitors, or every monitor it owns is paused or in maintenance. There is nothing to judge yet.
  - `monitor_count` integer, required: Number of monitors this agent owns. `0` for an agent with no monitors.
  - `last_seen` string, date-time, nullable: RFC 3339 timestamp: the latest ping or traced span across ALL monitors this agent owns, including paused and in-maintenance ones — this answers "when did I last hear from this agent at all", not "is it healthy". A span counts from the end time it reports, recorded at most once a minute per monitor. Null or omitted for an agent that has never received a ping or a span on any of its monitors.
  - `spend_24h` AgentSpend, required: 24-hour cost/token/run rollup for one agent, over the traced runs of the checks it owns. See `Agent.spend_24h` and `AgentFleet.spend_24h` for when the containing field itself is `null` instead of this object. The window is keyed on `last_span_at`, not `first_span_at`: a run counts in full when its last span falls inside the trailing 24 hours, so a long run's whole accumulated cost/tokens land on the day it ends, even though it may have started well before the window.
    - `cost_usd` string, nullable, required: Summed `cost_usd` across every span in the window, as decimal text (e.g. `"0.006600"`), never a JSON number. Null when no span in the window reported a GenAI cost, even though `runs` is greater than 0.
    - `cost_source` string, required: What `cost_usd` is made of. A plain string, not an enum, so a new value never breaks a client: `client` when every priced call's cost was reported by the client (the exporter's own figure, or Claude Code's per-call cost), `estimated` when LastPing priced every call at API list prices, `mixed` when the sum holds both, empty when `cost_usd` is null.
    - `tokens_in` integer, required: Summed input tokens across every span in the window.
    - `tokens_out` integer, required: Summed output tokens across every span in the window.
    - `runs` integer, required: Number of distinct runs contributing to this rollup.
  - `runs_7d` object[], required: Runs per UTC day over the last seven days, oldest first, always exactly seven entries including days with none. A run lands on the day its first ping arrived, so a run that spans midnight is counted once. Public because an agent asking how busy it has been is a question worth answering over the API as well as in the console.
    - `day` string, date, required
    - `runs` integer, required
  - `usage_24h` UsageDay, required: One model's token and cost usage on one UTC day, from the daily dependency aggregate or, for one agent's usage, from an OTLP metrics export (`origin`). Under a `project` filter a row is instead one UTC day of that project's traced runs, summed from each run's own totals (every span's tokens, the traces' cost) with `model` and `provider` empty, because a run's totals are not split by model. `tokens_in` INCLUDES `tokens_cache_read` (the OpenTelemetry rule): never add the two.
    - `day` string, date, required
    - `model` string, required: The model id. Empty either when the span named only a provider, or, under a `project` filter, on every row, because each row is then a whole day of the project's runs across all models.
    - `provider` string, required: The provider (`gen_ai.system`). Empty either when the span named only a model, or, under a `project` filter, on every row (one row per day across all models).
    - `tokens_in` integer, required
    - `tokens_out` integer, required
    - `tokens_cache_read` integer, required
    - `tokens_cache_write` integer, required
    - `cost_usd` string, nullable, required: US dollars as decimal text; null when no call reported or could be priced.
    - `cost_source` string, required: What `cost_usd` is made of. A plain string, not an enum, so a new value never breaks a client. `client` when `cost_usd` is the tool's own figure: a metric-derived row's (Claude Code's cost metric, or Cursor's best-effort estimate); for one agent's usage, a trace-derived row whose day and model Claude Code's cost metric also covers (its figure replaces the traces' figure, which moves to `estimated_cost_usd`); or a trace-derived row whose every priced call carried a client-reported cost. `estimated` when LastPing priced every call at API list prices, `mixed` when a trace-derived row holds both kinds. The client figure and the traces' figure may describe different sets of calls (the metric covers every Claude Code call of the day, the traces only the traced ones). Empty when `cost_usd` is null.
    - `estimated_cost_usd` string, nullable, required: The traces' figure for the day and model when Claude Code's cost metric also covers them and its figure is `cost_usd`; null otherwise. The traces' figure is LastPing's estimate at API list prices, except for calls whose own client-reported cost was attached to them.
    - `origin` 'traces' | 'metrics', required: Where the row's numbers come from. `traces`: the span-derived daily aggregate. `metrics`: an OTLP metrics export, shown only for a day and model no span covered. The two are never added: a tool such as Claude Code sends spans and metrics for the same calls, so for one day, provider and model the trace-derived row wins. The project-wide usage report is trace-derived only.
  - `top_dependencies` Dependency[], required: The agent's five heaviest outgoing dependencies over the same window, most calls first; always an array. The full list is `GET /api/v1/agents/{id}/dependencies`.
    - `kind` 'model' | 'tool' | 'http' | 'database' | 'queue' | 'rpc' | 'agent', required
    - `name` string, required: The display name: `postgresql app`, `api.github.com`, `anthropic claude-sonnet-4-5`. Never a URL and never a path.
    - `direction` 'out' | 'in', required: `out` for something the agent calls, `in` for a caller of the agent.
    - `calls` integer, required
    - `errors` integer, required: Calls whose span reported an error status.
    - `error_rate` number, double, required: `errors / calls`, from 0 to 1; 0 when there were no calls.
    - `p50_ms` integer, required
    - `p95_ms` integer, required
    - `p95_is_floor` boolean, required: True when the 95th percentile call was slower than the last bucket (60 s), so `p95_ms` is a floor.
    - `tokens_in` integer, nullable, required: Input tokens, including cache reads (the OpenTelemetry rule). Null for every kind but `model`.
    - `tokens_out` integer, nullable, required: Output tokens. Null for every kind but `model`.
    - `cost_usd` string, nullable, required: Cost in US dollars as decimal text. Null for every kind but `model`, and for a model no call reported or could be priced.
    - `cost_source` string, required: What `cost_usd` is made of. A plain string, not an enum, so a new value never breaks a client: `client` when every priced call's cost was reported by the client (the exporter's own figure, or Claude Code's per-call cost), `estimated` when LastPing priced every call at API list prices, `mixed` when the sum holds both, empty when `cost_usd` is null.
    - `series` DependencySeriesPoint[], required: One point per UTC day of the range, oldest first, including days with no calls, for a row sparkline.
      - `day` string, date, required
      - `calls` integer, required
      - `errors` integer, required
  - `created_at` string, date-time, required: RFC 3339 UTC timestamp when the agent was created.

## Other responses

- `400`: Invalid request body, or a name that cannot be slugified.
- `401`: Missing or invalid API key.
- `409`: An agent with the derived slug already exists in this project.

---

[API](https://skmtc.dev/lastping/apis/lastping-management-api.md) · [All operations](https://skmtc.dev/lastping/apis/lastping-management-api/llms.txt) · [OpenAPI document](https://skmtc.dev/lastping/apis/lastping-management-api/revisions/bbc6eda91c46?raw)
