# Layer tools

A **Layer tool** is one job from the Layer app’s tools menu, such as upscaling an image, making a seamless texture or reframing a video. Each has a key, such as `image.upscale`, and runs on any base model in its pool. A tool asks only for the fields its job needs and fixes the rest: Create texture always draws a tileable image, so its form has no `tileability` field. The asset a tool run makes reopens in that tool in the Layer app.

Reach for a tool when the job is one of these. To set every parameter a model takes yourself, run the [base model](/docs/models) instead. Tools are on `/v2` only.

A tool run is three calls: list the tools, read the schema of the one you want on a model, then run it with that schema’s fields.

## Listing tools

`GET /v2/workspaces/{workspace_id}/inference-tools` lists the tools you can run in the workspace, in the order the Layer app’s tools menu shows them. It’s [paginated](/docs/pagination) and takes these filters:

| Filter                   | Purpose                                                                                |
| ------------------------ | -------------------------------------------------------------------------------------- |
| modality                 | Only the tools that make this modality, such as image or video.                        |
| group                    | generate for the tools that make something new, edit for those that change an asset.   |
| project\_id, session\_id | Answer for the project the work lands in. See [The default model](#the-default-model). |

```bash
curl "https://api.app.layer.ai/api/v2/workspaces/$WORKSPACE_ID/inference-tools?group=edit&modality=image" \
  -H "Authorization: Bearer $LAYER_TOKEN"
```

```jsonc
{
  "inference_tools": [
    {
      "key": "image.upscale",
      "name": "Upscale image",
      "description": "Add resolution and detail.",
      "group": "edit",
      "modality": "image",
      "source": { "modality": "image", "takes_mask": false },
      "batches_outputs": true,
      "base_model_ids": ["clarity-upscaler", "esrgan-upscaler", "topaz-wonder-3.5-upscale-image", "…"],
      "default_base_model_id": "topaz-wonder-3.5-upscale-image",
      "concept": "upscale",
      "prompt_placeholder": "Describe the detail to add",
      "primary": true,
      "keywords": ["enhance", "super resolution", "hd", "4k"]
    }
    // …
  ],
  "pagination": { "next_cursor": "…", "has_more_results": true, "total_count": 23 }
}
```

| Field                                                              | Meaning                                                                                                                                                                                                              |
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| key                                                                | The tool’s key, used in the tool’s paths and as inference\_tool\_key on a model run.                                                                                                                                 |
| group, modality                                                    | Whether the tool makes something new or changes an asset, and the type of asset it makes.                                                                                                                            |
| source                                                             | What an edit works on (modality) and whether it can keep its edit to an area a mask marks (takes\_mask). null for a tool that makes something new.                                                                   |
| base\_model\_ids                                                   | The models you can run the tool on, in catalog order.                                                                                                                                                                |
| default\_base\_model\_id                                           | The model a run takes when it names none.                                                                                                                                                                            |
| batches\_outputs                                                   | Whether a run can make more than one output (batch\_size above 1).                                                                                                                                                   |
| name, description, concept, prompt\_placeholder, primary, keywords | What the Layer app shows for the tool: its name and blurb, its icon by concept name, the hint in its empty prompt, whether it’s an edit offered first on its asset type, and the extra terms a search matches it by. |

A tool none of whose models you can run is left out of the list.

## The default model

A run that names no `base_model_id` takes the tool’s `default_base_model_id`, picked in this order:

1. The tool’s own default, where it has one.
2. Else the first model you pinned in the Layer app that the tool runs on. Pins are per member, so two members can get different defaults for the same tool.
3. Else the model Layer recommends for the tool’s job.

Only models you can run count. `base_model_ids` and the default follow the [AI policy](/docs/ai-policies) of the workspace, or of the project the work lands in: a project can allow fewer models than the workspace, or set other defaults. Pass the `session_id` you’ll run in, or the `project_id` of its project, and the list and the schema answer for that project. A run given that `session_id` takes the default the same way, and its response’s `base_model_id` names the model it took. A run given only a `session_name` picks its model under the workspace’s policy, since the session it names is found only once the run is accepted: when a project’s default matters, run with its `session_id`.

## Reading a tool’s schema

`GET /v2/workspaces/{workspace_id}/inference-tools/{inference_tool_key}/inference-schema` returns the JSON Schema of what a run of the tool takes on one model: the one in `base_model_id` (by ID, slug, name or alias), or the default without it. It takes `project_id` and `session_id` as the list does.

```bash
curl "https://api.app.layer.ai/api/v2/workspaces/$WORKSPACE_ID/inference-tools/image.upscale/inference-schema?base_model_id=topaz-wonder-3.5-upscale-image" \
  -H "Authorization: Bearer $LAYER_TOKEN"
```

```jsonc
{
  "title": "Upscale image with Topaz Wonder 3.5",
  "type": "object",
  "additionalProperties": false,
  "properties": {
    "upscale_factor": {
      "type": "string",
      "default": "2",
      "title": "Upscale",
      "description": "How many times larger the output is than the source.",
      "x-layer": {
        "label": "Upscale",
        "order": 0,
        "options": [
          { "label": "2×", "value": "2" },
          { "label": "4×", "value": "4" }
        ]
        // …
      }
    },
    "creativity": { /* … */ },
    "face_enhancement": { /* … */ }
    // …
  },
  "x-inference-tool-key": "image.upscale",
  "x-slug": "topaz-wonder-3.5-upscale-image",
  "x-source": { "modality": "image", "required": true, "takes_mask": false }
}
```

* **`properties`** are the fields the tool shows on that model, ordered by `x-layer.order`. The fields the tool hides or fixes are left out, and `additionalProperties: false` refuses them. A field with `x-layer.advanced` is one the Layer app keeps under advanced settings.
* **`x-source`** describes an edit’s source, which is not a field: `modality` is the type of asset it works on, `required` whether this model needs one, and `takes_mask` whether a run on this model takes a mask. A tool that makes something new has no `x-source`.
* **A file field** takes each file as `{ "file_id": "…", "weight": 1.0 }`: the ID of a file in the workspace or of a public file, never a URL. Upload files first; see [Files & uploads](/docs/files).
* **The fields differ from model to model**, so read the schema again when the model changes.

An unknown key, or a tool none of whose models you can run, is a `404` with `INFERENCE_TOOL_NOT_FOUND`. A model you can’t run is a `404` with `BASE_MODEL_NOT_FOUND`, and one you can run that the tool doesn’t is a `422` with `INFERENCE_TOOL_MODEL_NOT_IN_POOL`.

## Running a tool

`POST /v2/workspaces/{workspace_id}/inference-tools/{inference_tool_key}/inferences` starts a run. To upscale an image four times:

```bash
# One key per run: reuse it for every retry of this run.
IDEMPOTENCY_KEY="$(uuidgen)"


curl -X POST "https://api.app.layer.ai/api/v2/workspaces/$WORKSPACE_ID/inference-tools/image.upscale/inferences" \
  -H "Authorization: Bearer $LAYER_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -d '{
    "base_model_id": "topaz-wonder-3.5-upscale-image",
    "source_file_id": "FILE_ID",
    "input": { "upscale_factor": "4" },
    "session_name": "Upscales"
  }'
# → { "inference_id": "…", "status": "in_progress", "base_model_id": "topaz-wonder-3.5-upscale-image",
#     "poll_interval_seconds": 10, "session_id": "…", "expected_events": [], … }
```

The run is [asynchronous](/docs/async-jobs): poll `GET /v2/workspaces/{workspace_id}/inferences/{inference_id}` until it ends, or pass a [webhook](#webhooks). The `Idempotency-Key` header makes a retry that sends the same key return the first run instead of starting a second one; see [Idempotency keys](/docs/integration-notes#idempotency-keys).

| Field                      | Meaning                                                                                                                                                                                                 |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| base\_model\_id            | One of the tool’s base\_model\_ids, by ID, slug, name or alias. Without it, the run takes the [default](#the-default-model).                                                                            |
| input                      | The fields of the tool’s schema on that model. Send every field the schema lists in required; a field you leave out takes its default, and the tool sets the values it fixes.                           |
| source\_file\_id           | The file an edit works on. Required where the schema says x-source.required; a tool that makes something new takes none.                                                                                |
| mask\_file\_id             | Only where the schema says x-source.takes\_mask: an image the size of the source, white where the edit may change it and black where the source stays as it is.                                         |
| batch\_size                | How many outputs the run makes, 1 to 16\. More than 1 needs a tool with batches\_outputs and a model that batches.                                                                                      |
| reference\_sets            | [Reference sets](/docs/models#reference-sets) to apply, as on POST .../inferences. A model that needs one, such as an ElevenLabs voice changer, refuses a run without it with REFERENCE\_SET\_REQUIRED. |
| session\_id, session\_name | The [session](/docs/sessions) the run lands in. The policy of its project decides the models the run may take.                                                                                          |
| webhook                    | Where to deliver the run’s terminal event. See [Webhooks](#webhooks).                                                                                                                                   |

An edit that sets no size keeps its source’s shape. The response adds `base_model_id`, the model the run takes, and `reference_set_contributions`, what each reference set contributed, to what a run of a base model returns.

A run the tool can’t make is refused before it starts. A model outside the tool’s pool is a `422` with `INFERENCE_TOOL_MODEL_NOT_IN_POOL`. A source the run needs or can’t take, a mask the model takes none of, or a batch where one output is made is a `422` with `INVALID_INPUTS`, its `detail` naming the field. See [Error codes](/docs/errors#error-codes).

## Pricing a run first

`POST /v2/workspaces/{workspace_id}/inference-tools/{inference_tool_key}/inferences/estimate` prices a run without starting it. Send the body of the run without `session_name` and `webhook`. The estimate builds the run as the run does, so send the same `source_file_id` and `session_id` too: an upscale is priced from the size of its source, and a project’s policy can change the default model, and with it the price.

```bash
curl -X POST "https://api.app.layer.ai/api/v2/workspaces/$WORKSPACE_ID/inference-tools/image.upscale/inferences/estimate" \
  -H "Authorization: Bearer $LAYER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "base_model_id": "topaz-wonder-3.5-upscale-image",
    "source_file_id": "FILE_ID",
    "input": { "upscale_factor": "4" }
  }'
```

| Field                                                                              | Meaning                                                        |
| ---------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| estimated\_price\_creative\_units                                                  | What the run costs.                                            |
| workspace\_balance\_creative\_units                                                | The workspace’s available balance.                             |
| estimated\_remaining\_balance\_creative\_units                                     | The balance after the run. Can be negative.                    |
| has\_sufficient\_creative\_units                                                   | Whether the workspace can afford the run.                      |
| base\_model\_id                                                                    | The model the run takes.                                       |
| reference\_set\_contributions, reference\_sets\_degraded, reference\_sets\_warning | What each reference set would contribute, and which would not. |

A body the run would refuse is refused here with the same status and `error_code`, so an estimate also checks a run before anything is spent. What the workspace has left is not checked here. Your [rate limit](/docs/rate-limits) and usage limit are checked when you submit the run, and the balance only as the run spends: a run accepted with a `202` can still end `failed` with `error_code` `INSUFFICIENT_BALANCE` or `USAGE_LIMIT_EXCEEDED`. Check `has_sufficient_creative_units` before you run, and handle those failures when you poll the run or read its webhook. See [Creative Units & billing](/docs/creative-units).

## Webhooks

A run takes a `webhook`, as `POST .../inferences` does, so its terminal event goes to your URL instead of you polling for it. The response’s `expected_events` lists the events that can arrive: of `inference.completed`, `inference.failed` and `inference.cancelled`, only the one that ends the run, plus the scoring events where the scope scores automatically. The delivery’s `data` is the body of `GET .../inferences/{inference_id}`, so it carries the run’s `inference_tool_key` and `base_model_id`. See [Webhooks](/docs/webhooks) for the payload, signing and retries.

## One run per file

A file slot whose schema carries `x-layer.batchable: true`, such as the reference images of `image.edit` and `video.edit`, is one the tool can run once per file, as the Layer app runs it. The API doesn’t fan a run out: a run takes every file you give it, together. For one run per file, send one run per file.

This is a different thing from `batch_size`, which asks one run for several outputs from the same inputs.

## A tool key on a base model run

A run of a base model can name the tool it is, so its asset reopens in that tool in the Layer app too. Reach for this when you already build runs from a model’s full form:

* `POST /v2/workspaces/{workspace_id}/base-models/{slug}/inferences?inference_tool_key=image.upscale` validates the body against the model’s form for the tool’s job. A field you leave out takes the value the tool’s form sets (a value it fixes, or the default of a field it shows), except the prompt, which you always write.
* `POST /v2/workspaces/{workspace_id}/inferences` takes `inference_tool_key` in its body and runs the body as you send it. It must carry each value the tool fixes, and a reframe tool’s run must ask for the reframe.

Either way the run must be one the tool makes: a `base_model_id` from the tool’s `base_model_ids`, with every value the tool fixes left as the tool sets it. A run that isn’t is refused with a `422`: `UNKNOWN_INFERENCE_TOOL` for a key that’s not a tool, `INFERENCE_TOOL_MODEL_NOT_IN_POOL` for another model or none, `INFERENCE_TOOL_PIN_MISMATCH`, its `detail` naming the field, for a changed or missing fixed value, and the other `INFERENCE_TOOL_*` codes in [Error codes](/docs/errors#error-codes). The tool route above sets the fixed values for you, so prefer it when you start from the tool.

## Reopening a past run in its tool

`GET /v2/workspaces/{workspace_id}/inferences/{inference_id}` returns `inference_tool_key`, the tool the run was made with, beside the `base_model_id` it ran on. To reopen the run in its tool, read the tool’s schema on that model:

```bash
curl "https://api.app.layer.ai/api/v2/workspaces/$WORKSPACE_ID/inference-tools/$INFERENCE_TOOL_KEY/inference-schema?base_model_id=$BASE_MODEL_ID" \
  -H "Authorization: Bearer $LAYER_TOKEN"
```

`inference_tool_key` is `null` for a run made without a tool, which reopens on its model alone.

A past run’s tool or model need not be one you can run now. Your policy may allow none of the tool’s models, another member may have made the run under a looser policy, the tool may have left the catalog, or the model may be disabled. The schema read says which, and what to show instead:

| Schema read                               | Why                                                                                       | What to do                                                                                                                                     |
| ----------------------------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| 200                                       | You can run the tool on the run’s model.                                                  | Open the tool on that model.                                                                                                                   |
| 404 INFERENCE\_TOOL\_NOT\_FOUND           | No tool has the key any more, or you can run none of its models.                          | Fall back to the model alone: when GET .../base-models/{base\_model\_id} answers, open the model’s own form. Else say the tool is unavailable. |
| 404 BASE\_MODEL\_NOT\_FOUND               | You can’t run the run’s model: it’s disabled or retired, or your policy doesn’t allow it. | Open the tool on your default (read the schema without base\_model\_id) and say the run’s model is unavailable.                                |
| 422 INFERENCE\_TOOL\_MODEL\_NOT\_IN\_POOL | The tool no longer runs on that model.                                                    | The same: open the tool on your default.                                                                                                       |

Note

Read the schema with the `project_id` or `session_id` the reopened run would land in, so the answer follows that project’s policy.

## Over MCP

The [MCP server](/docs/mcp) runs the same tools, and your agent picks them on its own when a request is one of their jobs, such as _“upscale this image 4x”_:

1. [list\_inference\_tools](/docs/mcp/tools/layer-tools/list%5Finference%5Ftools) lists the tools, with the same fields and filters as the REST list.
2. [get\_inference\_tool](/docs/mcp/tools/layer-tools/get%5Finference%5Ftool) returns a tool’s summary, the `base_model_id` its fields are for, and the JSON Schema of `input` on that model as `input_schema`.
3. [run\_inference\_tool](/docs/mcp/tools/layer-tools/run%5Finference%5Ftool) runs it, with the fields of the REST run except `webhook`. The run lands in a session: it takes `session_id`, `session_name` or `project_id`.
4. [get\_forge\_run](/docs/mcp/tools/forge/get%5Fforge%5Frun) polls the run, and returns its `inference_tool_key`.

[execute\_forge](/docs/mcp/tools/forge/execute%5Fforge) takes an optional `inference_tool_key` as the flat REST run does, and checks the run the same way. The MCP server has no estimate for a tool run: price it over REST.
