> ## Documentation Index
> Fetch the complete documentation index at: https://docs.engini.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Debug a failed tool call

> Read isSuccess, executionInfo, error codes and request ids to find out what actually went wrong.

<Info>**Uses:** Python · TypeScript · CLI · REST API</Info>

Tool execution has **two** independent failure layers, and confusing them is the most common source of wasted debugging time.

<Note>
  cURL samples assume `BASE=https://api.engini.io/v1` and `AUTH="x-api-key: $ENGINI_API_KEY"` - the setup from the [REST walkthrough](/examples/rest-walkthrough).
</Note>

| Layer               | Looks like                                                  | Where to look                   |
| ------------------- | ----------------------------------------------------------- | ------------------------------- |
| The **call** failed | non-2xx + [error envelope](/concepts/pagination-and-errors) | `errorCode`                     |
| The **tool** failed | **HTTP 200** + `isSuccess: false`                           | `errorMessage`, `executionInfo` |

## Always branch on `isSuccess`

A `200` means Engini reached the downstream app. It does **not** mean the app was happy.

<CodeGroup>
  ```python Python theme={null}
  from engini.errors import EnginiToolExecutionError

  try:
      result = client.tools.execute("gmail_send_mail", {"to": "alex@acme.com"}, connection_id=conn_id)
  except EnginiToolExecutionError as e:
      print(e.error_message, e.history_id, e.execution_info)
  ```

  ```typescript TypeScript theme={null}
  import { EnginiToolExecutionError } from "@engini/sdk";

  try {
    const result = await client.tools.execute("gmail_send_mail", { to: "alex@acme.com" }, { connectionId });
  } catch (e) {
    if (e instanceof EnginiToolExecutionError) console.error(e.errorMessage, e.historyId, e.executionInfo);
    else throw e;
  }
  ```

  ```bash CLI theme={null}
  # a tool-level failure is reported in the envelope, not the exit code
  engini tools call gmail_send_mail --args '{"to":"alex@acme.com"}' --json \
    | jq 'if .isSuccess then .output else {errorMessage, history_id, execution_info} end'
  ```

  ```bash cURL theme={null}
  resp=$(curl -s -X POST "$BASE/tools/gmail_send_mail/execute" -H "$AUTH" \
    -H "Content-Type: application/json" -d '{"fields":{"to":"alex@acme.com"}}')

  echo "$resp" | jq -e '.isSuccess' >/dev/null \
    && echo "$resp" | jq '.output' \
    || echo "$resp" | jq '{errorMessage, historyId, executionInfo}'
  ```
</CodeGroup>

In the SDKs this surfaces as `EnginiToolExecutionError`, carrying `error_message`, `execution_info` and `history_id` - so you get the same three facts without unpacking JSON yourself.

## Read `executionInfo`

When a tool fails, `executionInfo` tells you what the downstream API actually did:

```json theme={null}
{
  "isSuccess": false,
  "errorMessage": "Recipient address rejected",
  "historyId": 918273,
  "executionInfo": {
    "statusCode": 400,
    "headers": { "...": "..." },
    "executionTime": 412.7,
    "dataSize": 189
  }
}
```

* **`statusCode`** - the downstream app's status, not Engini's. A `401` here means *their* credentials expired, which usually means the connection needs refreshing.
* **`executionTime`** / **`dataSize`** - useful when a call is slow or a response is unexpectedly huge.
* **`historyId`** - the execution record. Quote it in support requests; it's the fastest way for us to find the exact run.

## Decode the call-level failures

When the call itself fails, branch on `errorCode` rather than parsing `message` - messages get reworded, codes are contract.

| You see                     | It means                                              | Do this                                          |
| --------------------------- | ----------------------------------------------------- | ------------------------------------------------ |
| `409 NO_DEFAULT_CONNECTION` | Several connections for that app, none marked default | Pass `?connectionId=`, or set a default          |
| `409 NO_TOOLSET_CONNECTION` | The toolset has no connection for this tool's app     | Add one to the toolset                           |
| `403 TOOL_NOT_IN_TOOLSET`   | The tool isn't in the toolset's allow-list            | Widen the toolset, or drop `?toolsetId=`         |
| `400 WRONG_APPLICATION`     | The `connectionId` belongs to a different app         | Use a connection for the tool's own application  |
| `400 VALIDATION_ERROR`      | Bad input                                             | Read `details[]` - it names the offending fields |
| `429 RATE_LIMIT_EXCEEDED`   | Too many requests                                     | Honor `Retry-After`, then retry                  |
| `401 UNAUTHORIZED`          | Bad or missing credential                             | Check `x-api-key`; verify with `whoami`          |

## Check the connection, not the tool

If the same tool fails for one connection and works for another, the connection is the suspect:

<CodeGroup>
  ```python Python theme={null}
  client.connections.check(42)   # raises on a conclusive failure
  ```

  ```typescript TypeScript theme={null}
  await client.connections.check(42);   // rejects on a conclusive failure
  ```

  ```bash CLI theme={null}
  engini connections check 42    # conclusive failure exits 1; inconclusive reports is_alive: null
  ```

  ```bash cURL theme={null}
  curl -s "$BASE/connections/42/check" -H "$AUTH" -o /dev/null -w "%{http_code}\n"   # empty 200 = healthy
  ```
</CodeGroup>

A completed object refresh is an even stronger signal than `check`, because it proves the credential actually authenticated against the provider - see [object sync](/examples/object-sync).

## Always keep the request id

Every response carries an **`x-request-id`** header, echoed as `requestId` in error bodies:

```bash theme={null}
curl -s -D - "$BASE/tools" -H "$AUTH" -o /dev/null | grep -i x-request-id
```

Include it - with `historyId` if you have one - in any support request. Those two ids let us reconstruct your exact call.

## From the CLI

```bash theme={null}
engini tools call gmail_send_mail --args '{}' --dry-run   # validate inputs before executing
engini tools call gmail_send_mail --args @payload.json    # exit code tells you the failure class
echo $?                                                    # 3=auth 4=not found 5=validation 124=timeout
```

`--dry-run` is the cheapest debugging tool available: it validates and previews the request without executing, so you can rule out malformed input before wondering about the connector.
