# Enroll a local or hosted runner Launch state: **Live**. Agents can register at /auth.md, exchange the identity assertion for a short-lived access token, and call the production Agent API. Contract status: **implemented**. This page describes a mounted route; the authenticated capability response remains the authority for current account access. ## Request `POST /api/agent/v1/runners` Operation ID: `createRunnerEnrollment` Send a short-lived access token obtained through [auth.md](https://neotask.ai/auth.md) for `https://neotask.ai/api/agent` in the `Authorization: Bearer` header. The token must include `neotask:runners:write`. Send an `Idempotency-Key` header with 8-200 characters. Reuse it only for an identical retry. For 24 hours, an identical retry returns the outcome recorded for the first request, with the same status and body, instead of running the operation again; a recorded failure is returned as that same failure. A readiness refusal returned before the operation started (for example `setup_required` with `runner_not_enrolled`, or `temporarily_disabled` with `run_execution_disabled`, `runner_offline` or `cloud_gateway_not_connected`) is not recorded, so the identical retry runs once the runner or gateway is ready. The JSON request body uses `AgentRunnerEnrollmentRequest` in OpenAPI. ## Response An enrolling runner and confirmation challenge (201), or an installation review that still requires human action (202). The JSON schema is `AgentRunnerEnrollmentResponse` in [OpenAPI](https://neotask.ai/openapi.yaml). - Tenant, principal, plan, and scopes come from the verified Agent API credential; authority identifiers in the body are rejected. - The runner secret is encrypted with authenticated context and is never returned or stored in plaintext. - A claimed registration first receives a pending review with a same-origin human-session URL. Retry the exact enrollment body and idempotency key after an owner or admin approves it. Network failures do not justify replacing that request or its secret. - A denied or expired review returns reviewStatus and the installation fingerprint without creating a runner. The CLI can request a fresh review with runner enroll --new-review --idempotency-key NEW_KEY. It first rechecks the original request, retains the installation and secret, and requires another human decision. - An unclaimed anonymous Agent Trial on the ordinary Free plan has no human reviewer, so it receives 201 directly for one local runner whose clientType and runtimeOwner are cli. The server admits it in the creation transaction under the same account, principal and erasure fences, and local possession is still proven by the confirmation challenge with runner HMAC. Desktop or cloud runners return human_action_required, and a second live runner returns device_limit_reached, until the human claim. An unconfirmed enrollment whose confirmation window has passed no longer counts: the next enrollment retires it, so enroll again with a new idempotency key. The claim keeps the same runner. - After enrollment, confirm possession at POST /api/agent-runner/v1/runners/{runnerId}/confirm with neotask:runners:execute and the returned challenge. The confirmation request also uses the runner HMAC proof. - The standalone CLI obtains the runner token by exchanging the same registration assertion for the exact runner scope. An account token or MCP token cannot substitute for this grant. See /docs/authentication.md for credential renewal. - Runner HMAC canonical bytes are NEOTASK-RUNNER-HMAC-V1, key generation, uppercase method, exact path and query, timestamp, nonce, and SHA-256(raw body), joined by newline. Timestamps are short-lived and each nonce is single-use in the shared replay store. ## Errors | HTTP | Codes | Meaning | Next action | |---:|---|---|---| | 400 | `invalid_request`, `invalid_idempotency_key` | The request body or Idempotency-Key is malformed. | Correct the documented input. Reuse an idempotency key only for the identical mutation. | | 409 | `idempotency_conflict`, `idempotency_in_progress` | The idempotency key belongs to another request or its first request is still running. | Do not change the request under the same key. Retry later or use a new key for new work. | | 409 | `human_action_required`, `runner_already_enrolled`, `runner_conflict` | Human approval or a concurrent installation state prevents enrollment. | Complete the human approval action and retry the same request. Resolve a conflict before changing installation identity or credentials. | | 403 | `device_limit_reached` | The account already has its allowed live runners. An unclaimed Agent Trial may keep one local runner. | Reuse or revoke the existing runner, or complete the human claim before adding another runner. | | 403 | `scope_denied`, `tenant_inactive`, `model_not_allowed` | The credential, account, or requested model cannot perform the operation. | Use an allowed model, request the documented scope, or ask the user to restore account access. | | 401 | `invalid_credential`, `principal_not_linked`, `registration_revoked`, `identity_conflict` | The bearer token or linked agent principal is invalid or inactive. | Register, refresh, or claim through the documented Auth.md flow, then retry with a new token. | | 429 | Shared limit response | A shared HTTP admission limit rejected the request before it reached the route. | Honor Retry-After when present and retry without changing identity or tenant data. | | 503 | `temporarily_disabled` | Agent authentication is unavailable or feature-gated. | Do not bypass authentication. Retry only after the returned guidance or launch state changes. | ## Related resources - [Agent API index](https://neotask.ai/docs/llms.txt) - [OpenAPI JSON](https://neotask.ai/openapi.json) - [Arazzo quickstart](https://neotask.ai/arazzo.yaml) - [Knowledge manifest](https://neotask.ai/agent-public-contracts/v1/knowledge-manifest.json)