# Integration Service Tool

Walkthrough for adding a tool that calls an Integration Service connector activity (e.g., Slack Send Message, Web Search, Jira Create Issue). Integration Service tools connect to external apps via pre-built connectors and authenticated connections.

For Orchestrator process tools (RPA / agent / API / agentic process), see [../process/process.md](../process/process.md).

## When to Use

- Agent needs to call a third-party SaaS / API exposed via UiPath Integration Service (Slack, Salesforce, Jira, ServiceNow, Web Search, etc.)
- A connection (authenticated link to the target system) already exists or can be created in IS

## Key Difference from Orchestrator Tools

IS tools use **connection** resources (not package/process resources) at the solution level. You only need to create the agent-level `resource.json` — solution-level files are auto-generated by `uip solution resources refresh`.

IS tools differ structurally from Orchestrator-based tools:
- `type` is `"integration"` (not `"process"`, `"agent"`, etc.)
- `location` is `"external"`
- `properties` contains IS-specific fields: `toolPath`, `objectName`, `connection`, `parameters`, `bodyStructure`
- Additional top-level fields: `iconUrl`, `isPreview`
- No `referenceKey` or `argumentProperties`
- Solution-level resources use `connection/` (not `package/` + `process/`)
- **Connection bindings are exempt from `folderPath` propagation** — they are bound by `connection.id`.

## Agent-Level Resource Shape

**Path:** `<AGENT_NAME>/resources/<ToolName>/resource.json`

```jsonc
{
  "$resourceType": "tool",
  "id": "<uuid>",
  "type": "integration",
  "location": "external",
  "name": "<DisplayName from activity>",
  "description": "<full Description from activity — do not truncate>",
  "isEnabled": true,
  "inputSchema": {
    "type": "object",
    "properties": {
      "<fieldName>": {
        "type": "<type from requestField>",
        "title": "<displayName from requestField>",
        "description": "<description from requestField>"
      }
    },
    "additionalProperties": false,
    "required": ["<required field names>"]
  },
  "outputSchema": {
    "$schema": "http://json-schema.org/draft-07/schema#",
    "type": "object",
    "properties": {
      "<scalarField>": {
        "title": "<displayName from responseField>",
        "type": "<type from responseField>",
        "description": "<description from responseField>"
      },
      "results[*]": {                                   // ← literal key, keep the `[*]` suffix
        "title": "<displayName>",
        "type": "array",
        "items": { "$ref": "#/definitions/results[*]" } // ← same literal `[*]` in $ref
      }
    },
    "definitions": {
      "results[*]": {                                   // ← matches the $ref literally
        "type": "object",
        "properties": {
          "<nestedField>": {
            "title": "<displayName>",
            "type": "<type>",
            "description": "<description>"
          }
        }
      }
    }
  },
  "iconUrl": "<connector image URL — see rules below>",
  "settings": {},
  "guardrail": { "policies": [] },     // Must always be present and empty — required for backward-compatible solution loading
  "isPreview": false,
  "properties": {
    "toolPath": "<path from metadata, e.g. /v2/webSearch>",
    "objectName": "<ObjectName from activity, e.g. v2::webSearch>",
    "toolDisplayName": "<DisplayName>",
    "toolDescription": "<full Description from activity — same text as top-level description>",
    "method": "<method from metadata, e.g. POST>",
    "bodyStructure": { "contentType": "json" },
    "connection": {
      "id": "<connection-id from uip is connections list>",
      "name": "<connection name>",
      "elementInstanceId": 0,
      "apiBaseUri": "",
      "state": "enabled",
      "isDefault": false,                    // ← always false on the tool's connection block
      "connector": {
        "key": "<connector-key>",
        "name": "<connector display name>",
        "image": "<same URL as top-level iconUrl>",
        "enabled": true,
        "isPreview": false
      },
      "folder": {
        "key": "<FolderKey from connection>",
        "path": "<same value as folder.key — NOT empty>"
      },
      "solutionProperties": {
        "resourceKey": "<connection-id>"
      }
    },
    "parameters": [
      {
        "name": "<field name>",
        "displayName": "<field displayName>",
        "type": "<field type>",
        "fieldLocation": "body",
        "value": "{{prompt}}",
        "description": "<field description>",
        "position": "primary",
        "sortOrder": 1,
        "required": true,
        "fieldVariant": "dynamic",
        "isCascading": false,
        "dynamic": true,
        "enumValues": null,
        "loadReferenceOptionsByDefault": null,
        "dynamicBehavior": [],
        "reference": null
      }
    ]
  }
}
```

### Building `inputSchema` from `requestFields`

- Each request field becomes a property with its `type` and `description`
- Fields with `enum` → add `enum` and `oneOf` arrays
- Fields with nested dotted names (e.g., `attachment.title`) → nest as objects in the schema
- Fields marked `required: true` → add to the `required` array
- Add `"additionalProperties": false` to the input schema

### Building `outputSchema` from `responseFields`

- Scalar response fields become properties with their `type` and `description` — preserve the metadata description, don't drop it.
- Fields with `[*]` in the name (e.g., `results[*].title`) become a property keyed **literally** `"results[*]"` (keep the `[*]` in the key) with `type: "array"` and `items: { "$ref": "#/definitions/results[*]" }`. Add a matching `definitions` entry keyed with the same literal `"results[*]"`. Do NOT rename to `results`, `resultItem`, or camelCase — Studio Web matches the literal metadata key and will drop the tool if it's renamed.
- Add `"$schema": "http://json-schema.org/draft-07/schema#"` to the output schema

**IMPORTANT rules for `outputSchema` (arrays of objects):**
- Response fields in the metadata named like `results[*].title` indicate an array of objects. Represent this as:
  - A property keyed **literally** `"results[*]"` (keep the `[*]` suffix in the JSON key) with `type: "array"` and `items: { "$ref": "#/definitions/results[*]" }`.
  - A `definitions` entry keyed with the exact same literal `"results[*]"` whose `properties` contain the nested fields (`title`, `snippet`, `url`, ...).
- Do NOT rename to `"results"` / `"resultItem"` / camelCase. Studio Web matches the literal key from the activity metadata — renaming causes the tool to be silently dropped from the agent UI.
- Preserve each response field's `description` from the metadata (both scalar and nested). Do not drop it.

### Building `properties.parameters` from `requestFields`

- Each `requestField` becomes a parameter with `fieldLocation: "body"` and `value: "{{prompt}}"` (dynamic, filled by the LLM at runtime)
- Each `parameter` from metadata (query/path params) keeps its original `fieldLocation` (e.g., `"query"`)
- Fields with `enum` values: set `fieldVariant: "static"`, `dynamic: false`, `value` to the first enum value, and `enumValues` to an **array of `{name, value}` objects** (NOT bare strings) — copy the metadata's `fields.<name>.enum` through verbatim. Bare-string arrays pass `uip agent validate` but make Studio Web drop the tool from the agent UI.
- Fields with `reference`: include the `reference` object and set `loadReferenceOptionsByDefault: true`
- Set `position: "primary"` for required fields, `"secondary"` for optional
- Increment `sortOrder` starting from 1

**Parameter `fieldVariant` values:**
- `"dynamic"` — value filled by the LLM at runtime (`value: "{{prompt}}"`, `dynamic: true`, `enumValues: null`)
- `"static"` — pre-configured value (e.g., single-value enum default). Set `dynamic: false`, `value` to the chosen enum value, and `enumValues` to the object-array form below.

**Parameter `enumValues` format — MUST be an array of `{name, value}` objects, never bare strings:**
```jsonc
"enumValues": [
  { "name": "GoogleCustomSearch", "value": "GoogleCustomSearch" }
]
```
The activity metadata's `fields.<name>.enum` already has this exact shape — copy it through verbatim. A bare-string array like `["GoogleCustomSearch"]` passes `uip agent validate` but makes Studio Web silently drop the tool from the agent UI.

**Parameter `fieldLocation` values:**
- `"body"` — sent in request body (most `requestFields`)
- `"query"` — sent as query parameter (from metadata `parameters` with `type: "query"`)
- `"path"` — sent as path parameter

**Parameter `toolDescription` and top-level `description`:** both must be the full description from the activity metadata. Do not abbreviate either.

### `iconUrl` and `connector.image` rules

- Both fields MUST be populated with the same URL in the tenant-scoped form: `{UIPATH_URL}/{organizationName}/{tenantName}/elements_/v3/element/elements/{connectorKey}/image`.
- Build it directly from the auth env vars (`UIPATH_URL`, `UIPATH_ORGANIZATION_NAME`, `UIPATH_TENANT_NAME`). No discovery call is needed — the tenant route resolves the scale unit server-side.
- Leaving `iconUrl` as `""` or omitting it does NOT produce a validation error, but Studio Web may silently drop the tool from the agent UI. Always populate it.

### `properties.connection` rules

- `connection.id` MUST be the actual IS connection ID (from `uip is connections list`). Studio Web validates tools by fetching the connection by this ID — a random UUID will cause "Connection is required" errors.
- `connection.folder.key` AND `connection.folder.path` MUST both be populated. `path` is the folder key string (same value as `key`) — never the empty string.
- `connection.isDefault` MUST be `false` on the tool's connection block, even if the connection is marked default in `uip is connections list`. The flag here is tool-scoped, not IS-scoped.
- `solutionProperties.resourceKey` MUST equal `connection.id`. This links the tool to the solution connection resource.
- All tools sharing the same connector MUST share the same `solutionProperties.resourceKey`.

## Solution-Level Files

**Auto-generated.** Do not create them manually. After creating the agent-level `resource.json`:

1. Run `uip agent refresh` — regenerates `entry-points.json` and `bindings_v2.json` in the agent project directory.
2. Run `uip agent validate` — read-only check. Fails with `AgentValidationOutdated` if refresh is needed.
3. Run `uip solution resources refresh` from the solution root — auto-generates `resources/solution_folder/connection/{connectorKey}/` files and `debug_overwrites.json`.

**`location` and `folderPath`:**

| `location` | `folderPath` | Meaning |
|------------|-------------|---------|
| `"solution"` | `"solution_folder"` | Resource is another project within this same solution. Creating this agent-level resource.json is sufficient. |
| `"external"` | `"solution_folder"` | Resource is already deployed in Orchestrator, outside this solution. Write the agent-level resource.json, then run `uip agent refresh` → `uip agent validate` → `uip solution resources refresh` — refresh auto-generates the solution-level process declaration, package declaration, and `debug_overwrites.json` entry. |

### Connection Definition (refresh fallback)

**Path:** `resources/solution_folder/connection/{connectorKey}/{connectionName}.json`

Provisions an Integration Service connection as part of the solution. Required when an agent has an integration tool (`type: "integration"`). One per connector — all tools using the same connector share this connection resource.

**Auto-generated:** Do not create these files manually. After creating the agent-level integration tool `resource.json`, run `uip agent refresh` (regenerates `entry-points.json` and `bindings_v2.json`), then `uip agent validate` (read-only check), then `uip solution resources refresh` (auto-generates connection resources and `debug_overwrites.json` from `bindings_v2.json`).

**Cross-reference:** The connection resource `key` matches the `solutionProperties.resourceKey` in integration tool resources that use this connector.

```jsonc
{
  "docVersion": "1.0.0",
  "resource": {
    "name": "my-connection",           // Connection identifier
    "kind": "connection",
    "type": "uipath-salesforce-slack", // Connector key from IS
    "apiVersion": "integrationservice.uipath.com/v1",
    "isOverridable": true,
    "spec": {
      "connectorName": "Slack",
      "authenticationType": "AuthenticateAfterDeployment",  // credentials provided post-deploy
      "connectorVersion": "2.13.8",
      "connectorKey": "uipath-salesforce-slack",
      "pollingInterval": 5
    },
    "key": "<unique-uuid>"
  }
}
```

`authenticationType: "AuthenticateAfterDeployment"` means the connection credentials are provided by the user after deployment (not bundled in the solution).

## Walkthrough

### Step 1 — Scaffold solution and agent (if not already done)

Scaffold per [../../project-lifecycle.md § End-to-End Example](../../project-lifecycle.md#end-to-end-example--new-standalone-agent).

### Step 2 — Find the connector

```bash
uip is typecache packages --output json
```

`uip is typecache packages` returns the connectors available to low-code agents — the curated set the Agent Builder UI shows, from the Studio typecache. Note the connector `Key` (e.g., `uipath-salesforce-slack`). It defaults to `--project-type Agent`; pass a different `--project-type` for other project types.

### Step 3 — Find a connection

```bash
uip is connections list "<connector-key>" --output json
```

Present connections to the user. Recommend the default enabled one but let the user confirm. Note the connection `Id`, `FolderKey`, `Name`. If no connection exists, prompt the user to create one via `uip is connections create "<connector-key>"`.

This command also populates the local cache at `~/.uipath/cache/integrationservice/<connector-key>/connections.json` — used later by `uip solution resources refresh` to generate `debug_overwrites.json`. Running it is mandatory even when you already found the connection via `uip solution resources list --kind Connection`, because refresh reads from the cache this command writes.

### Step 4 — Discover activities

```bash
uip is typecache activities "<connector-key>" --output json
```

`uip is typecache activities` calls the same Agent Builder typecache endpoints the frontend uses. It returns exactly the activities the UI shows for low-code agents — the curated subset from the Studio NuGet package for the connector. It defaults to `--project-type Agent`. Activities with an empty `objectName` (deprecated stubs) are filtered out. File operation and HTTP activities appear in the UI with a "Preview" chip but are fully selectable — they are included in the output. Connectors not in the typecache are absent from the UI and return empty here. Present the activities to the user and note the chosen activity's `DisplayName`, `Description`, `ObjectName`, `MethodName`.

### Step 5 — Get connector details (for iconUrl)

```bash
uip is connectors get "<connector-key>" --output json
```

Note the connector `Name` and image URL.

### Step 6 — Get activity metadata

```bash
uip is resources describe "<connector-key>" "<object-name>" \
  --connection-id "<connection-id>" --operation Create --output json
```

The response includes a `metadataFile` path. Read that cached JSON file to get:
- `requestFields` → build `inputSchema` and `properties.parameters` (body fields)
- `responseFields` → build `outputSchema`
- `parameters` → query/path parameters (add to `properties.parameters`)
- `path` → `properties.toolPath`
- `method` → `properties.method`
- `description` → tool description

If no `--connection-id` is available (e.g., the connector auto-provisions connections), omit it — static metadata will be returned. To verify a connection's health beforehand, run `uip is connections ping "<connection-id>" --output json`.

### Step 7 — Build and write the tool resource.json

**File:** `<AGENT_NAME>/resources/<ToolName>/resource.json`

Build the `resource.json` from the metadata. See § Agent-Level Resource Shape above for the full template and field mapping.

### Step 8 — Refresh, validate, and refresh solution resources

```bash
# Refresh — regenerates entry-points.json and bindings_v2.json.
uip agent refresh "<AGENT_NAME>" --output json

# Validate — read-only check.
uip agent validate "<AGENT_NAME>" --output json

# Refresh solution resources — auto-generates solution-level connection
# resources and debug_overwrites from bindings_v2.json
uip solution resources refresh --output json
```

At this point, the solution can be uploaded to Studio Web and tested:

```bash
uip solution bundle . -d ./dist --output json
uip solution upload ./dist/<SOLUTION_NAME>.uis --output json
```

## Gotchas

See [../../critical-rules/critical-rules.md](../../critical-rules/critical-rules.md):
- Critical Rules 11, 12 (folderPath / location)
- Critical Rule 13 (use `uip is typecache` for low-code IS discovery)

IS-specific gotchas (each from a real Studio-Web silent-drop bug):
- `enumValues` MUST be `[{name, value}]` objects, not bare strings.
- `outputSchema` array properties keyed `"results[*]"` literally — renaming to `results` drops the tool.
- `iconUrl` and `connector.image` MUST both be set to the tenant-scoped URL `{UIPATH_URL}/{org}/{tenant}/elements_/v3/element/elements/{connectorKey}/image`.
- `connection.folder.path` MUST equal `connection.folder.key` (never empty).
- `connection.isDefault` MUST be `false` even if the IS connection is default.
- `solutionProperties.resourceKey` MUST equal `connection.id` and is shared by all tools using the same connector.

## References

- [../../agent-definition.md](../../agent-definition.md) § Resources Convention
- [../../solution-resources.md](../../solution-resources.md) § Refresh Mechanics
- [../../project-lifecycle.md](../../project-lifecycle.md) § Resource Discovery
