{
  "schemaVersion": "1.0",
  "title": "Run the Chatwoot connector quickstart",
  "description": "In this tutorial, you will start the repository's deterministic Chatwoot path, read its tenant-bound account, and create one conversation after an explicit approval. Both Actions travel from aipctl, through product-neutral aipd, to the stan",
  "canonical": "https://getaip.org/docs/connectors/chatwoot/getting-started/quickstart",
  "route": "/docs/connectors/chatwoot/getting-started/quickstart",
  "source": "docs/connectors/chatwoot/getting-started/quickstart.md",
  "protocol": "Agent Interoperability Protocol",
  "protocolVersion": "1.0",
  "section": "Connectors",
  "documentType": "Connector",
  "language": "en",
  "downloads": {
    "md": "/docs/download/connectors/chatwoot/getting-started/quickstart.md",
    "txt": "/docs/download/connectors/chatwoot/getting-started/quickstart.txt",
    "json": "/docs/download/connectors/chatwoot/getting-started/quickstart.json",
    "pdf": "/docs/download/connectors/chatwoot/getting-started/quickstart.pdf"
  },
  "content": {
    "format": "text/markdown",
    "markdown": "---\ntitle: Run the Chatwoot connector quickstart\ndescription: >-\n  Read one bound account and create one approval-governed conversation through\n  the deterministic standalone Chatwoot path\nkind: tutorial\naudience: application-developer\nappliesTo: \"1.x\"\nwritingStandard: \"aip-docs/1.0\"\nlastReviewedRevision: \"97be86e9efedf07ecf1783b03800f683f107fb04\"\nconnector: chatwoot\ncapabilityIds:\n  - cap:chatwoot:account.get\n  - cap:chatwoot:conversation.create\naliases:\n  - Chatwoot quickstart\n  - create a Chatwoot conversation\n---\n\n# Run the Chatwoot connector quickstart\n\nIn this tutorial, you will start the repository's deterministic Chatwoot path,\nread its tenant-bound account, and create one conversation after an explicit\napproval. Both Actions travel from `aipctl`, through product-neutral `aipd`,\nto the standalone `aip-host-chatwoot` process.\n\nThe provider is a controlled local fixture. You do not need a Chatwoot\ndeployment or credential, and the procedure does not call an external service.\nThis is a learning exercise, not a production deployment or qualification run.\n\n## What you will verify\n\nBy the end, you will have observed:\n\n- source-derived local images and connector release identities;\n- registry admission before the Chatwoot host starts;\n- one host bound to one AIP tenant and one configured Chatwoot account;\n- a signed, low-risk `cap:chatwoot:account.get` Action;\n- a `cap:chatwoot:conversation.create` Action held for approval;\n- a signed approval followed by one completed, idempotent result;\n- cleanup limited to the named tutorial Compose project.\n\nYou will not test a real Chatwoot deployment, webhook ingress, cancellation,\nrollback, gateway failover, or a production security boundary.\n\n## Prerequisites\n\nYou need:\n\n- a clean `aip-core` checkout at the reviewed source revision;\n- Docker Engine, Docker Compose v2, and BuildKit;\n- enough CPU, memory, and disk for the source-owned fleet images;\n- TCP port `18443` free on loopback;\n- a POSIX-compatible shell, `lsof`, `sed`, and `grep`;\n- authority to remove the named tutorial project after reviewing its state.\n\nRun every command from the `aip-core` repository root. Image preparation builds\nthe gateway, control plane, fixtures, and all product hosts, so the first run\ncan take substantially longer than a single native binary.\n\nThe Compose project name is fixed as `aip-connector-fleet-qualification`. Do\nnot continue if another investigation or retained qualification run owns that\nproject or its volumes.\n\n## 1. Verify the source boundary\n\nSet and verify the reviewed revision:\n\n```sh\nexport AIP_SOURCE_REVISION=97be86e9efedf07ecf1783b03800f683f107fb04\n\ntest \"$(git rev-parse HEAD)\" = \"$AIP_SOURCE_REVISION\"\ntest -z \"$(git status --porcelain)\"\n```\n\nBoth checks must exit with status `0`. Use a separate clean checkout if you\nhave local work; do not hide or discard changes to satisfy the preflight.\n\nConfirm that Docker and Compose are available:\n\n```sh\ndocker info >/dev/null\ndocker compose version\n```\n\nConfirm that the fixed edge port is unused:\n\n```sh\nif ! command -v lsof >/dev/null; then\n  echo \"lsof is required for the port preflight\" >&2\n  exit 1\nfi\n\nif lsof -nP -iTCP:18443 -sTCP:LISTEN | grep -q .; then\n  echo \"Port 18443 is already in use\" >&2\n  exit 1\nfi\n```\n\nExpected result: the port check prints nothing and exits with status `0`.\nIdentify an existing listener before changing it. This tutorial does not\nauthorize stopping an unrelated process.\n\n## 2. Prepare source-derived images\n\nRun the repository preparation script:\n\n```sh\n./deploy/connector-fleet/prepare.sh\ntest -s deploy/connector-fleet/.env.qualification\n```\n\nExpected result: preparation ends with `connector fleet preparation: PASS`.\nThe owner-only environment file records the source digest, image tag, image\nIDs, connector release ID, and release revision selected by Compose.\n\nThose values identify local fixture artifacts. They are not a production\nadmission package or a Chatwoot credential.\n\nDefine a helper that always selects the source-owned file, environment, and\nproduct profile:\n\n```sh\ncompose() {\n  docker compose \\\n    --profile products \\\n    --env-file deploy/connector-fleet/.env.qualification \\\n    -f deploy/connector-fleet/compose.yml \\\n    \"$@\"\n}\n```\n\nInspect the fixed project before starting it:\n\n```sh\ncompose ps --all\n```\n\nStop here if the command reveals resources you need to retain. Do not run\ncleanup merely because the project already exists.\n\n## 3. Start the Chatwoot path\n\nStart the Chatwoot host and the dependencies declared by the Compose graph:\n\n```sh\ncompose up --detach --wait --wait-timeout 300 chatwoot-acme-a\n```\n\nExpected result: the command exits with status `0`. The dependency graph starts\ndurable storage, identity and policy initialization, prerequisite admission\njobs, the lifecycle control plane, the TLS edge, two product-neutral gateway\nreplicas, the controlled product upstream, and `chatwoot-acme-a`.\n\nInspect one-shot and long-running services:\n\n```sh\ncompose ps --all\n```\n\nThe Chatwoot host, both gateways, control plane, edge, PostgreSQL, and product\nfixture should be running and healthy. Initialization, migration, grant, and\nadmission jobs may be exited with status `0`; that is their successful terminal\nstate.\n\nDo not bypass a failed admission job or start the host outside its declared\nidentity. Preserve the failed job's bounded logs and correct its source-owned\ninput.\n\n## 4. Read the bound Chatwoot account\n\nRun `aipctl` inside the isolated network with the generated client identity and\ntrust material:\n\n```sh\ncompose run --rm --no-deps \\\n  --entrypoint /usr/local/bin/aipctl \\\n  -e AIPCTL_NATIVE_PRINCIPAL_ID=service:aipctl:qualification-acme \\\n  -e AIPCTL_NATIVE_TRUST_DOMAIN=fleet.test \\\n  -e AIPCTL_NATIVE_SIGNING_SEED_FILE=/fleet-state/secrets/client-acme-signing-seed.hex \\\n  -e AIPCTL_NATIVE_PEER_DID_FILE=/fleet-state/public/gateway.did \\\n  -e AIPCTL_NATIVE_TLS_CA_FILE=/caddy-data/caddy/pki/authorities/local/root.crt \\\n  qualification action call \\\n  https://aipd.fleet.test:8443 \\\n  cap:chatwoot:account.get \\\n  --action-id act_chatwoot_quickstart_account_001 \\\n  --input '{}'\n```\n\nExpected result: the response identifies\n`act_chatwoot_quickstart_account_001`, names\n`cap:chatwoot:account.get`, reports a completed result, and contains fixture\naccount ID `42`.\n\nThe Action supplies no account identifier. The host inserts its configured\naccount ID into `GET /api/v1/accounts/42` and adds the connector-owned API\ntoken. The result therefore demonstrates the local account binding without\nexposing the token or allowing the caller to choose another account.\n\n## 5. Request a conversation\n\nCreate a private temporary directory for the tutorial responses:\n\n```sh\nEVIDENCE_DIR=$(mktemp -d \"${TMPDIR:-/tmp}/aip-chatwoot-quickstart.XXXXXX\")\nchmod 700 \"$EVIDENCE_DIR\"\nprintf 'Tutorial evidence: %s\\n' \"$EVIDENCE_DIR\"\n```\n\nKeep one Action ID, one idempotency key, and one exact input for the entire\nmutation:\n\n```sh\nACTION_ID=act_chatwoot_quickstart_conversation_001\nIDEMPOTENCY_KEY=chatwoot-quickstart-conversation-v1\nINPUT='{\"body\":{\"source_id\":\"quickstart-source\",\"inbox_id\":1,\"contact_id\":1}}'\nPENDING=\"$EVIDENCE_DIR/conversation-pending.json\"\n```\n\nSubmit the conversation request with the source-owned Chatwoot caller identity:\n\n```sh\ncompose run --rm --no-deps \\\n  --entrypoint /usr/local/bin/aipctl \\\n  -e AIPCTL_NATIVE_PRINCIPAL_ID=service:aipctl:qualification-chatwoot \\\n  -e AIPCTL_NATIVE_TRUST_DOMAIN=fleet.test \\\n  -e AIPCTL_NATIVE_SIGNING_SEED_FILE=/fleet-state/secrets/client-chatwoot-acme-signing-seed.hex \\\n  -e AIPCTL_NATIVE_PEER_DID_FILE=/fleet-state/public/gateway.did \\\n  -e AIPCTL_NATIVE_TLS_CA_FILE=/caddy-data/caddy/pki/authorities/local/root.crt \\\n  qualification action call \\\n  https://aipd.fleet.test:8443 \\\n  cap:chatwoot:conversation.create \\\n  --action-id \"$ACTION_ID\" \\\n  --idempotency-key \"$IDEMPOTENCY_KEY\" \\\n  --input \"$INPUT\" >\"$PENDING\"\n\ngrep -q '\"status\": \"pending_approval\"' \"$PENDING\"\n```\n\nExpected result: the command exits successfully, but the Action status is\n`pending_approval`. No provider request is authorized at this point.\n\nDo not change the input, Action ID, or idempotency key after this step. A\nChatwoot mutation is not retry-safe, and the connector does not implement\nrollback or provider cancellation.\n\n## 6. Approve the mutation\n\nExtract and validate the approval identity and policy hash returned for this\nexact Action:\n\n```sh\napproval_id=$(sed -n \\\n  's/^[[:space:]]*\"approval_id\": \"\\([^\"]*\\)\",\\{0,1\\}$/\\1/p' \\\n  \"$PENDING\" | head -n 1)\npolicy_hash=$(sed -n \\\n  's/^[[:space:]]*\"policy_hash\": \"\\([^\"]*\\)\",\\{0,1\\}$/\\1/p' \\\n  \"$PENDING\" | head -n 1)\n\ncase \"$approval_id\" in\n  appr_[A-Za-z0-9_]*) ;;\n  *) echo \"Unexpected approval ID\" >&2; exit 1 ;;\nesac\n\ntest \"${#policy_hash}\" -eq 64\nprintf '%s' \"$policy_hash\" | grep -Eq '^[0-9a-f]{64}$'\n```\n\nReview `$PENDING` before approving. It identifies the exact Action, input\nsnapshot, policy, and requested side effect. Stop if any value differs from the\nconversation request above.\n\nCreate one signed approval decision:\n\n```sh\ndecided_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)\ndecision=$(printf \\\n  '{\"approval_id\":\"%s\",\"decision\":\"approved\",\"approver\":{\"id\":\"human:qualification-approver\",\"kind\":\"human\"},\"decided_at\":\"%s\",\"reason\":\"Approved for the Chatwoot quickstart fixture\",\"constraints\":[],\"evidence\":[],\"decision_id\":\"decision:%s:approved\",\"policy_hash\":\"%s\",\"authority_path\":[]}' \\\n  \"$approval_id\" \"$decided_at\" \"$approval_id\" \"$policy_hash\")\n\ncompose run --rm --no-deps \\\n  --entrypoint /usr/local/bin/aipctl \\\n  -e AIPCTL_NATIVE_PRINCIPAL_ID=human:qualification-approver \\\n  -e AIPCTL_NATIVE_TRUST_DOMAIN=fleet.test \\\n  -e AIPCTL_NATIVE_SIGNING_SEED_FILE=/fleet-state/secrets/approval-acme-signing-seed.hex \\\n  -e AIPCTL_NATIVE_PEER_DID_FILE=/fleet-state/public/gateway.did \\\n  -e AIPCTL_NATIVE_TLS_CA_FILE=/caddy-data/caddy/pki/authorities/local/root.crt \\\n  qualification approval decide \\\n  https://aipd.fleet.test:8443 \\\n  \"$decision\" >\"$EVIDENCE_DIR/approval-decision.json\"\n\ngrep -q '\"kind\": \"aip.approval.granted\"' \\\n  \"$EVIDENCE_DIR/approval-decision.json\"\ngrep -q '\"kind\": \"aip.action.resumed_result\"' \\\n  \"$EVIDENCE_DIR/approval-decision.json\"\n```\n\nExpected result: the signed decision grants the pending approval and resumes\nthe same Action. Approval authorizes this fixture mutation; it does not make\nthe operation retry-safe.\n\n## 7. Read the completed conversation result\n\nRepeat the same Action with the same input and idempotency key:\n\n```sh\nCOMPLETED=\"$EVIDENCE_DIR/conversation-completed.json\"\n\ncompose run --rm --no-deps \\\n  --entrypoint /usr/local/bin/aipctl \\\n  -e AIPCTL_NATIVE_PRINCIPAL_ID=service:aipctl:qualification-chatwoot \\\n  -e AIPCTL_NATIVE_TRUST_DOMAIN=fleet.test \\\n  -e AIPCTL_NATIVE_SIGNING_SEED_FILE=/fleet-state/secrets/client-chatwoot-acme-signing-seed.hex \\\n  -e AIPCTL_NATIVE_PEER_DID_FILE=/fleet-state/public/gateway.did \\\n  -e AIPCTL_NATIVE_TLS_CA_FILE=/caddy-data/caddy/pki/authorities/local/root.crt \\\n  qualification action call \\\n  https://aipd.fleet.test:8443 \\\n  cap:chatwoot:conversation.create \\\n  --action-id \"$ACTION_ID\" \\\n  --idempotency-key \"$IDEMPOTENCY_KEY\" \\\n  --input \"$INPUT\" >\"$COMPLETED\"\n\ngrep -q '\"status\": \"completed\"' \"$COMPLETED\"\ngrep -q '\"id\": 9001' \"$COMPLETED\"\ngrep -q '\"status\": \"open\"' \"$COMPLETED\"\n```\n\nExpected result: the completed output contains the controlled conversation\nwith ID `9001` and status `open`. Reusing both identities retrieves the durable\nresult for the same intent. Do not generalize the fixture's fixed values to a\nreal Chatwoot deployment.\n\nIf the transport fails after approval and the result remains unknown, do not\nchange the Action ID or key and do not submit a new conversation. Preserve the\nresponse files and inspect durable Action state before deciding on recovery.\n\n## 8. Verify the tutorial outcome\n\nRequire the Chatwoot host and both gateway replicas to remain running:\n\n```sh\ntest \"$(compose ps --status running --services | \\\n  grep -cx 'chatwoot-acme-a')\" -eq 1\ntest \"$(compose ps --status running --services | \\\n  grep -Ec '^aipd(-b)?$')\" -eq 2\n```\n\nVerify the three expected state transitions from the retained responses:\n\n```sh\ngrep -q '\"status\": \"pending_approval\"' \"$PENDING\"\ngrep -q '\"kind\": \"aip.approval.granted\"' \\\n  \"$EVIDENCE_DIR/approval-decision.json\"\ngrep -q '\"status\": \"completed\"' \"$COMPLETED\"\n```\n\nAll checks must exit with status `0`. You have now completed one coherent\nlearning path: an admitted standalone host fixed the provider account, served\na read, held a mutation for approval, and returned one idempotent conversation\nresult.\n\n## Diagnose before cleanup\n\nIf a step fails, capture bounded state before changing the project:\n\n```sh\ncompose ps --all\ncompose logs --no-color --tail 200 \\\n  aipd aipd-b control-plane chatwoot-acme-a product-upstream\n```\n\n| Symptom | Check |\n|---|---|\n| Preparation cannot reach Docker | Repair the daemon, then repeat the source and project checks |\n| Port `18443` is occupied | Identify the owner; do not terminate it without confirming scope |\n| Admission exits nonzero | Read the failed job's bounded log; do not bypass its package or trust policy |\n| Chatwoot host is unhealthy | Check its declared identity, account, database, token file, and fixture |\n| Account read is rejected | Preserve the generated caller, signing key, peer DID, CA, and tenant |\n| Conversation stays unapproved | Verify the returned approval ID and policy hash before deciding |\n| Approval does not resume the Action | Preserve the pending and decision responses; do not create a replacement Action |\n| Completed result is absent | Reuse the exact Action ID, key, and input; treat provider outcome as unknown |\n\nThis tutorial does not authorize regenerating identities, editing credentials,\nweakening TLS, deleting the database during diagnosis, or starting the host\nmanually outside registry admission.\n\n## Stop and remove the tutorial project\n\nReview the temporary response files and the fixed Compose project first.\nRetain them if they are needed for an investigation.\n\nWhen neither contains evidence or state you need, remove only the named\nproject:\n\n```sh\ncompose down --volumes --remove-orphans --timeout 30\ntest -z \"$(compose ps --all --quiet)\"\n```\n\nThe first command permanently deletes that project's PostgreSQL state,\ngenerated keys, and retained Compose evidence. It does not prune unrelated\ncontainers, images, volumes, or build cache.\n\nDelete the separate temporary response directory only after deciding that it\nis no longer needed:\n\n```sh\ncase \"$EVIDENCE_DIR\" in\n  \"${TMPDIR:-/tmp}\"/aip-chatwoot-quickstart.*)\n    rm -rf -- \"$EVIDENCE_DIR\"\n    ;;\n  *)\n    echo \"Refusing unexpected evidence path: $EVIDENCE_DIR\" >&2\n    exit 1\n    ;;\nesac\n```\n\n## What to do next\n\n- Read the [Chatwoot connector overview](../README.md) to choose the next\n  capability family and understand the one-account boundary.\n- Use the [connector fleet quickstart](../../../getting-started/connector-fleet-quickstart.md)\n  for the shorter cross-connector learning path.\n- Read [Deploy the connector fleet](../../../guides/deploy-connector-fleet.md)\n  before replacing fixture identities with deployment-owned identities.\n- Use [connector fleet qualification](../../../testing/connector-fleet-qualification.md)\n  to evaluate failure, restart, isolation, and retained-evidence claims.\n- Read [conformance and qualification](../../../reference/conformance.md) before\n  describing an artifact as conformant, qualified, or live verified.\n",
    "text": "Run the Chatwoot connector quickstart\n\nIn this tutorial, you will start the repository's deterministic Chatwoot path,\nread its tenant-bound account, and create one conversation after an explicit\napproval. Both Actions travel from aipctl, through product-neutral aipd,\nto the standalone aip-host-chatwoot process.\n\nThe provider is a controlled local fixture. You do not need a Chatwoot\ndeployment or credential, and the procedure does not call an external service.\nThis is a learning exercise, not a production deployment or qualification run.\n\nWhat you will verify\n\nBy the end, you will have observed:\n• source-derived local images and connector release identities;\n• registry admission before the Chatwoot host starts;\n• one host bound to one AIP tenant and one configured Chatwoot account;\n• a signed, low-risk cap:chatwoot:account.get Action;\n• a cap:chatwoot:conversation.create Action held for approval;\n• a signed approval followed by one completed, idempotent result;\n• cleanup limited to the named tutorial Compose project.\n\nYou will not test a real Chatwoot deployment, webhook ingress, cancellation,\nrollback, gateway failover, or a production security boundary.\n\nPrerequisites\n\nYou need:\n• a clean aip-core checkout at the reviewed source revision;\n• Docker Engine, Docker Compose v2, and BuildKit;\n• enough CPU, memory, and disk for the source-owned fleet images;\n• TCP port 18443 free on loopback;\n• a POSIX-compatible shell, lsof, sed, and grep;\n• authority to remove the named tutorial project after reviewing its state.\n\nRun every command from the aip-core repository root. Image preparation builds\nthe gateway, control plane, fixtures, and all product hosts, so the first run\ncan take substantially longer than a single native binary.\n\nThe Compose project name is fixed as aip-connector-fleet-qualification. Do\nnot continue if another investigation or retained qualification run owns that\nproject or its volumes.\n1. Verify the source boundary\n\nSet and verify the reviewed revision:\n\nexport AIPSOURCEREVISION=97be86e9efedf07ecf1783b03800f683f107fb04\n\ntest \"$(git rev-parse HEAD)\" = \"$AIPSOURCEREVISION\"\ntest -z \"$(git status --porcelain)\"\n\nBoth checks must exit with status 0. Use a separate clean checkout if you\nhave local work; do not hide or discard changes to satisfy the preflight.\n\nConfirm that Docker and Compose are available:\n\ndocker info >/dev/null\ndocker compose version\n\nConfirm that the fixed edge port is unused:\n\nif ! command -v lsof >/dev/null; then\n  echo \"lsof is required for the port preflight\" >&2\n  exit 1\nfi\n\nif lsof -nP -iTCP:18443 -sTCP:LISTEN | grep -q .; then\n  echo \"Port 18443 is already in use\" >&2\n  exit 1\nfi\n\nExpected result: the port check prints nothing and exits with status 0.\nIdentify an existing listener before changing it. This tutorial does not\nauthorize stopping an unrelated process.\n2. Prepare source-derived images\n\nRun the repository preparation script:\n\n./deploy/connector-fleet/prepare.sh\ntest -s deploy/connector-fleet/.env.qualification\n\nExpected result: preparation ends with connector fleet preparation: PASS.\nThe owner-only environment file records the source digest, image tag, image\nIDs, connector release ID, and release revision selected by Compose.\n\nThose values identify local fixture artifacts. They are not a production\nadmission package or a Chatwoot credential.\n\nDefine a helper that always selects the source-owned file, environment, and\nproduct profile:\n\ncompose() {\n  docker compose \\\n    --profile products \\\n    --env-file deploy/connector-fleet/.env.qualification \\\n    -f deploy/connector-fleet/compose.yml \\\n    \"$@\"\n}\n\nInspect the fixed project before starting it:\n\ncompose ps --all\n\nStop here if the command reveals resources you need to retain. Do not run\ncleanup merely because the project already exists.\n3. Start the Chatwoot path\n\nStart the Chatwoot host and the dependencies declared by the Compose graph:\n\ncompose up --detach --wait --wait-timeout 300 chatwoot-acme-a\n\nExpected result: the command exits with status 0. The dependency graph starts\ndurable storage, identity and policy initialization, prerequisite admission\njobs, the lifecycle control plane, the TLS edge, two product-neutral gateway\nreplicas, the controlled product upstream, and chatwoot-acme-a.\n\nInspect one-shot and long-running services:\n\ncompose ps --all\n\nThe Chatwoot host, both gateways, control plane, edge, PostgreSQL, and product\nfixture should be running and healthy. Initialization, migration, grant, and\nadmission jobs may be exited with status 0; that is their successful terminal\nstate.\n\nDo not bypass a failed admission job or start the host outside its declared\nidentity. Preserve the failed job's bounded logs and correct its source-owned\ninput.\n4. Read the bound Chatwoot account\n\nRun aipctl inside the isolated network with the generated client identity and\ntrust material:\n\ncompose run --rm --no-deps \\\n  --entrypoint /usr/local/bin/aipctl \\\n  -e AIPCTLNATIVEPRINCIPALID=service:aipctl:qualification-acme \\\n  -e AIPCTLNATIVETRUSTDOMAIN=fleet.test \\\n  -e AIPCTLNATIVESIGNINGSEEDFILE=/fleet-state/secrets/client-acme-signing-seed.hex \\\n  -e AIPCTLNATIVEPEERDIDFILE=/fleet-state/public/gateway.did \\\n  -e AIPCTLNATIVETLSCAFILE=/caddy-data/caddy/pki/authorities/local/root.crt \\\n  qualification action call \\\n  https://aipd.fleet.test:8443 \\\n  cap:chatwoot:account.get \\\n  --action-id actchatwootquickstartaccount001 \\\n  --input '{}'\n\nExpected result: the response identifies\nactchatwootquickstartaccount001, names\ncap:chatwoot:account.get, reports a completed result, and contains fixture\naccount ID 42.\n\nThe Action supplies no account identifier. The host inserts its configured\naccount ID into GET /api/v1/accounts/42 and adds the connector-owned API\ntoken. The result therefore demonstrates the local account binding without\nexposing the token or allowing the caller to choose another account.\n5. Request a conversation\n\nCreate a private temporary directory for the tutorial responses:\n\nEVIDENCEDIR=$(mktemp -d \"${TMPDIR:-/tmp}/aip-chatwoot-quickstart.XXXXXX\")\nchmod 700 \"$EVIDENCEDIR\"\nprintf 'Tutorial evidence: %s\\n' \"$EVIDENCEDIR\"\n\nKeep one Action ID, one idempotency key, and one exact input for the entire\nmutation:\n\nACTIONID=actchatwootquickstartconversation001\nIDEMPOTENCYKEY=chatwoot-quickstart-conversation-v1\nINPUT='{\"body\":{\"sourceid\":\"quickstart-source\",\"inboxid\":1,\"contactid\":1}}'\nPENDING=\"$EVIDENCEDIR/conversation-pending.json\"\n\nSubmit the conversation request with the source-owned Chatwoot caller identity:\n\ncompose run --rm --no-deps \\\n  --entrypoint /usr/local/bin/aipctl \\\n  -e AIPCTLNATIVEPRINCIPALID=service:aipctl:qualification-chatwoot \\\n  -e AIPCTLNATIVETRUSTDOMAIN=fleet.test \\\n  -e AIPCTLNATIVESIGNINGSEEDFILE=/fleet-state/secrets/client-chatwoot-acme-signing-seed.hex \\\n  -e AIPCTLNATIVEPEERDIDFILE=/fleet-state/public/gateway.did \\\n  -e AIPCTLNATIVETLSCAFILE=/caddy-data/caddy/pki/authorities/local/root.crt \\\n  qualification action call \\\n  https://aipd.fleet.test:8443 \\\n  cap:chatwoot:conversation.create \\\n  --action-id \"$ACTIONID\" \\\n  --idempotency-key \"$IDEMPOTENCYKEY\" \\\n  --input \"$INPUT\" >\"$PENDING\"\n\ngrep -q '\"status\": \"pendingapproval\"' \"$PENDING\"\n\nExpected result: the command exits successfully, but the Action status is\npendingapproval. No provider request is authorized at this point.\n\nDo not change the input, Action ID, or idempotency key after this step. A\nChatwoot mutation is not retry-safe, and the connector does not implement\nrollback or provider cancellation.\n6. Approve the mutation\n\nExtract and validate the approval identity and policy hash returned for this\nexact Action:\n\napprovalid=$(sed -n \\\n  's/^[[:space:]]\"approvalid\": \"\\([^\"]\\)\",\\{0,1\\}$/\\1/p' \\\n  \"$PENDING\" | head -n 1)\npolicyhash=$(sed -n \\\n  's/^[[:space:]]\"policyhash\": \"\\([^\"]\\)\",\\{0,1\\}$/\\1/p' \\\n  \"$PENDING\" | head -n 1)\n\ncase \"$approvalid\" in\n  appr[A-Za-z0-9]) ;;\n  ) echo \"Unexpected approval ID\" >&2; exit 1 ;;\nesac\n\ntest \"${#policyhash}\" -eq 64\nprintf '%s' \"$policyhash\" | grep -Eq '^[0-9a-f]{64}$'\n\nReview $PENDING before approving. It identifies the exact Action, input\nsnapshot, policy, and requested side effect. Stop if any value differs from the\nconversation request above.\n\nCreate one signed approval decision:\n\ndecidedat=$(date -u +%Y-%m-%dT%H:%M:%SZ)\ndecision=$(printf \\\n  '{\"approvalid\":\"%s\",\"decision\":\"approved\",\"approver\":{\"id\":\"human:qualification-approver\",\"kind\":\"human\"},\"decidedat\":\"%s\",\"reason\":\"Approved for the Chatwoot quickstart fixture\",\"constraints\":[],\"evidence\":[],\"decisionid\":\"decision:%s:approved\",\"policyhash\":\"%s\",\"authoritypath\":[]}' \\\n  \"$approvalid\" \"$decidedat\" \"$approvalid\" \"$policyhash\")\n\ncompose run --rm --no-deps \\\n  --entrypoint /usr/local/bin/aipctl \\\n  -e AIPCTLNATIVEPRINCIPALID=human:qualification-approver \\\n  -e AIPCTLNATIVETRUSTDOMAIN=fleet.test \\\n  -e AIPCTLNATIVESIGNINGSEEDFILE=/fleet-state/secrets/approval-acme-signing-seed.hex \\\n  -e AIPCTLNATIVEPEERDIDFILE=/fleet-state/public/gateway.did \\\n  -e AIPCTLNATIVETLSCAFILE=/caddy-data/caddy/pki/authorities/local/root.crt \\\n  qualification approval decide \\\n  https://aipd.fleet.test:8443 \\\n  \"$decision\" >\"$EVIDENCEDIR/approval-decision.json\"\n\ngrep -q '\"kind\": \"aip.approval.granted\"' \\\n  \"$EVIDENCEDIR/approval-decision.json\"\ngrep -q '\"kind\": \"aip.action.resumedresult\"' \\\n  \"$EVIDENCEDIR/approval-decision.json\"\n\nExpected result: the signed decision grants the pending approval and resumes\nthe same Action. Approval authorizes this fixture mutation; it does not make\nthe operation retry-safe.\n7. Read the completed conversation result\n\nRepeat the same Action with the same input and idempotency key:\n\nCOMPLETED=\"$EVIDENCEDIR/conversation-completed.json\"\n\ncompose run --rm --no-deps \\\n  --entrypoint /usr/local/bin/aipctl \\\n  -e AIPCTLNATIVEPRINCIPALID=service:aipctl:qualification-chatwoot \\\n  -e AIPCTLNATIVETRUSTDOMAIN=fleet.test \\\n  -e AIPCTLNATIVESIGNINGSEEDFILE=/fleet-state/secrets/client-chatwoot-acme-signing-seed.hex \\\n  -e AIPCTLNATIVEPEERDIDFILE=/fleet-state/public/gateway.did \\\n  -e AIPCTLNATIVETLSCAFILE=/caddy-data/caddy/pki/authorities/local/root.crt \\\n  qualification action call \\\n  https://aipd.fleet.test:8443 \\\n  cap:chatwoot:conversation.create \\\n  --action-id \"$ACTIONID\" \\\n  --idempotency-key \"$IDEMPOTENCYKEY\" \\\n  --input \"$INPUT\" >\"$COMPLETED\"\n\ngrep -q '\"status\": \"completed\"' \"$COMPLETED\"\ngrep -q '\"id\": 9001' \"$COMPLETED\"\ngrep -q '\"status\": \"open\"' \"$COMPLETED\"\n\nExpected result: the completed output contains the controlled conversation\nwith ID 9001 and status open. Reusing both identities retrieves the durable\nresult for the same intent. Do not generalize the fixture's fixed values to a\nreal Chatwoot deployment.\n\nIf the transport fails after approval and the result remains unknown, do not\nchange the Action ID or key and do not submit a new conversation. Preserve the\nresponse files and inspect durable Action state before deciding on recovery.\n8. Verify the tutorial outcome\n\nRequire the Chatwoot host and both gateway replicas to remain running:\n\ntest \"$(compose ps --status running --services | \\\n  grep -cx 'chatwoot-acme-a')\" -eq 1\ntest \"$(compose ps --status running --services | \\\n  grep -Ec '^aipd(-b)?$')\" -eq 2\n\nVerify the three expected state transitions from the retained responses:\n\ngrep -q '\"status\": \"pendingapproval\"' \"$PENDING\"\ngrep -q '\"kind\": \"aip.approval.granted\"' \\\n  \"$EVIDENCEDIR/approval-decision.json\"\ngrep -q '\"status\": \"completed\"' \"$COMPLETED\"\n\nAll checks must exit with status 0. You have now completed one coherent\nlearning path: an admitted standalone host fixed the provider account, served\na read, held a mutation for approval, and returned one idempotent conversation\nresult.\n\nDiagnose before cleanup\n\nIf a step fails, capture bounded state before changing the project:\n\ncompose ps --all\ncompose logs --no-color --tail 200 \\\n  aipd aipd-b control-plane chatwoot-acme-a product-upstream\n\n| Symptom | Check |\n\n| Preparation cannot reach Docker | Repair the daemon, then repeat the source and project checks |\n| Port 18443 is occupied | Identify the owner; do not terminate it without confirming scope |\n| Admission exits nonzero | Read the failed job's bounded log; do not bypass its package or trust policy |\n| Chatwoot host is unhealthy | Check its declared identity, account, database, token file, and fixture |\n| Account read is rejected | Preserve the generated caller, signing key, peer DID, CA, and tenant |\n| Conversation stays unapproved | Verify the returned approval ID and policy hash before deciding |\n| Approval does not resume the Action | Preserve the pending and decision responses; do not create a replacement Action |\n| Completed result is absent | Reuse the exact Action ID, key, and input; treat provider outcome as unknown |\n\nThis tutorial does not authorize regenerating identities, editing credentials,\nweakening TLS, deleting the database during diagnosis, or starting the host\nmanually outside registry admission.\n\nStop and remove the tutorial project\n\nReview the temporary response files and the fixed Compose project first.\nRetain them if they are needed for an investigation.\n\nWhen neither contains evidence or state you need, remove only the named\nproject:\n\ncompose down --volumes --remove-orphans --timeout 30\ntest -z \"$(compose ps --all --quiet)\"\n\nThe first command permanently deletes that project's PostgreSQL state,\ngenerated keys, and retained Compose evidence. It does not prune unrelated\ncontainers, images, volumes, or build cache.\n\nDelete the separate temporary response directory only after deciding that it\nis no longer needed:\n\ncase \"$EVIDENCEDIR\" in\n  \"${TMPDIR:-/tmp}\"/aip-chatwoot-quickstart.)\n    rm -rf -- \"$EVIDENCEDIR\"\n    ;;\n  )\n    echo \"Refusing unexpected evidence path: $EVIDENCEDIR\" >&2\n    exit 1\n    ;;\nesac\n\nWhat to do next\n• Read the Chatwoot connector overview (../README.md) to choose the next\n  capability family and understand the one-account boundary.\n• Use the connector fleet quickstart (../../../getting-started/connector-fleet-quickstart.md)\n  for the shorter cross-connector learning path.\n• Read Deploy the connector fleet (../../../guides/deploy-connector-fleet.md)\n  before replacing fixture identities with deployment-owned identities.\n• Use connector fleet qualification (../../../testing/connector-fleet-qualification.md)\n  to evaluate failure, restart, isolation, and retained-evidence claims.\n• Read conformance and qualification (../../../reference/conformance.md) before\n  describing an artifact as conformant, qualified, or live verified.\n"
  },
  "integrity": {
    "algorithm": "sha256",
    "sourceDigest": "ff26f47e2553b31fd957b36ebb8e98ee7effd779562b870de6bafa1cdc1d8beb"
  }
}
