A full-featured PHP SDK for the Chatwoot API with a fluent interface, covering all three API families: Application, Client, and Platform.
Tested against Chatwoot 4.11.x · PHP 8.1 · 8.2 · 8.3 · 8.4
composer require ramiroestrella/chatwoot-php-sdkuse RamiroEstrella\ChatwootPhpSdk\ChatwootClient;
$chatwoot = new ChatwootClient(
baseUrl: 'https://app.chatwoot.com',
apiToken: 'your_api_access_token',
accountId: 1
);All API responses are returned as typed DTO objects instead of plain arrays. Every property is nullable and type-safe — the SDK handles all coercion and response-unwrapping automatically, including Chatwoot 4.x's nested { "payload": { "contact": {...} } } envelope format.
$contact = $chatwoot->application()->contacts()->show(42);
echo $contact->name; // string
echo $contact->email; // string
echo $contact->blocked; // bool
echo $contact->id; // int| DTO | Used by |
|---|---|
ContactDTO |
contacts |
ConversationDTO |
conversations |
ConversationMetaDTO |
nested inside ConversationDTO |
MessageDTO |
messages |
AgentDTO |
agents, team members, inbox members, platform users |
InboxDTO |
inboxes |
TeamDTO |
teams |
WebhookDTO |
webhooks |
CannedResponseDTO |
canned responses |
AccountDTO |
account, platform accounts |
AgentBotDTO |
agent bots |
AutomationRuleDTO |
automation rules |
AuditLogDTO |
audit logs |
CustomAttributeDTO |
custom attributes |
ContactInboxDTO |
contact inbox links |
All DTOs extend BaseDTO which provides:
$dto = ContactDTO::fromArray($responseArray); // hydrate from API response
$dtos = ContactDTO::collect($arrayOfArrays); // hydrate a collection → ContactDTO[]
$dto->toArray();
$dto->toArray(excludeNull: true);List endpoints return a typed collection object:
$contacts = $chatwoot->application()->contacts()->list(['page' => 1]);
$contacts->items; // ContactDTO[]
$contacts->count; // total records across all pages
$contacts->currentPage; // current page number
$contacts->isEmpty();
$contacts->first(); // ContactDTO|null
$contacts->map(fn($c) => $c->email);
$contacts->filter(fn($c) => $c->blocked === false);| Collection | Used by |
|---|---|
ContactCollection |
contacts list, search, filter |
ConversationCollection |
conversations list, filter, contact conversations |
MessageCollection |
messages list |
Properties with a known set of values use PHP backed enums for full IDE autocomplete and type safety.
| Enum | Values |
|---|---|
AgentRole |
Agent, Administrator |
AgentAvailabilityStatus |
Available, Busy, Offline |
ConversationStatus |
Open, Resolved, Pending, Snoozed |
ConversationPriority |
Low, Medium, High, Critical |
MessageType (int) |
Incoming=0, Outgoing=1, Activity=2, Template=3 |
MessageStatus |
Sent, Delivered, Read, Failed |
MessageContentType |
Text, InputEmail, Cards, InputSelect, Form, Article |
CustomAttributeModel (int) |
Conversation=0, Contact=1 |
use RamiroEstrella\ChatwootPhpSdk\Enums\ConversationStatus;
use RamiroEstrella\ChatwootPhpSdk\Enums\ConversationPriority;
$conv = $chatwoot->application()->conversations()->show($id);
$conv->status; // ConversationStatus enum
$conv->status->value; // 'open'
$conv->status === ConversationStatus::Open; // true
$conv->priority?->value; // 'high'
// Methods accept both enums and plain strings
$chatwoot->application()->conversations()->toggleStatus($id, ConversationStatus::Resolved);
$chatwoot->application()->conversations()->toggleStatus($id, 'resolved'); // also works| Method | API | Auth | Availability |
|---|---|---|---|
$chatwoot->application() |
Application API | api_access_token |
Cloud + Self-hosted |
$chatwoot->client($inboxIdentifier) |
Client API | inbox identifier string | Cloud + Self-hosted |
$chatwoot->platform($platformToken) |
Platform API | Platform App token | Self-hosted only |
Used for agent/admin operations. Get your token from Profile Settings → Access Token.
$account = $chatwoot->application()->account()->show();
$account = $chatwoot->application()->account()->update(['name' => 'My Support Team']);$profile = $chatwoot->application()->profile()->get();
$chatwoot->application()->profile()->update(['display_name' => 'Agent Smith', 'availability' => 'busy']);// List → ContactCollection
$contacts = $chatwoot->application()->contacts()->list(['page' => 1]);
// CRUD → ContactDTO
$contact = $chatwoot->application()->contacts()->create(['name' => 'Bob', 'email' => 'bob@example.com']);
$contact = $chatwoot->application()->contacts()->show($contactId);
$contact = $chatwoot->application()->contacts()->update($contactId, ['name' => 'Robert']);
$chatwoot->application()->contacts()->delete($contactId);
// Search / filter → ContactCollection
$results = $chatwoot->application()->contacts()->search('bob@example.com');
$filtered = $chatwoot->application()->contacts()->filter(['payload' => [...]]);
// Conversations → ConversationCollection
$convos = $chatwoot->application()->contacts()->conversations($contactId);
// Link to inbox → ContactInboxDTO (source_id is the contact identifier for Client API)
$contactInbox = $chatwoot->application()->contacts()->createContactInbox($contactId, $inboxId);
echo $contactInbox->source_id;
// Contactable inboxes → ContactInboxDTO[]
$inboxes = $chatwoot->application()->contacts()->contactableInboxes($contactId);
// Merge → ContactDTO
$merged = $chatwoot->application()->contacts()->merge($parentId, $childId);// List → ConversationCollection
$conversations = $chatwoot->application()->conversations()->list([
'status' => 'open',
'inbox_id' => 3,
'page' => 1,
]);
// Create → ConversationDTO
$conversation = $chatwoot->application()->conversations()->create([
'source_id' => $contactInbox->source_id,
'inbox_id' => 3,
]);
// Show → ConversationDTO
$conv = $chatwoot->application()->conversations()->show($conversationId);
echo $conv->status->value; // 'open' or 'pending' (depends on inbox config)
foreach ($conv->messages as $msg) { echo $msg->content; }
// Update → ConversationDTO
$conv = $chatwoot->application()->conversations()->update($conversationId, [
'assignee_id' => $agentId,
]);
// Toggle status → ConversationDTO (re-fetches after toggle)
$conv = $chatwoot->application()->conversations()->toggleStatus($conversationId, ConversationStatus::Resolved);
$conv = $chatwoot->application()->conversations()->toggleStatus($conversationId, 'snoozed', time() + 3600);
// Toggle priority → ConversationDTO (re-fetches after toggle)
$conv = $chatwoot->application()->conversations()->togglePriority($conversationId, ConversationPriority::High);
$conv = $chatwoot->application()->conversations()->togglePriority($conversationId, null); // unset
// Labels
$chatwoot->application()->conversations()->addLabels($conversationId, ['vip', 'billing']); // string[]
$chatwoot->application()->conversations()->listLabels($conversationId); // string[]
// Custom attributes → ConversationDTO
$chatwoot->application()->conversations()->updateCustomAttributes($conversationId, ['order_id' => 'ORD-1']);
// Other
$chatwoot->application()->conversations()->toggleTypingStatus($conversationId, 'on');
$chatwoot->application()->conversations()->counts();
$chatwoot->application()->conversations()->filter($payload, page: 1); // ConversationCollectionNote: New conversations may start as
pendingdepending on your inbox configuration in Chatwoot 4.x.
Note:
toggleStatus()andtogglePriority()both re-fetch the full conversation after toggling, because Chatwoot 4.x returns only{ success: true, current_status: "..." }from those endpoints — not a full conversation object.
// List → MessageCollection
$messages = $chatwoot->application()->messages()->list($conversationId);
foreach ($messages->items as $msg) {
echo $msg->content . ' (type: ' . $msg->message_type->value . ')';
}
// Send text → MessageDTO
$msg = $chatwoot->application()->messages()->sendText($conversationId, 'Hello there!');
// Send private note → MessageDTO
$msg = $chatwoot->application()->messages()->sendPrivateNote($conversationId, 'Internal note');
// Send WhatsApp template → MessageDTO
$msg = $chatwoot->application()->messages()->sendWhatsAppTemplate(
$conversationId,
'Your order {{1}} is confirmed.',
[
'name' => 'order_confirmation',
'category' => 'MARKETING',
'language' => 'en',
'processed_params' => ['body' => ['1' => '12345']],
]
);
// Full create → MessageDTO
$msg = $chatwoot->application()->messages()->create($conversationId, [
'content' => 'Hello!',
'message_type' => 'outgoing',
'private' => false,
]);
$chatwoot->application()->messages()->delete($conversationId, $messageId);$agents = $chatwoot->application()->agents()->list(); // AgentDTO[]
$agent = $chatwoot->application()->agents()->create(['name' => 'Alice', 'email' => 'alice@example.com', 'role' => 'agent']);
$agent = $chatwoot->application()->agents()->update($agentId, ['role' => 'administrator']);
$chatwoot->application()->agents()->delete($agentId);$inboxes = $chatwoot->application()->inboxes()->list(); // InboxDTO[]
$inbox = $chatwoot->application()->inboxes()->show($inboxId);
$inbox = $chatwoot->application()->inboxes()->create(['name' => 'Support', 'channel_type' => 'Channel::Api']);
$inbox = $chatwoot->application()->inboxes()->update($inboxId, ['name' => 'New Name']);
// Agents → AgentDTO[]
$agents = $chatwoot->application()->inboxes()->listAgents($inboxId);
$agents = $chatwoot->application()->inboxes()->addAgents($inboxId, [$agentId1, $agentId2]);
$agents = $chatwoot->application()->inboxes()->updateAgents($inboxId, [$agentId1]);
$chatwoot->application()->inboxes()->removeAgent($inboxId, $agentId);
// Agent bot
$bot = $chatwoot->application()->inboxes()->showAgentBot($inboxId); // AgentBotDTO|null
$chatwoot->application()->inboxes()->setAgentBot($inboxId, $botId);
$chatwoot->application()->inboxes()->setAgentBot($inboxId, null); // remove$teams = $chatwoot->application()->teams()->list();
$team = $chatwoot->application()->teams()->create('Support Team', 'Handles all support');
$team = $chatwoot->application()->teams()->show($teamId);
$team = $chatwoot->application()->teams()->update($teamId, ['name' => 'New Name']);
$chatwoot->application()->teams()->delete($teamId);
// Members → AgentDTO[]
$agents = $chatwoot->application()->teams()->listAgents($teamId);
$agents = $chatwoot->application()->teams()->addAgents($teamId, [$agentId]);
$agents = $chatwoot->application()->teams()->updateAgents($teamId, [$agentId]);
$chatwoot->application()->teams()->removeAgents($teamId, [$agentId]);use RamiroEstrella\ChatwootPhpSdk\Application\Resources\WebhooksResource;
$webhooks = $chatwoot->application()->webhooks()->list(); // WebhookDTO[]
$webhook = $chatwoot->application()->webhooks()->create(
'https://your-server.com/hook',
[WebhooksResource::EVENT_CONVERSATION_CREATED, WebhooksResource::EVENT_MESSAGE_CREATED],
'My Webhook' // optional name
);
$webhook = $chatwoot->application()->webhooks()->update($webhookId, 'https://new-url.com', [
WebhooksResource::EVENT_CONTACT_CREATED,
]);
$chatwoot->application()->webhooks()->delete($webhookId);Available event constants: EVENT_CONVERSATION_CREATED, EVENT_CONVERSATION_STATUS_CHANGED, EVENT_CONVERSATION_UPDATED, EVENT_CONTACT_CREATED, EVENT_CONTACT_UPDATED, EVENT_MESSAGE_CREATED, EVENT_MESSAGE_UPDATED, EVENT_WEBWIDGET_TRIGGERED.
$responses = $chatwoot->application()->cannedResponses()->list('greeting'); // CannedResponseDTO[]
$cr = $chatwoot->application()->cannedResponses()->create('hi', 'Hello! How can I help?');
$cr = $chatwoot->application()->cannedResponses()->update($id, ['content' => 'Updated text']);
$chatwoot->application()->cannedResponses()->delete($id);// 0 = conversation attributes, 1 = contact attributes
$attrs = $chatwoot->application()->customAttributes()->list(0); // CustomAttributeDTO[]
$attr = $chatwoot->application()->customAttributes()->create([...]);
$attr = $chatwoot->application()->customAttributes()->show($id);
$attr = $chatwoot->application()->customAttributes()->update($id, [...]);
$chatwoot->application()->customAttributes()->delete($id);$rules = $chatwoot->application()->automationRules()->list(); // AutomationRuleDTO[]
$rule = $chatwoot->application()->automationRules()->create([...]);
$rule = $chatwoot->application()->automationRules()->show($id);
$rule = $chatwoot->application()->automationRules()->update($id, [...]);
$chatwoot->application()->automationRules()->delete($id);$logs = $chatwoot->application()->auditLogs()->list(page: 1); // AuditLogDTO[]
foreach ($logs as $log) {
echo $log->action . ' by ' . $log->username;
}$chatwoot->application()->conversationAssignments()->getAssignee($conversationId);
$chatwoot->application()->conversationAssignments()->assignAgent($conversationId, $agentId);
$chatwoot->application()->conversationAssignments()->unassignAgent($conversationId);
$chatwoot->application()->conversationAssignments()->getParticipants($conversationId);
$chatwoot->application()->conversationAssignments()->addParticipants($conversationId, [$agentId]);
$chatwoot->application()->conversationAssignments()->updateParticipants($conversationId, [$agentId]);
$chatwoot->application()->conversationAssignments()->removeParticipants($conversationId, [$agentId]);$labels = $chatwoot->application()->contactLabels()->list($contactId);
$chatwoot->application()->contactLabels()->update($contactId, ['vip', 'enterprise']);$bots = $chatwoot->application()->agentBots()->list(); // AgentBotDTO[]
$bot = $chatwoot->application()->agentBots()->create(['name' => 'My Bot', 'outgoing_url' => 'https://...']);
$bot = $chatwoot->application()->agentBots()->show($id);
$bot = $chatwoot->application()->agentBots()->update($id, ['name' => 'Updated']);
$chatwoot->application()->agentBots()->delete($id);$portals = $chatwoot->application()->helpCenter()->listPortals();
$portal = $chatwoot->application()->helpCenter()->createPortal(['slug' => 'help', 'name' => 'Help Center']);
$portal = $chatwoot->application()->helpCenter()->showPortal('help');
$portal = $chatwoot->application()->helpCenter()->updatePortal('help', ['name' => 'New Name']);
$articles = $chatwoot->application()->helpCenter()->listArticles('help', ['locale' => 'en', 'page' => 1]);
$article = $chatwoot->application()->helpCenter()->createArticle('help', [
'title' => 'Getting Started',
'content' => '<p>Welcome!</p>',
'locale' => 'en',
'status' => 'published',
]);
$article = $chatwoot->application()->helpCenter()->showArticle('help', $articleId);
$article = $chatwoot->application()->helpCenter()->updateArticle('help', $articleId, ['title' => 'New Title']);
$chatwoot->application()->helpCenter()->deleteArticle('help', $articleId);$filters = $chatwoot->application()->customFilters()->list('conversation');
$filter = $chatwoot->application()->customFilters()->create([
'name' => 'Open VIP',
'filter_type' => 'conversation',
'query' => ['payload' => [...]],
]);
$filter = $chatwoot->application()->customFilters()->show($id);
$filter = $chatwoot->application()->customFilters()->update($id, ['name' => 'Updated']);
$chatwoot->application()->customFilters()->delete($id);$apps = $chatwoot->application()->integrations()->list();
$hook = $chatwoot->application()->integrations()->createHook(['app_id' => 'slack', 'url' => 'https://...']);
$hook = $chatwoot->application()->integrations()->updateHook($hookId, ['url' => 'https://new-url.com']);
$chatwoot->application()->integrations()->deleteHook($hookId);// V1
$chatwoot->application()->reports()->get(['metric' => 'account', 'type' => 'account', 'since' => $ts, 'until' => $ts]);
$chatwoot->application()->reports()->summary(['type' => 'account', 'since' => $ts, 'until' => $ts]);
// V2 (Chatwoot 4.x — requires since/until params)
$chatwoot->application()->reports()->accountConversationMetrics(['since' => $ts, 'until' => $ts]);
$chatwoot->application()->reports()->agentConversationMetrics();
$chatwoot->application()->reports()->conversationsByChannel(['since' => $ts, 'until' => $ts]);
$chatwoot->application()->reports()->conversationsByInbox();
$chatwoot->application()->reports()->conversationsByTeam();
$chatwoot->application()->reports()->firstResponseTimeDistribution();
$chatwoot->application()->reports()->outgoingMessageCounts();For building custom chat interfaces for end-users. No agent token needed.
Important: The inbox must be a Website (web widget) type. Email, API, and phone channel inboxes will return 404 on these endpoints.
Finding your inbox identifier: Go to Settings → Inboxes → (your web widget inbox) → Collaboration tab. Copy the long hex string — this is different from the numeric inbox ID.
// Step 1: get a source_id for the contact via Application API
$contactInbox = $chatwoot->application()->contacts()->createContactInbox($contactId, $inboxId);
$sourceId = $contactInbox->source_id;
// Step 2: use the Client API with the inbox identifier string
$client = $chatwoot->client('abc123def456...');
// Contacts
$contact = $client->contacts()->create(['name' => 'Alice', 'email' => 'alice@example.com']);
$client->contacts()->get($sourceId);
$client->contacts()->update($sourceId, ['name' => 'Alice Updated']);
// Conversations
$conversation = $client->conversations($sourceId)->create();
$client->conversations($sourceId)->list();
$client->conversations($sourceId)->get($conversationId);
$client->conversations($sourceId)->resolve($conversationId);
$client->conversations($sourceId)->toggleTyping($conversationId, 'on');
$client->conversations($sourceId)->updateLastSeen($conversationId);
// Messages
$client->messages($sourceId)->list($conversationId);
$client->messages($sourceId)->send($conversationId, 'Hello, I need help!');For installation-level management. Self-hosted only.
Get a token at https://your-domain.com/super_admin → Platform Apps → New → copy Access Token.
$platform = $chatwoot->platform('your_platform_app_token');
// Accounts → AccountDTO
$account = $platform->accounts()->create(['name' => 'ACME Corp']);
$account = $platform->accounts()->show($accountId);
$account = $platform->accounts()->update($accountId, ['name' => 'Updated']);
$platform->accounts()->delete($accountId);
// Users → AgentDTO
$user = $platform->users()->create([
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => 'securePass123!',
'password_confirmation' => 'securePass123!',
]);
$user = $platform->users()->show($userId);
$user = $platform->users()->update($userId, ['name' => 'John Smith']);
$loginUrl = $platform->users()->getLoginUrl($userId); // returns SSO URL
$platform->users()->delete($userId);
// Account Users
$members = $platform->accountUsers()->list($accountId);
$member = $platform->accountUsers()->create($accountId, $userId, 'agent');
$platform->accountUsers()->delete($accountId, $userId);
// Platform Agent Bots → AgentBotDTO
$bots = $platform->agentBots()->list();
$bot = $platform->agentBots()->create(['name' => 'My Bot']);
$bot = $platform->agentBots()->show($botId);
$bot = $platform->agentBots()->update($botId, ['name' => 'Updated']);
$platform->agentBots()->delete($botId);use RamiroEstrella\ChatwootPhpSdk\Exceptions\AuthenticationException;
use RamiroEstrella\ChatwootPhpSdk\Exceptions\NotFoundException;
use RamiroEstrella\ChatwootPhpSdk\Exceptions\ValidationException;
use RamiroEstrella\ChatwootPhpSdk\Exceptions\ApiException;
try {
$contact = $chatwoot->application()->contacts()->show(999999);
} catch (AuthenticationException $e) {
// 401 — invalid or missing API token
} catch (NotFoundException $e) {
// 404 — resource not found
} catch (ValidationException $e) {
// 422 — validation failed
print_r($e->getErrors());
} catch (ApiException $e) {
// any other API error
echo "Error [{$e->getCode()}]: " . $e->getMessage();
}Pass Guzzle options directly via the options array:
$chatwoot = new ChatwootClient(
baseUrl: 'https://chatwoot.mycompany.com',
apiToken: 'token',
accountId: 1,
options: [
'timeout' => 60,
'verify' => false, // disable SSL verification
'proxy' => 'http://proxy.example.com:8080',
]
);The SDK type-hints against HttpClientInterface, so you can inject your own HTTP implementation for logging, retries, or testing:
use RamiroEstrella\ChatwootPhpSdk\Http\HttpClientInterface;
class LoggingHttpClient implements HttpClientInterface { ... }
$chatwoot = new ChatwootClient(baseUrl: '...', apiToken: '...', accountId: 1);
$chatwoot->setHttpClient(new LoggingHttpClient());Unit tests use PHPUnit mocks and make no real HTTP calls. They run entirely offline and are safe to run at any time.
# Install dependencies
composer install
# Run all unit tests
./vendor/bin/phpunit --testsuite Unit
# With descriptive test names
./vendor/bin/phpunit --testsuite Unit --testdox
# Run a specific resource's tests
./vendor/bin/phpunit --testsuite Unit --filter ContactsResourceTest --testdox
./vendor/bin/phpunit --testsuite Unit --filter ConversationsResourceTest --testdoxExpected result: 263 tests, 522 assertions, all passing.
Integration tests hit a real Chatwoot instance. They create real resources, assert against live API responses, and clean up after themselves using finally blocks — your instance will not be left with test data.
Step 1 — Copy and fill in the env file:
cp .env.integration.example .env.integrationEdit .env.integration with your values:
# Required for all integration tests
CHATWOOT_BASE_URL=https://chat.example.com
CHATWOOT_API_TOKEN=your_agent_api_access_token
CHATWOOT_ACCOUNT_ID=1
# Required for conversation and assignment tests
# Find: Settings → Inboxes → [your inbox] → edit → numeric ID in the URL
CHATWOOT_INBOX_ID=3
# Required for Client API tests — must be a Website (web widget) inbox
# Find: Settings → Inboxes → [web widget inbox] → Collaboration tab → Inbox Identifier
CHATWOOT_INBOX_IDENTIFIER=your_inbox_identifier_string
# Required for Platform API tests (self-hosted only)
# Create: https://your-domain.com/super_admin → Platform Apps → New
CHATWOOT_PLATFORM_TOKEN=your_platform_app_tokenStep 2 — Load env vars and run:
set -a && source .env.integration && set +a
# Run all integration tests
./vendor/bin/phpunit --testsuite Integration --testdox
# Run a specific test class
./vendor/bin/phpunit --testsuite Integration --filter ApplicationApiTest --testdox
./vendor/bin/phpunit --testsuite Integration --filter ClientApiTest --testdox
./vendor/bin/phpunit --testsuite Integration --filter PlatformApiTest --testdoxRun unit + integration together:
set -a && source .env.integration && set +a
./vendor/bin/phpunit --testdox| Test class | Covers |
|---|---|
ApplicationApiTest |
Account, profile, agents, contacts (CRUD + search + filter + merge), inboxes, conversations (status, priority, labels, messages, assignments), teams, webhooks, canned responses, custom attributes, audit logs, reports (v1 + v2), contact labels, automation rules |
ClientApiTest |
Contact lifecycle, conversation lifecycle (create/list/resolve/typing), messages — all via the public Client API |
PlatformApiTest |
Platform accounts (CRUD), platform users (CRUD + SSO URL), account user membership, platform agent bots |
- Tests that require
CHATWOOT_INBOX_ID,CHATWOOT_INBOX_IDENTIFIER, orCHATWOOT_PLATFORM_TOKENskip gracefully if the env var is not set. - The Client API tests skip if the inbox identifier points to a non-widget inbox type.
- Platform agent bots skip if the endpoint returns 500 (known bug in some Chatwoot 4.x builds).
This SDK was built and tested against Chatwoot 4.11.2 and handles several 4.x-specific behaviors transparently:
- Nested response envelopes — single-resource endpoints return
{ "payload": { "contact": {...} } }. The SDK unwraps these automatically. - Webhook list shape — the list endpoint returns
{ "payload": { "webhooks": [...] } }(nested under a key), while create/update return{ "payload": { "webhook": {...} } }. Both are handled. - Toggle endpoints —
toggle_statusandtoggle_priorityreturn only{ success: true, current_status: "..." }, not a full conversation. The SDK re-fetches the conversation automatically so the return value is always a completeConversationDTO. - Pending conversations — new conversations may start as
pendinginstead ofopendepending on inbox configuration. - Team name casing — Chatwoot lowercases team names server-side.
- Reports v2 — the v2 report endpoints require
since/untilepoch timestamp parameters.
src/
├── ChatwootClient.php
├── Http/
│ ├── HttpClient.php
│ └── HttpClientInterface.php
├── Exceptions/
│ ├── ChatwootException.php
│ ├── ApiException.php
│ ├── AuthenticationException.php
│ ├── NotFoundException.php
│ └── ValidationException.php
├── Enums/
│ ├── AgentAvailabilityStatus.php
│ ├── AgentRole.php
│ ├── ConversationPriority.php
│ ├── ConversationStatus.php
│ ├── CustomAttributeModel.php
│ ├── MessageContentType.php
│ ├── MessageStatus.php
│ └── MessageType.php
├── DTO/
│ ├── BaseDTO.php
│ ├── AccountDTO.php ├── AgentBotDTO.php
│ ├── AgentDTO.php ├── AuditLogDTO.php
│ ├── AutomationRuleDTO.php ├── CannedResponseDTO.php
│ ├── ContactDTO.php ├── ContactInboxDTO.php
│ ├── ConversationDTO.php ├── ConversationMetaDTO.php
│ ├── CustomAttributeDTO.php ├── InboxDTO.php
│ ├── MessageDTO.php ├── TeamDTO.php
│ ├── WebhookDTO.php
│ └── Collections/
│ ├── PaginatedCollection.php
│ ├── ContactCollection.php
│ ├── ConversationCollection.php
│ └── MessageCollection.php
├── Application/
│ ├── ApplicationClient.php
│ └── Resources/
│ ├── BaseResource.php
│ ├── AccountResource.php ├── AccountAgentBotsResource.php
│ ├── AgentsResource.php ├── AuditLogsResource.php
│ ├── AutomationRulesResource.php ├── CannedResponsesResource.php
│ ├── ContactLabelsResource.php ├── ContactsResource.php
│ ├── ConversationAssignmentsResource.php
│ ├── ConversationsResource.php ├── CustomAttributesResource.php
│ ├── CustomFiltersResource.php ├── HelpCenterResource.php
│ ├── InboxesResource.php ├── IntegrationsResource.php
│ ├── MessagesResource.php ├── ProfileResource.php
│ ├── ReportsResource.php ├── TeamsResource.php
│ └── WebhooksResource.php
├── Client/
│ ├── ClientApiClient.php
│ └── Resources/
│ ├── BaseClientResource.php
│ ├── ContactsApiResource.php
│ ├── ConversationsApiResource.php
│ └── MessagesApiResource.php
└── Platform/
├── PlatformClient.php
└── Resources/
├── BasePlatformResource.php
├── PlatformAccountsResource.php
├── PlatformAccountUsersResource.php
├── PlatformAgentBotsResource.php
└── PlatformUsersResource.php
tests/
├── Unit/
│ ├── Application/ (unit tests for all 20 Application resources)
│ ├── Client/ (unit tests for Client API resources)
│ ├── Platform/ (unit tests for Platform API resources)
│ ├── DTO/ (BaseDTO hydration, enums, paginated collections)
│ ├── ChatwootClientTest.php
│ └── ExceptionsTest.php
└── Integration/
├── IntegrationTestCase.php
├── ApplicationApiTest.php
├── ClientApiTest.php
└── PlatformApiTest.php
MIT — see LICENSE.