{
  "schemaVersion": "1.0",
  "title": "Chatwoot AIP composite operations",
  "description": "Use these three connector-owned capabilities when the strict message, status, or handoff contract matches the task. Their colon-named IDs, inputs, outputs, and provider behavior differ from the generic dot-named catalogue.",
  "canonical": "https://getaip.org/docs/connectors/chatwoot/capabilities/aip-composite-operations",
  "route": "/docs/connectors/chatwoot/capabilities/aip-composite-operations",
  "source": "docs/connectors/chatwoot/capabilities/aip-composite-operations.md",
  "protocol": "Agent Interoperability Protocol",
  "protocolVersion": "1.0",
  "section": "Connectors",
  "documentType": "Connector",
  "language": "en",
  "downloads": {
    "md": "/docs/download/connectors/chatwoot/capabilities/aip-composite-operations.md",
    "txt": "/docs/download/connectors/chatwoot/capabilities/aip-composite-operations.txt",
    "json": "/docs/download/connectors/chatwoot/capabilities/aip-composite-operations.json",
    "pdf": "/docs/download/connectors/chatwoot/capabilities/aip-composite-operations.pdf"
  },
  "content": {
    "format": "text/markdown",
    "markdown": "---\ntitle: Chatwoot AIP composite operations\ndescription: >-\n  Use three strict connector-owned composites with explicit message, status,\n  handoff, idempotency, and partial-effect boundaries\nkind: capability-reference\naudience: application-developer\nappliesTo: \"1.x\"\nwritingStandard: \"aip-docs/1.0\"\nlastReviewedRevision: \"97be86e9efedf07ecf1783b03800f683f107fb04\"\nconnector: chatwoot\ncapabilityIds:\n  - cap:chatwoot:message:create\n  - cap:chatwoot:conversation:status\n  - cap:chatwoot:conversation:handoff\naliases:\n  - Chatwoot composite capabilities\n  - Chatwoot strict message and handoff operations\n---\n\n# Chatwoot AIP composite operations\n\nUse these three connector-owned capabilities when the strict message, status,\nor handoff contract matches the task. Their colon-named IDs, inputs, outputs,\nand provider behavior differ from the generic dot-named catalogue.\n\nRead the pinned implementation caveats before calling them. Two published\nschemas permit inputs that do not produce the intuitive runtime behavior unless\nthe application adds a stricter check.\n\n## Choose composite or catalogue\n\n| Composite on this page | Related catalogue capability | Choose the composite when |\n|---|---|---|\n| `cap:chatwoot:message:create` | `cap:chatwoot:message.create` | Text and public/private mode fit the strict connector input |\n| `cap:chatwoot:conversation:status` | `cap:chatwoot:conversation.toggle_status` | The desired status is exactly `open`, `resolved`, or `pending` |\n| `cap:chatwoot:conversation:handoff` | `cap:chatwoot:conversation.assignment` plus optional message operation | One governed intent assigns and optionally adds a private note |\n\nColons and dots are significant. Do not normalize either ID or reuse approval,\nAction, or idempotency evidence across them.\n\n## Composite summary\n\n| Capability | Kind | Risk | Approval | Retry | Provider calls |\n|---|---|---|---|---|---:|\n| `message:create` | Tool | Medium | Required | Unsafe | 1 |\n| `conversation:status` | Tool | Medium | Required | Safe | 1 |\n| `conversation:handoff` | Tool | High | Required | Unsafe | 0–2 |\n\nAll three execute synchronously. None advertises async, streaming,\ncancellation, transaction, reconciliation, or rollback support.\n\n## Discovery and operation allowlist\n\nThe connector builds discovery by adding these three composites first and then\nthe admitted dot-named catalogue operations. The Chatwoot operations file\nfilters the 150-operation catalogue; it does not remove these composites from\nthe connector-generated manifest.\n\nTenant catalogue admission, routing, policy, and authorization still apply. A\nconnector-generated capability is not permission for every actor or tenant to\ninvoke it.\n\n## Send a message\n\n`cap:chatwoot:message:create` publishes this input:\n\n| Field | Type | Published requirement | Meaning |\n|---|---|---|---|\n| `conversation_id` | String | Required | Conversation in the configured account |\n| `content` | String | Required | Message or private-note text |\n| `private` | Boolean | Optional; schema default `false` | `false` for public reply, `true` for private note |\n\nAdditional properties are rejected by the published schema. The caller cannot\nsupply the internal AIP action marker.\n\n### Supply private explicitly\n\nThe published schema marks `private` optional, but the pinned invocation path\ndeserializes into a Boolean field without a serde default. A direct input that\nomits `private` can fail as `connector.chatwoot.invalid_action`.\n\nSend the field explicitly until source and schema agree:\n\n```json\n{\n  \"conversation_id\": \"314\",\n  \"content\": \"Your request is now with the operations team.\",\n  \"private\": false\n}\n```\n\nThis guidance avoids relying on a JSON Schema default as if it were applied by\nthe connector.\n\n### Provider request\n\nThe connector posts to:\n\n```text\nPOST /api/v1/accounts/{configured_account_id}/conversations/314/messages\n```\n\nIt constructs a provider body with:\n\n- the supplied `content` and `private` values;\n- `content_attributes.aip.connector_id` set to `chatwoot`;\n- `content_attributes.aip.loop_prevention` set to `true`;\n- `content_attributes.aip.action_id` set from the real AIP Action ID.\n\nThose attributes let inbound mapping suppress the connector's own message\necho. They do not prove external delivery or provider deduplication.\n\n### Constructed result\n\nAfter a successful provider response, the connector discards the provider body\nand returns:\n\n```json\n{\n  \"conversation_id\": \"314\",\n  \"sent\": true\n}\n```\n\n`sent: true` means the provider request returned a successful HTTP status. It\ndoes not prove that a customer channel delivered or displayed the message.\n\n## Update conversation status\n\n`cap:chatwoot:conversation:status` requires exactly:\n\n| Field | Type | Accepted values |\n|---|---|---|\n| `conversation_id` | Non-blank string | Conversation in the configured account |\n| `status` | String enum | `open`, `resolved`, `pending` |\n\nAdditional properties are rejected by the published schema.\n\nExample:\n\n```json\n{\n  \"conversation_id\": \"314\",\n  \"status\": \"pending\"\n}\n```\n\nThe connector posts:\n\n```text\nPOST /api/v1/accounts/{configured_account_id}/conversations/314/toggle_status\n```\n\nwith:\n\n```json\n{\n  \"status\": \"pending\"\n}\n```\n\nAfter a successful provider response, it constructs:\n\n```json\n{\n  \"conversation_id\": \"314\",\n  \"status\": \"pending\"\n}\n```\n\nThis is the only Chatwoot mutation whose connector contract advertises safe\nretry. The provider call still receives no AIP idempotency header. Read current\nconversation state before a repeat and preserve the same governed intent.\n\n## Handoff a conversation\n\n`cap:chatwoot:conversation:handoff` publishes:\n\n| Field | Type | Published requirement | Effect |\n|---|---|---|---|\n| `conversation_id` | String | Required | Selects the conversation |\n| `assignee_id` | String | Optional | Requests provider assignment |\n| `note` | String | Optional | Sends a private handoff note |\n\nAdditional properties are rejected by the schema.\n\n### Require an intent field\n\nThe published schema requires only `conversation_id`. The pinned handler does\nnot reject an input with neither `assignee_id` nor `note`. It makes no provider\nrequest and still returns `handoff: true`.\n\nApplications should require at least one non-empty intent field before\napproval:\n\n```json\n{\n  \"conversation_id\": \"314\",\n  \"assignee_id\": \"42\",\n  \"note\": \"Escalated with the approved incident context.\"\n}\n```\n\nThis application check is stricter than the pinned capability schema. It\nprevents a successful no-op from being mistaken for a handoff.\n\n### Provider sequence\n\nWhen `assignee_id` is present, the connector first posts:\n\n```text\nPOST /api/v1/accounts/{configured_account_id}/conversations/314/assignments\n```\n\nwith `{\"assignee_id\":\"42\"}`.\n\nWhen `note` is present, the connector then posts a private message through the\nsame message helper used by `message:create`. The note includes the AIP\nloop-prevention, connector, and Action markers.\n\nAfter all requested calls succeed, the connector constructs:\n\n```json\n{\n  \"conversation_id\": \"314\",\n  \"handoff\": true\n}\n```\n\n### Partial-effect boundary\n\n| Requested fields | Provider sequence | Important failure boundary |\n|---|---|---|\n| Assignee only | Assignment | A lost response can hide a completed assignment |\n| Note only | Private message | A lost response can hide a created note |\n| Assignee and note | Assignment, then private message | Note failure can leave assignment completed |\n| Neither | No provider request | Connector returns success without a handoff effect |\n\nThe two-call path is not atomic. It has no transaction, reconcile method, or\ncompensating capability. Recovery must inspect assignment and message state\nseparately.\n\n## Shared approval and idempotency contract\n\nAll three composites declare:\n\n| Contract field | Value |\n|---|---|\n| Approval | Required; tenant-policy selector |\n| Approval validity | 900,000 ms |\n| Approval evidence | Reason, input snapshot, policy decision |\n| Idempotency | Required; external-account scope; 86,400,000 ms published TTL |\n| Data | Restricted; contains PII; redaction required |\n| Execution | Synchronous; no async, streaming, or cancellation support |\n| Compensation | Rollback not supported |\n| Expected latency hint | 3,000 ms |\n| Provider timeout hint | 30,000 ms |\n| Maximum queue-delay hint | 5,000 ms |\n\nThe AIP idempotency key remains part of governed execution, but these composite\nprovider requests do not add an `Idempotency-Key` header. They also do not add\nthe catalogue's `X-AIP-Action-ID` header.\n\nThe pinned composite invoke branches do not call the catalogue's\n`required_idempotency_key` check. Use the governed AIP execution path, which\nowns contract enforcement; do not treat direct connector invocation as an\nidempotency gate.\n\nMessage and handoff notes embed the Action ID inside provider content\nattributes. Status and assignment requests contain no equivalent Action field\nin their constructed bodies.\n\nDo not treat the AIP key as proof of Chatwoot deduplication. Preserve the Action\nand key for AIP settlement, then reconcile provider state before another call.\n\n## Side effects\n\n| Composite | Declared side effects |\n|---|---|\n| `message:create` | `send_message`, `write`, `external_network` |\n| `conversation:status` | `write`, `external_network` |\n| `conversation:handoff` | `write`, `send_message`, `identity`, `external_network` |\n\nHandoff declares the broadest policy signal because it can change ownership\nand add a private message. Approval should match the actual optional fields,\nnot only the capability ID.\n\n## Failures and recovery\n\n| Failure code | Typical cause | Safe response |\n|---|---|---|\n| `connector.chatwoot.invalid_action` | Schema/runtime mismatch, invalid status, or malformed strict input | Correct input before any provider dispatch |\n| `connector.chatwoot.authentication` | Provider returned `401` or `403` | Verify account route and credential revision |\n| `connector.chatwoot.remote_rejected` | Provider rejected input or current state | Correct authoritative state before a new Action |\n| `connector.chatwoot.remote_temporary` | Provider returned `429` or a server error | Read provider state; use status retry only within its contract |\n| `connector.chatwoot.transport` | Provider exchange failed | Treat the mutation outcome as uncertain |\n| `connector.chatwoot.response_too_large` | Provider response crossed the active bound | Reconcile state; do not enlarge the bound to force a repeat |\n\nEvery failure sets `retryable: false` and omits a provider request ID. The\nstatus capability's safe-retry contract is a separate policy signal.\n\nFor uncertain message creation, inspect the conversation for the Action marker\nbefore another call. For uncertain status, read the conversation and compare\nthe intended state. For uncertain handoff, inspect assignment first and then\nthe private note.\n\nIf assignment completed but note failed, do not repeat the entire handoff\nblindly. Decide whether a separately approved note-only Action is appropriate\nfor current state.\n\n## Verify an integration\n\nRequire all of the following:\n\n- discovery returns the exact colon-named capability;\n- the operations allowlist is not mistaken for composite authorization;\n- `private` is present explicitly for `message:create`;\n- handoff contains at least one reviewed intent field;\n- the governed AIP path enforces the required idempotency contract;\n- approval binds every optional field and the intended external effect;\n- one Action ID, canonical input, and idempotency key are retained;\n- constructed output is not mistaken for raw provider output;\n- message delivery, status, assignment, and note state are verified through\n  authoritative Chatwoot reads;\n- loop-prevention attributes are retained for connector-originated messages;\n- partial or uncertain outcomes are reconciled instead of repeated.\n\nThese checks validate application handling. They do not establish customer\ndelivery, provider deduplication, atomic handoff, or live qualification.\n\n## Related documentation\n\n- [Chatwoot capability index](README.md)\n- [Conversations and messages](conversations-and-messages.md)\n- [Run the Chatwoot connector quickstart](../getting-started/quickstart.md)\n- [Approvals and policy](../../../concepts/approvals-and-policy.md)\n- [Error reference](../../../reference/errors.md)\n",
    "text": "Chatwoot AIP composite operations\n\nUse these three connector-owned capabilities when the strict message, status,\nor handoff contract matches the task. Their colon-named IDs, inputs, outputs,\nand provider behavior differ from the generic dot-named catalogue.\n\nRead the pinned implementation caveats before calling them. Two published\nschemas permit inputs that do not produce the intuitive runtime behavior unless\nthe application adds a stricter check.\n\nChoose composite or catalogue\n\n| Composite on this page | Related catalogue capability | Choose the composite when |\n\n| cap:chatwoot:message:create | cap:chatwoot:message.create | Text and public/private mode fit the strict connector input |\n| cap:chatwoot:conversation:status | cap:chatwoot:conversation.togglestatus | The desired status is exactly open, resolved, or pending |\n| cap:chatwoot:conversation:handoff | cap:chatwoot:conversation.assignment plus optional message operation | One governed intent assigns and optionally adds a private note |\n\nColons and dots are significant. Do not normalize either ID or reuse approval,\nAction, or idempotency evidence across them.\n\nComposite summary\n\n| Capability | Kind | Risk | Approval | Retry | Provider calls |\n\n| message:create | Tool | Medium | Required | Unsafe | 1 |\n| conversation:status | Tool | Medium | Required | Safe | 1 |\n| conversation:handoff | Tool | High | Required | Unsafe | 0–2 |\n\nAll three execute synchronously. None advertises async, streaming,\ncancellation, transaction, reconciliation, or rollback support.\n\nDiscovery and operation allowlist\n\nThe connector builds discovery by adding these three composites first and then\nthe admitted dot-named catalogue operations. The Chatwoot operations file\nfilters the 150-operation catalogue; it does not remove these composites from\nthe connector-generated manifest.\n\nTenant catalogue admission, routing, policy, and authorization still apply. A\nconnector-generated capability is not permission for every actor or tenant to\ninvoke it.\n\nSend a message\n\ncap:chatwoot:message:create publishes this input:\n\n| Field | Type | Published requirement | Meaning |\n\n| conversationid | String | Required | Conversation in the configured account |\n| content | String | Required | Message or private-note text |\n| private | Boolean | Optional; schema default false | false for public reply, true for private note |\n\nAdditional properties are rejected by the published schema. The caller cannot\nsupply the internal AIP action marker.\n\nSupply private explicitly\n\nThe published schema marks private optional, but the pinned invocation path\ndeserializes into a Boolean field without a serde default. A direct input that\nomits private can fail as connector.chatwoot.invalidaction.\n\nSend the field explicitly until source and schema agree:\n\n{\n  \"conversationid\": \"314\",\n  \"content\": \"Your request is now with the operations team.\",\n  \"private\": false\n}\n\nThis guidance avoids relying on a JSON Schema default as if it were applied by\nthe connector.\n\nProvider request\n\nThe connector posts to:\n\nPOST /api/v1/accounts/{configuredaccountid}/conversations/314/messages\n\nIt constructs a provider body with:\n• the supplied content and private values;\n• contentattributes.aip.connectorid set to chatwoot;\n• contentattributes.aip.loopprevention set to true;\n• contentattributes.aip.actionid set from the real AIP Action ID.\n\nThose attributes let inbound mapping suppress the connector's own message\necho. They do not prove external delivery or provider deduplication.\n\nConstructed result\n\nAfter a successful provider response, the connector discards the provider body\nand returns:\n\n{\n  \"conversationid\": \"314\",\n  \"sent\": true\n}\n\nsent: true means the provider request returned a successful HTTP status. It\ndoes not prove that a customer channel delivered or displayed the message.\n\nUpdate conversation status\n\ncap:chatwoot:conversation:status requires exactly:\n\n| Field | Type | Accepted values |\n\n| conversationid | Non-blank string | Conversation in the configured account |\n| status | String enum | open, resolved, pending |\n\nAdditional properties are rejected by the published schema.\n\nExample:\n\n{\n  \"conversationid\": \"314\",\n  \"status\": \"pending\"\n}\n\nThe connector posts:\n\nPOST /api/v1/accounts/{configuredaccountid}/conversations/314/togglestatus\n\nwith:\n\n{\n  \"status\": \"pending\"\n}\n\nAfter a successful provider response, it constructs:\n\n{\n  \"conversationid\": \"314\",\n  \"status\": \"pending\"\n}\n\nThis is the only Chatwoot mutation whose connector contract advertises safe\nretry. The provider call still receives no AIP idempotency header. Read current\nconversation state before a repeat and preserve the same governed intent.\n\nHandoff a conversation\n\ncap:chatwoot:conversation:handoff publishes:\n\n| Field | Type | Published requirement | Effect |\n\n| conversationid | String | Required | Selects the conversation |\n| assigneeid | String | Optional | Requests provider assignment |\n| note | String | Optional | Sends a private handoff note |\n\nAdditional properties are rejected by the schema.\n\nRequire an intent field\n\nThe published schema requires only conversationid. The pinned handler does\nnot reject an input with neither assigneeid nor note. It makes no provider\nrequest and still returns handoff: true.\n\nApplications should require at least one non-empty intent field before\napproval:\n\n{\n  \"conversationid\": \"314\",\n  \"assigneeid\": \"42\",\n  \"note\": \"Escalated with the approved incident context.\"\n}\n\nThis application check is stricter than the pinned capability schema. It\nprevents a successful no-op from being mistaken for a handoff.\n\nProvider sequence\n\nWhen assigneeid is present, the connector first posts:\n\nPOST /api/v1/accounts/{configuredaccountid}/conversations/314/assignments\n\nwith {\"assigneeid\":\"42\"}.\n\nWhen note is present, the connector then posts a private message through the\nsame message helper used by message:create. The note includes the AIP\nloop-prevention, connector, and Action markers.\n\nAfter all requested calls succeed, the connector constructs:\n\n{\n  \"conversationid\": \"314\",\n  \"handoff\": true\n}\n\nPartial-effect boundary\n\n| Requested fields | Provider sequence | Important failure boundary |\n\n| Assignee only | Assignment | A lost response can hide a completed assignment |\n| Note only | Private message | A lost response can hide a created note |\n| Assignee and note | Assignment, then private message | Note failure can leave assignment completed |\n| Neither | No provider request | Connector returns success without a handoff effect |\n\nThe two-call path is not atomic. It has no transaction, reconcile method, or\ncompensating capability. Recovery must inspect assignment and message state\nseparately.\n\nShared approval and idempotency contract\n\nAll three composites declare:\n\n| Contract field | Value |\n\n| Approval | Required; tenant-policy selector |\n| Approval validity | 900,000 ms |\n| Approval evidence | Reason, input snapshot, policy decision |\n| Idempotency | Required; external-account scope; 86,400,000 ms published TTL |\n| Data | Restricted; contains PII; redaction required |\n| Execution | Synchronous; no async, streaming, or cancellation support |\n| Compensation | Rollback not supported |\n| Expected latency hint | 3,000 ms |\n| Provider timeout hint | 30,000 ms |\n| Maximum queue-delay hint | 5,000 ms |\n\nThe AIP idempotency key remains part of governed execution, but these composite\nprovider requests do not add an Idempotency-Key header. They also do not add\nthe catalogue's X-AIP-Action-ID header.\n\nThe pinned composite invoke branches do not call the catalogue's\nrequiredidempotencykey check. Use the governed AIP execution path, which\nowns contract enforcement; do not treat direct connector invocation as an\nidempotency gate.\n\nMessage and handoff notes embed the Action ID inside provider content\nattributes. Status and assignment requests contain no equivalent Action field\nin their constructed bodies.\n\nDo not treat the AIP key as proof of Chatwoot deduplication. Preserve the Action\nand key for AIP settlement, then reconcile provider state before another call.\n\nSide effects\n\n| Composite | Declared side effects |\n\n| message:create | sendmessage, write, externalnetwork |\n| conversation:status | write, externalnetwork |\n| conversation:handoff | write, sendmessage, identity, externalnetwork |\n\nHandoff declares the broadest policy signal because it can change ownership\nand add a private message. Approval should match the actual optional fields,\nnot only the capability ID.\n\nFailures and recovery\n\n| Failure code | Typical cause | Safe response |\n\n| connector.chatwoot.invalidaction | Schema/runtime mismatch, invalid status, or malformed strict input | Correct input before any provider dispatch |\n| connector.chatwoot.authentication | Provider returned 401 or 403 | Verify account route and credential revision |\n| connector.chatwoot.remoterejected | Provider rejected input or current state | Correct authoritative state before a new Action |\n| connector.chatwoot.remotetemporary | Provider returned 429 or a server error | Read provider state; use status retry only within its contract |\n| connector.chatwoot.transport | Provider exchange failed | Treat the mutation outcome as uncertain |\n| connector.chatwoot.responsetoolarge | Provider response crossed the active bound | Reconcile state; do not enlarge the bound to force a repeat |\n\nEvery failure sets retryable: false and omits a provider request ID. The\nstatus capability's safe-retry contract is a separate policy signal.\n\nFor uncertain message creation, inspect the conversation for the Action marker\nbefore another call. For uncertain status, read the conversation and compare\nthe intended state. For uncertain handoff, inspect assignment first and then\nthe private note.\n\nIf assignment completed but note failed, do not repeat the entire handoff\nblindly. Decide whether a separately approved note-only Action is appropriate\nfor current state.\n\nVerify an integration\n\nRequire all of the following:\n• discovery returns the exact colon-named capability;\n• the operations allowlist is not mistaken for composite authorization;\n• private is present explicitly for message:create;\n• handoff contains at least one reviewed intent field;\n• the governed AIP path enforces the required idempotency contract;\n• approval binds every optional field and the intended external effect;\n• one Action ID, canonical input, and idempotency key are retained;\n• constructed output is not mistaken for raw provider output;\n• message delivery, status, assignment, and note state are verified through\n  authoritative Chatwoot reads;\n• loop-prevention attributes are retained for connector-originated messages;\n• partial or uncertain outcomes are reconciled instead of repeated.\n\nThese checks validate application handling. They do not establish customer\ndelivery, provider deduplication, atomic handoff, or live qualification.\n\nRelated documentation\n• Chatwoot capability index (README.md)\n• Conversations and messages (conversations-and-messages.md)\n• Run the Chatwoot connector quickstart (../getting-started/quickstart.md)\n• Approvals and policy (../../../concepts/approvals-and-policy.md)\n• Error reference (../../../reference/errors.md)\n"
  },
  "integrity": {
    "algorithm": "sha256",
    "sourceDigest": "f6da4ba23a90a7698137a8f7c7ae741fc62003a1d677312ee8385b5786eeb50f"
  }
}
