---
title: "Register an agent target (atomic prove-then-persist)"
method: POST
path: "/api/v1/model"
tags: ["integration"]
---

# Register an agent target (atomic prove-then-persist)

`POST /api/v1/model`

## Request

- Base URL: `https://api.enokilabs.ai`
- URL: `https://api.enokilabs.ai/api/v1/model`
- Auth: none declared

Fires a live round-trip and persists the target ONLY if the probe succeeds; a failed probe persists nothing and returns a categorized error (unreachable / auth_failed / parse_error / no_reply / not_an_endpoint). Static auth (none/bearer/api_key/basic) or oauth_client_credentials, over http or websocket. The response never echoes the config, credential, or reply. The returned model_id is the handle for POST /api/v1/run. List first via GET /api/v1/model — creates are not deduplicated.

## Headers

- `Authorization` string, nullable: Bearer token with API key

## Request body

- ApiCreateTargetRequest: Request body for `POST /api/v1/model` (register an agent target).
  - `name` string, required: Display name for the target.
  - `environment` 'prod' | 'staging' | 'dev'
  - `auth_type` 'none' | 'bearer' | 'api_key' | 'basic' | 'oauth_client_credentials': bearer='Authorization: Bearer <cred>', api_key='x-api-key: <cred>', basic=HTTP Basic with the credential as 'user:password', oauth_client_credentials=an access token minted from the 'oauth' config below and re-minted before it expires. No enterprise auth.
  - `credential` string, nullable: Required for bearer/api_key/basic, and the OAuth client secret for oauth_client_credentials; omit for none. Stored encrypted, never returned.
  - `config` ApiAgentConfig, required: How to reach the agent: its endpoint, transport and where its reply is.
    - `endpoint_url` string, required
    - `request_field` string
    - `response_field` string
    - `session_field` string, nullable
    - `session_response_field` string, nullable
    - `session_response_header_field` string, nullable
    - `session_id_numeric` boolean
    - `transport` 'http' | 'websocket'
    - `http_stream_format` 'sync' | 'jsonl' | 'sse'
    - `chunk_combination` 'concatenate' | 'replace'
    - `chunk_skip_roles` string[]
    - `extra_http_headers` object, nullable
    - `request_body_template` object, nullable
    - `ws_auth_method` 'query_param' | 'header' | 'post_connect', nullable
    - `ws_auth_endpoint` unknown
    - `ws_url_endpoint` unknown
    - `ws_request_extra_fields` object, nullable
    - `ws_event_type_field` string
    - `ws_done_event_type` string, nullable
    - `ws_result_extract_field` string, nullable
    - `ws_stream_extract_field` string, nullable
    - `request_timeout` number
    - `reply_fetch` ReplyFetchConfig: How to read the reply of a target that answers after the POST. The POST returns only our message; the reply appears later in a message list served by a GET. List items are assumed oldest first.
      - `url` string, required: GET URL of the message list, on the endpoint's host. May contain __SESSION_ID__.
      - `messages_field` string, required: Dot-path to the list of messages.
      - `role_field` string, required: Dot-path, within a message, to its author role.
      - `reply_roles` string[], required: Role values that mark the bot's messages.
      - `text_field` string, required: Dot-path, within a message, to its text.
      - `anchor_id_field` string, nullable: Dot-path, in the POST response, to our message's id; messages after it are candidate replies. Unset: the anchor is the last message whose text is ours.
      - `item_id_field` string: Dot-path, within a message, to its id, matched to the anchor.
      - `poll_interval_seconds` number
      - `deadline_seconds` number
      - `settle_seconds` number: After the first reply, keep polling this long for more, then concatenate.
  - `oauth` ApiOAuthClientCredentialsConfig: How to mint an access token for the agent with OAuth client credentials.
    - `token_url` string, required: The OAuth 2.0 token endpoint. Absolute URL; https is required unless the platform allows private targets.
    - `client_id` string, required: OAuth client identifier.
    - `scope` string, nullable: Space-delimited scopes, when the provider expects them (Okta, Azure, ...).
    - `audience` string, nullable: Audience parameter, for providers that require it instead of scope (Auth0).
    - `extra_params` object: Additional form fields for the token request, for providers wanting something neither scope nor audience covers. Carries no secret.
    - `client_auth_method` 'post' | 'basic': How the client credentials are presented: 'post' sends client_id + client_secret in the form body; 'basic' sends them as HTTP Basic.

## Response `201`

Successful Response

- ApiCreateTargetResponse: Response for `POST /api/v1/model` (201). The inventory id and the connection verdict only. It never echoes the config (it may hold a header secret), the credential, or any part of the target's reply.
  - `model_id` string, required: Inventory model id — use directly as model_id in POST /api/v1/run.
  - `connected` true, required: Always true: the target is persisted only after a successful probe.

## Other responses

- `400`: Endpoint URL rejected (scheme or private/internal address)
- `422`: Two distinct shapes share this status. A **probe failure** returns `detail` as an **object** (`error_category` + a redacted message, shown below). A **request-validation** error returns the standard FastAPI `detail` as a **list** of per-field errors. Clients must branch on the type of `detail` (object vs list) rather than assume the validation shape.

---

[API](https://skmtc.dev/enokilabs/apis/enoki-external-api.md) · [All operations](https://skmtc.dev/enokilabs/apis/enoki-external-api/llms.txt) · [OpenAPI document](https://skmtc.dev/enokilabs/apis/enoki-external-api/revisions/9b13fb6ab124?raw)
