# Get a tool

> Fetch one tool's live input schema and the hash you generate against.

Canonical page: https://www.rundiffusion.com/docs/api/tools/get
Endpoint: GET /api/v2/tools/{tool_id}
Authorization: OAuth device flow or Personal API Access Token or Company API Access Token

---

The detail response is what you build a generate request from. It carries the
`tool_fields_hash`, the `fields` you populate, and the `num_results` bounds.

`GET /api/v2/tools/{tool_id}`

Authorization: OAuth device flow or Personal API Access Token or Company API Access Token

## Request

**Headers**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Authorization` | string | Yes | Bearer token. This endpoint accepts an OAuth device flow token, a Personal API Access Token, or a Company API Access Token. See [Authentication](/docs/api/authentication). |

**Path**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `tool_id` | string | Yes | The id of a tool from the [list response](/docs/api/tools/list). An unknown ID returns 404. |

No query parameters and no body.

## Response

`200 OK`. The detail response repeats every list field and adds everything you
need to build a generate request: the `tool_fields_hash`, the `fields` you
populate, and the `num_results` bounds.

```json
{
  "id": "1kCVin…",
  "name": "Nano Banana 🍌",
  "description": "Fast banana-themed image generator.",
  "avatar_url": "https://api2.rundiffusion.com/api/v2/avatars/Xc4LpQ….png",
  "average_gen_in_seconds": 12,
  "tool_tags": [
    { "id": "9QmTz4…", "type": "MEDIA", "label": "Image" },
    { "id": "Ld6PnQ…", "type": "USE_CASE", "label": "Text to Image" }
  ],
  "pricing_type": "COST",
  "tokens_per_result": 85,
  "tool_fields_hash": "v1:9f2c1a…",
  "num_results": { "min": 1, "max": 4, "default_value": 1 },
  "fields": [
    {
      "isField": true,
      "fieldKey": "b6f0a41c-…",
      "type": "PROMPT",
      "label": "Prompt",
      "required": true,
      "display": { "maxLength": 2000 }
    },
    {
      "isField": true,
      "fieldKey": "8d21e7b9-…",
      "type": "WIDTH_HEIGHT",
      "label": "Size",
      "required": true,
      "display": {
        "select": {
          "format": "wh",
          "options": [
            { "text": "Square", "value": "1024x1024", "width": 1024, "height": 1024 },
            { "text": "Widescreen", "value": "1344x768", "width": 1344, "height": 768 }
          ]
        }
      }
    },
    {
      "isGroup": true,
      "label": "Advanced",
      "display": { "expandMode": "CLOSED" },
      "fields": [
        {
          "isField": true,
          "fieldKey": "5f1d90ac-…",
          "type": "SINGLE_SELECT",
          "label": "Style",
          "required": false,
          "defaultValue": "photographic",
          "display": {
            "text": {
              "options": [
                { "text": "Photographic", "value": "photographic" },
                { "text": "Illustration", "value": "illustration" }
              ]
            }
          }
        },
        {
          "isField": true,
          "fieldKey": "a03b5c62-…",
          "type": "STEPS",
          "label": "Steps",
          "required": false,
          "defaultValue": 20,
          "display": { "min": 1, "max": 50 }
        }
      ]
    }
  ]
}
```

> **fields is camelCase, not snake_case**
>
> The rest of the API is snake_case. `fields` is not: it is the tool's own
> schema, passed through as the product stores it, so its keys stay camelCase.
> You will see `fieldKey`, `defaultValue`, `maxLength`, `expandMode`. Read them
> exactly as they arrive.

**Added by the detail response**

| Name | Type | Description |
| --- | --- | --- |
| `tool_fields_hash` | string | Versioned hash, prefixed v1: followed by 64 hex characters. Pass it back unchanged when you generate. It is how the API detects that the tool changed after you fetched it, and a mismatch returns 409 TOOL_SCHEMA_STALE. |
| `num_results` | object \| null | How many results this tool may be asked for, as {min, max, default_value}. Send your choice as [num_results](/docs/api/generate/create) on the generate request. Null means the tool always returns exactly one result and num_results is ignored. |
| `fields` | array | The input schema, as a tree. Walk it recursively: every node is either a group that carries children or a leaf that is one input you supply. Order is the order the product displays. |

### Inside `fields`

Each node carries exactly one of `isField` or `isGroup`, always `true`. The
other key is simply absent, so test for the one you want rather than reading a
boolean off both. Groups exist to title and collapse a set of inputs in a form;
they carry no value of their own, so a client that is not rendering a UI can
flatten them away and keep only the leaves.

**Every node**

| Name | Type | Description |
| --- | --- | --- |
| `isField` | true | Present only on a leaf, which is one input you supply a value for. Absent on a group. |
| `isGroup` | true | Present only on a group, whose children are under fields. Absent on a leaf. Groups nest, so recurse rather than assuming one level. |
| `label` | string | The human-readable name shown in the product. For display only. It is not the key, it can be edited at any time, and it is not part of tool_fields_hash. |
| `display` | object | Per-type presentation block. Read it: for the select types it holds the permitted option values, and for the slider and numeric types it holds the permitted range. Its keys vary by type. See below. |
| `logic` | object | Optional conditional rules, under logic.field keyed by the fieldKey of the field being watched. Each rule may carry show, require, disable, or autoSet, and each states the watched value under onValue. This is what the product applies as a form is filled in. |

**Leaf nodes only**

| Name | Type | Description |
| --- | --- | --- |
| `fieldKey` | string | The key for this input. Use it verbatim as a key in the inputs object on a generate request. It is an opaque identifier rather than a slug, so renaming the field in the product never breaks a working integration. Read it fresh from the response rather than hard-coding it. |
| `type` | string | What kind of value the field takes. It determines both the shape you send in inputs and which keys display carries. HEADER and HTML are captions rather than inputs: they never appear in inputs. One of: `PROMPT`, `NEG_PROMPT`, `TEXT`, `TEXT_AREA`, `NUM`, `BOOLEAN`, `SINGLE_SELECT`, `MULTI_SELECT`, `SEED`, `STEPS`, `CFG`, `SLIDER`, `UPSCALE_FACTOR_SLIDER`, `WIDTH_HEIGHT`, `IMG`, `IMGS`, `VIDEO`, `ASSET_3D`, `HEADER`, `HTML` |
| `required` | boolean | Whether a generate request must include this field. Omitting an optional field accepts defaultValue. |
| `defaultValue` | any | The value used when you omit the field. Absent when the field has no default, which for an optional field means the tool decides. |
| `tooltip` | string | Help text shown beside the field in the product. Display only, and not part of tool_fields_hash. |
| `placeholder` | string | Placeholder text for an empty input. Display only, and not part of tool_fields_hash. |

#### What `display` carries, by type

The `display` block is the difference between a request the tool accepts and one
it rejects. A `SINGLE_SELECT` will not take an arbitrary string, and a `STEPS`
field will not take an arbitrary number: the permitted values live here.

| `type` | Read from `display` |
| --- | --- |
| `SINGLE_SELECT`, `MULTI_SELECT` | One of `text`, `radio`, or `image`, each holding `options[]`. Send an option's `value`, never its `text`. |
| `WIDTH_HEIGHT` | `select.options[]` of `{ text, value, width, height }`, or `sliders` with `min` and `max` as `[width, height]` pairs. |
| `SLIDER`, `UPSCALE_FACTOR_SLIDER` | `min`, `max`, `step`, and sometimes an explicit `options[]` that narrows the choice further. |
| `STEPS`, `CFG` | `min` and `max`. |
| `PROMPT`, `NEG_PROMPT` | `maxLength`, the character ceiling. |
| `IMG`, `IMGS`, `VIDEO` | Constraints the API enforces on what you attach, such as `maxImages`, `minDimension`, `maxAspectRatio`, `maxDuration`. |
| `HEADER`, `HTML` | `subheader` or `content`, the caption text itself. |
| Groups | `expandMode`, `OPEN` or `CLOSED`. Presentation only. |

Keys are added as tool types gain options, so read the block rather than
destructuring a fixed set of keys out of it.

A `display` key ending in `FieldKey`, such as `dataFieldKey` on a
`WIDTH_HEIGHT` or `UPSCALE_FACTOR_SLIDER`, holds another leaf's `fieldKey`. It
points at the field this one reads to offer a suggestion in the product, so it
is a cross-reference rather than a value you send.

## Errors

| Name | Type | Description |
| --- | --- | --- |
| `401` | UNAUTHENTICATED | Missing, malformed, or expired token. |
| `404` | NOT_FOUND | No tool with that ID, or it is not visible to this caller. |
| `429` | RATE_LIMITED | Too many requests. Back off and retry per the Retry-After header. See [Rate limits](/docs/api/rate-limits#tools). |

See [Errors](/docs/api/errors) for the full envelope and the code list.

## Treat the schema as live

`fields` describes the tool as it is right now, and `tool_fields_hash` is a
fingerprint of exactly that. Editing the tool changes both together. Sending the
pair is what lets the server confirm your inputs were built against the tool it
is about to run.

The loop that keeps working:

1. `GET /api/v2/tools/{tool_id}` for the current `fields` and `tool_fields_hash`.
2. Build `inputs`, keyed by the `fieldKey` values in **that** response.
3. Send the inputs and that hash to `generate` together.
4. On `409 TOOL_SCHEMA_STALE`, return to step 1. The live hash comes back in
   `details.expected_tool_fields_hash`, and re-fetching gets you the fields it
   belongs to.

Step 4 is not an error path you need to engineer around. It is how the API tells
you a tool moved, and a client that simply loops back to step 1 recovers on its
own, without anyone being paged.

> **Never hardcode a hash or a field key**
>
> Caching the detail is fine and worth doing. Cache the **whole** response so
> `fields` and `tool_fields_hash` stay together, and let step 4 evict it. What
> does not survive is either value copied into source. Any edit to the tool moves
> the hash, including a change to a field you were not even using, so a pinned
> hash starts failing every generate call at the same moment and needs a deploy
> to recover. The same goes for a `fieldKey`: it is stable while the field
> exists, but nothing guarantees the field itself stays.

The loop above is the whole discipline: read the tool, build from what it
returned, and let a 409 send you back to the start.
