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

# Tool contract & errors

> The rules every Reply MCP tool enforces — schemas, pagination, response envelopes, error codes, and retries.

These rules apply to **every one of the 70 tools**, verified against the served JSON schemas and
live validation probes. An agent that follows them will rarely hit an avoidable error.

## Contract rules the schemas enforce

* **Unknown arguments are invalid.** Every `inputSchema` sets `additionalProperties: false`. Don't
  pass extra keys "just in case" — the call fails validation.
* **Required means non-empty.** Required string and array fields reject `null`, `""`, and `[]`
  alike. In the server's own words: *"null, empty strings, and empty arrays are not accepted for
  required fields."*
* **Every tool is annotated.** `readOnlyHint: true` (31 tools) or `destructiveHint: true` (39
  tools) arrives in `tools/list`. Gate on these before adding your own confirmation UX.
* **Pagination is uniform.** List/search tools take `top` (default 20, max 100) and `skip`;
  responses return `Data.Items` plus `HasMore`. Iterate with `skip += top` until `HasMore` is
  `false`.
* **Patch semantics on updates.** Every `update_*` tool changes only the fields you pass;
  omitted/null fields keep their current value. A passed list field replaces the whole list.
* **IDs come from resolvers.** Mutating tools require exact numeric IDs from prior
  `search_*` / `list_*` / `filter_*` output. The descriptions repeat one rule verbatim: *"Never
  invent, estimate, default, or ask the user."*
* **Approvals are addressed by pair.** Approve/reject/regenerate identify a Jason draft by
  `sequenceId` + `contactId` — there is no separate draft/message ID.
* **Batches are bounded and explicit.** Bulk tools cap at 100 items. `reply_bulk_approve_messages`
  is atomic (any stale reference rejects the whole batch; nothing is sent). Contact batches return
  per-item results (`Affected` / `AffectedContactIds` / `NotProcessed`) — the authoritative record
  of what actually happened; report exact counts ("reassigned 48 of 50").

## Enum quick reference

Every closed value set the server declares, in one place. All are case-insensitive unless noted.

| Field                       | Values                                                                                   | Used by                                             |
| --------------------------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------- |
| `channel`                   | `Email`, `LinkedIn`                                                                      | `send_inbox_reply`, `list_pending_approvals` filter |
| Per-sequence contact status | `Active`, `Paused`, `Finished`, `Inactive`, `OutOfOffice`                                | `change_status_in_sequence`                         |
| Sequence search status      | `New`, `Active`, `Paused`                                                                | `search_sequences`                                  |
| Reply mode                  | `Review`, `Autonomous` (+ `sendExistingDrafts` boolean)                                  | `set_sequence_reply_mode`                           |
| `toneOfVoice`               | `Confident`, `Persuasive`, `Witty`, `Straightforward`, `Empathetic`                      | reply handlers, reengagement cards                  |
| `responseLength`            | `SuperShort`, `Short`, `Medium`, `Long`                                                  | reply handlers, reengagement cards                  |
| `taskType`                  | `ToDo`, `Call`, `Meeting`, `LinkedIn`, `ManualEmail`, `Sms`, `WhatsApp`                  | `create_task`, `list_my_tasks`                      |
| `linkedInTaskType`          | `Message`, `Connect`, `InMail`, `ViewProfile`                                            | `create_task` (required when `taskType=LinkedIn`)   |
| `callResolution`            | `Positive`, `ToCall`, `Negative`                                                         | `complete_task` (recommended for Call tasks)        |
| Playbook visibility         | `Team` (default), `Organization`                                                         | `create_playbook`, `duplicate_playbook`             |
| `category`                  | `integrations`, `reporting`, `sequences`, `contacts`, `email`, `linkedIn`, `ai`, `other` | `report_unsupported_request`                        |
| `sortMode`                  | `NewestFirst` (default), `OldestFirst`                                                   | `list_pending_approvals`                            |

## Response envelopes

**Transport.** Responses are SSE frames (`text/event-stream`) whose `data:` lines carry ordinary
JSON-RPC payloads. Read the last `data:` frame for the result.

**Success.** A successful `tools/call` returns `result.isError = false`, and the actual payload is
a JSON string inside `result.content[0].text`:

```json theme={null}
{ "Success": true, "Data": { "Items": [ ... ], "HasMore": false } }
```

**Error.** Tool failures are **not** HTTP errors — they come back as **HTTP 200**, JSON-RPC
success, with `result.isError = true` and the error as a JSON string in `result.content[0].text`:

```json theme={null}
{ "Success": false, "ErrorCode": "InvalidArguments", "ErrorMessage": "..." }
```

Parse the inner JSON; branch on `Success`, then on `ErrorCode`. Real captured examples:

```json theme={null}
{"Success":false,"ErrorCode":"InvalidArguments","ErrorMessage":"Tool 'reply_get_sequence_steps' arguments failed validation: SequenceId: 'Sequence Id' must be greater than '0'."}
{"Success":false,"ErrorCode":"InvalidArguments","ErrorMessage":"Tool 'reply_send_inbox_reply' is missing required argument(s): message. Supply a real value for each; null, empty strings, and empty arrays are not accepted for required fields."}
{"Success":false,"ErrorCode":"InvalidArguments","ErrorMessage":"Tool 'reply_search_contacts' arguments failed validation: : At least one of Email or LinkedIn must be provided. If the user gave only a name or company, ask them for an email address or LinkedIn URL instead."}
```

## Error model

Handle failures at three layers:

1. **Transport / auth.** `401` (missing/invalid key), `403` (valid key, missing scope or plan
   feature), `429` (rate limit — back off using `X-Rate-Limit-Reset`), `5xx` (transient).
2. **Tool-level `ErrorCode`s** (inside the envelope): `InvalidArguments`, `NotFound`, `Forbidden`,
   `Conflict`, `InvalidInput`, `InvalidParameter`, `UpstreamFailure`, `ServiceUnavailable`, plus
   domain-specific codes named per tool (`NoEmailAccounts`, `NoContacts`, `ContactLimitExceeded`,
   `ChannelMismatch`, `ContactOptedOut`, `ThreadSendFailed`, `InvalidStatusTransition`,
   `ArticleNotFound`, `PERMISSION_DENIED`, …). Each tool's description states which codes it can
   return and what to do about each.
3. **Per-item partial results.** Batch tools report per-contact skips in `NotProcessed` (e.g.
   `ContactAlreadyInSequence`, `ContactInBlackList`, `NotFound`) while the call as a whole
   succeeds. Read them; never claim an item succeeded unless it appears in the affected list.

## Safe-retry matrix

<Warning>
  A timed-out **send** may already have succeeded server-side. Verify with a read before retrying
  anything that reaches a prospect.
</Warning>

| When                                                                            | Tools                                                                                                                                                                    | Policy                           |
| ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------- |
| Auto-retry once on transient `ServiceUnavailable` / `UpstreamFailure` / timeout | all read tools — `search_*`, `list_*`, `get_*`, `filter_*`, `*_stats`, `compare_*`, `get_app_map`, `search_knowledge_base`                                               | Safe to retry                    |
| Retry only **after a verifying read**                                           | `create_*`, `update_*`, `add_contact_to_sequence`, `change_status_in_sequence`, `assign_*`, `attach_*`, `complete_task`, `mark_contacts_as_replied`                      | Read first, then retry           |
| **Never** auto-retry — confirm with the user first                              | `send_inbox_reply`, `approve_message`, `bulk_approve_messages`, `reject_message`, `start_sequence`, `blacklist_contact`, `change_contact_owner`, switching to Autonomous | Verify server state, then decide |

Next: [Building agents](/mcp/agent-guide) for how to sequence these calls, and the
[tool reference](/mcp/tools) for the full catalog.
