# Register a tool manifest

POST

/v1/tools

```sh
curl --request POST \
  --url https://example.com/v1/tools \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '{ "id": "example", "projectId": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "description": "example", "version": "example", "input": {}, "output": {}, "needs": [ { "name": "example", "optional": true } ], "effects": [ { "kind": "reads", "resource": "example", "notes": "example" } ], "transport": "auto", "mcpEndpoint": "example", "metadata": {}, "mutating": true, "needsApprovalWhenUnattended": true, "sandbox": "none", "limits": { "memMB": 1, "cpuMs": 1 }, "network": { "kind": "none" }, "needsSpec": { "env": { "additionalProperty": {} }, "secrets": { "additionalProperty": {} }, "config": { "additionalProperty": {} }, "capabilities": [ "example" ], "bindings": [ "example" ] }, "codeArtifactRef": { "kind": "oci", "imageRef": "example", "modulePath": "example", "artifactVersion": "example" }, "spec": { "kind": "http", "method": "GET", "urlTemplate": "example", "headers": [ { "name": "example", "value": "example" } ], "authorization": { "kind": "bearer", "secretRef": { "envName": "example", "name": "example" } }, "requestBody": { "kind": "json-input" }, "timeoutMs": 1, "parseJson": true, "successStatus": { "min": 1, "max": 1 } } }'
```

Body is a `ToolManifest` (Tool minus its runtime handler). Server validates via `@kindgi/tools.validateToolManifest`. Metadata only: the handler is not uploaded through this route and must already be available to the runtime. A tool never gets a model provider’s key: one that declares (`needsSpec.secrets`) or sends (`spec.authorization.secretRef`) a secret a provider registration of the tenant names, in any env, is refused. Where each secret it names is read is recorded on the version (`secretScopes`): the first of its own scope, its project’s org and the tenant that has the secret and that the caller administers, else the broadest they administer; the runtime reads it there only.

## Authorizations

- **[bearerAuth](https://docs.kindgi.com/v0.1.10/reference/api.md#bearerauth)**

## Parameters

### Header Parameters

**Idempotency-Key**

string

\>= 1 characters

Caller-supplied idempotency key, scoped to the caller: the same key from someone else in the tenant is their own request (from 0.1.5). Retries with the same key return the original response byte-identical (per `docs/API-ROUTE-CONVENTIONS.md` §3.1). A retry sent while the first request still runs is answered `409 idempotency-key-in-flight` with `Retry-After`, on a runtime whose store holds keys (from 0.1.5); retry after it, and you get the first request’s answer. An answer that carries a secret (a new API key or public run token, a session token, a generated signing secret) isn’t kept: a retry gets `409 idempotency-key-replay-withheld`, with the first request’s status and when it succeeded (from 0.1.5).

## Request Bodyrequired

application/json

ToolManifest — Tool minus its runtime `handler`. Validated server-side via `@kindgi/tools.validateToolManifest`. Metadata-only registration: the handler is not uploaded and must already be available to the runtime.

object

**id**

required

ToolId — dotted namespace (e.g. `acme.verify-citation`).

string

\>= 1 characters

**projectId**

required

Project this belongs to (its content scope). Required: missing, or not a project in the caller’s tenant → `400 bad-input`.

string format: uuid

**description**

required

string

\>= 1 characters

**version**

string

/^\d+\\.\d+\\.\d+$/

**input**

required

JSON Schema (Draft 2020-12) for the tool input.

object

**_key_**

additional properties

any

**output**

required

JSON Schema (Draft 2020-12) for the tool output.

object

**_key_**

additional properties

any

**needs**

Array\<object>

object

**name**

required

string

\>= 1 characters

**optional**

boolean

**effects**

Array\<object>

object

**kind**

required

string

Allowed values: reads writes deletes network spawns-run emits-event external-side-effect sensitive-data-egress

**resource**

string

**notes**

string

**transport**

string

Allowed values: auto native mcp

**mcpEndpoint**

string

**metadata**

object

**_key_**

additional properties

any

**mutating**

Semantic marker: `true` when this tool causes observable side effects (writes state, calls external APIs with mutations, sends messages). Read-only tools set `false`. Absent defaults to `true`. Consumed by the HITL default classifier — a read-only tool passes straight through, a mutating tool asks on first use.

boolean

**needsApprovalWhenUnattended**

`true`: in a run no person started (one whose `startedBy` isn’t `person`), every call of this tool waits for a person’s yes, whatever the agent’s approval rules say, and a flow’s tool step that calls it is refused. Absent means `false`.

boolean

**sandbox**

Isolation posture the runtime enforces around the handler. Optional additive field.

string

Allowed values: none context-isolated strict

**limits**

Sandbox-enforced resource caps at dispatch. Optional additive field.

object

**memMB**

required

integer

\>= 1

**cpuMs**

required

integer

\>= 1

**network**

One of:

- **object**

  Network egress policy. Optional additive field. Discriminated on `kind`.

  object

  **kind**

  required

  string

  Allowed values: none unrestricted

- **object**

  Network egress policy. Optional additive field. Discriminated on `kind`.

  object

  **kind**

  required

  string

  Allowed value: allowlist

  **hosts**

  required

  Array\<string>

**needsSpec**

Typed discriminated needs (env / secrets / config / capabilities / bindings). Optional additive field.

object

**env**

object

**_key_**

additional properties

object

**secrets**

object

**_key_**

additional properties

object

**config**

object

**_key_**

additional properties

object

**capabilities**

Array\<string>

**bindings**

Array\<string>

**codeArtifactRef**

One of:

- **object**

  object

  **kind**

  required

  Allowed value: oci

  **imageRef**

  required

  string

  \>= 1 characters

  **modulePath**

  required

  string

  \>= 1 characters

  **artifactVersion**

  required

  string

  \>= 1 characters

- **object**

  object

  **kind**

  required

  Allowed value: filesystem

  **modulePath**

  required

  string

  \>= 1 characters

**spec**

One of:

- **object**

  Declarative HTTP-invocation spec. Attached to `ToolManifest.spec` under the discriminant `kind: 'http'`. The runtime `Tool.handler` is synthesized by the ‘http’ spec synthesizer to perform URL-template substitution, secret-ref resolution via `ToolContext.resolveSecret`, and the outbound fetch. All fields serialize cleanly to JSON.

  object

  **kind**

  required

  Allowed value: http

  **method**

  required

  string

  Allowed values: GET POST PUT PATCH DELETE

  **urlTemplate**

  required

  URL template with `{param}` placeholders substituted from the tool’s input at invoke time.

  string

  \>= 1 characters

  **headers**

  Array\<object>

  object

  **name**

  required

  string

  \>= 1 characters

  **value**

  required

  string

  **authorization**

  One of:

  - **object**

    object

    **kind**

    required

    Allowed value: bearer

    **secretRef**

    required

    object

    **envName**

    required

    string

    \>= 1 characters

    **name**

    required

    string

    \>= 1 characters

  - **object**

    object

    **kind**

    required

    Allowed value: header

    **headerName**

    required

    string

    \>= 1 characters

    **secretRef**

    required

    object

    **envName**

    required

    string

    \>= 1 characters

    **name**

    required

    string

    \>= 1 characters

  **requestBody**

  One of:

  - **object**

    object

    **kind**

    required

    Allowed value: json-input

  - **object**

    object

    **kind**

    required

    Allowed value: input-passthrough

  - **object**

    object

    **kind**

    required

    Allowed value: text

    **template**

    required

    string

  **timeoutMs**

  Wall-clock timeout in ms. Default 30000.

  integer

  \>= 1

  **parseJson**

  When true (default), the response body is parsed as JSON before returning to the invoker.

  boolean

  **successStatus**

  object

  **min**

  required

  integer

  \>= 100 <= 599

  **max**

  required

  integer

  \>= 100 <= 599

## Responses

### 201

Tool registered.

application/json

object

**toolId**

required

string

##### Examplegenerated

```json
{
  "toolId": "example"
}
```

### 400

Validation failed (see `details.issues`); or `projectId` isn’t a project id (a UUID). Or `provider-key-refused`: the tool names a model provider’s key (`details.secret`, `details.providerId`); store the key it needs under its own name (the same value is fine) and name that. Or `validation-failed` for a `metadata` key under `kindgi.`: those are the runtime’s own (`details.issues`, at `/metadata/<key>`).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 401

Missing / malformed / expired / revoked bearer token.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 404

The body’s `projectId` names no project of this tenant (`project-not-found`).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 409

`tool-already-registered`: that (id, version) is taken. Or `tool-project-mismatch`: the tool’s versions live in another project (a tool belongs to the project its first version was published into and never moves, though in a tenant with environment orders a version may be made in another project of that project’s order; the message doesn’t name the project). Or `tool-kind-mismatch`: the tool’s active versions are of another kind, an MCP endpoint’s tool (`details.endpointId`) where this version is published here, or the other way round; a tool’s active versions are all one kind, and nothing was written (`details.toolId`, `details.version`). Or `environment-takes-promotions`: the project is a later environment of an environment order, which takes builds only by promotion from the one before it; publish in the order’s first environment instead (`details.firstProjectId`, when you can read that project; `details.position`, the project’s place from 0). Nothing is written. Or `registry-read-only`: this registry takes no writes (under `kindgi dev`, the pack’s files are the source); the message says what to do instead.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 413

The request body is over the API’s limit (`request-too-large`; `details.maxBytes` says how much).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 429

`rate-limit-exceeded` with `details.limit: "schema-compile-time"`, on a server with a compile budget (Kindgi Cloud): this tenant’s schemas spent `details.budgetMs` compiling in the last 60 seconds. Nothing was stored; `Retry-After` says when to send it again.

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

### 500

Server error (unmapped domain code or framework crash).

application/json

object

**error**

required

object

**code**

required

Stable machine-readable discriminant. Values match domain error codes (see `docs/API-ROUTE-CONVENTIONS.md` §4.3).

string

**message**

required

string

**details**

Optional, kind-specific.

object

**_key_**

additional properties

any

**requestId**

required

Server-assigned request id; also echoed via `X-Request-Id` header.

string

##### Examplegenerated

```json
{
  "error": {
    "code": "example",
    "message": "example",
    "details": {},
    "requestId": "example"
  }
}
```

## See also

- [`GET /v1/tools`: List tools](https://docs.kindgi.com/v0.1.10/reference/api/operations/toolslist.md)
- [Give a tool a secret](https://docs.kindgi.com/v0.1.10/guides/tools/give-a-tool-a-secret.md)
- [Kindgi 0.1.8](https://docs.kindgi.com/v0.1.10/releases/0.1.8.md)
- [Error code `environment-takes-promotions`](https://docs.kindgi.com/v0.1.10/reference/errors.md#environment-takes-promotions)
- [Error code `project-not-found`](https://docs.kindgi.com/v0.1.10/reference/errors.md#project-not-found)
- [Error code `provider-key-refused`](https://docs.kindgi.com/v0.1.10/reference/errors.md#provider-key-refused)
- [Error code `rate-limit-exceeded`](https://docs.kindgi.com/v0.1.10/reference/errors.md#rate-limit-exceeded)
- [Error code `registry-read-only`](https://docs.kindgi.com/v0.1.10/reference/errors.md#registry-read-only)
- [Error code `request-too-large`](https://docs.kindgi.com/v0.1.10/reference/errors.md#request-too-large)
- [Error code `tool-already-registered`](https://docs.kindgi.com/v0.1.10/reference/errors.md#tool-already-registered)
- [Error code `tool-kind-mismatch`](https://docs.kindgi.com/v0.1.10/reference/errors.md#tool-kind-mismatch)
- [Error code `tool-project-mismatch`](https://docs.kindgi.com/v0.1.10/reference/errors.md#tool-project-mismatch)
- [Error code `validation-failed`](https://docs.kindgi.com/v0.1.10/reference/errors.md#validation-failed)
- [Every error code](https://docs.kindgi.com/v0.1.10/reference/errors.md)
