This is a totally unoffical FreeScout PHP client. This client contains methods for easily interacting with the FreeScout API.
It's based on the Help Scout PHP SDK, which just happens to work nearly identically.
| SDK Version | PHP Version | Branch | Documentation |
|---|---|---|---|
1.* |
>= 7.3 | master |
This page |
The recommended way to install the client is by using Composer.
composer require thelogicstudio/freescout-api "^1.0"You should always use Composer's autoloader in your application to autoload classes. All examples below assume you've already included this in your code:
require_once 'vendor/autoload.php';Use the factory to create a client. Once created, you can set the various credentials to make requests.
use FreeScout\Api\ApiClientFactory;
$client = ApiClientFactory::createClient([
'baseUri' => 'https://freescout.example.org',
'apiKey' => 'abc123',
]);Get a customer. Whenever getting a customer, all it's entities (email addresses, phone numbers, social profiles, etc.) come preloaded in the same request.
$customer = $client->customers()->get($customerId);Get customers.
$customers = $client->customers()->list();Get customers with a filter.
As described in the API docs the customer list can be filtered by a variety of fields. The CustomerFields class
provides a simple interface to set filter values. For example:
use FreeScout\Api\Customers\CustomerFilters;
$filter = (new CustomerFilters())
->byFirstName('Tom')
->byLastName('Graham');
$customers = $client->customers()->list($filter);Create a customer.
use FreeScout\Api\Customers\Customer;
$customer = new Customer();
$customer->setFirstName('Bob');
// ...
try {
$customerId = $client->customers()->create($customer);
} catch (\FreeScout\Api\Exception\ValidationErrorException $e) {
var_dump($e->getError()->getErrors());
}Update a customer.
// ...
$customer->setFirstName('Bob');
$client->customers()->update($customer);Create a customer email.
use FreeScout\Api\Customers\Entry\Email;
$email = new Email();
$email->setValue('lucy@example.com');
$email->setType('work');
// ...
$client->customerEntry()->createEmail($customerId, $email);Update a customer email.
// ...
$email->setType('home');
$client->customerEntry()->updateEmail($customerId, $email);Delete a customer email.
$client->customerEntry()->deleteEmail($customerId, $emailId);Create a customer address.
use FreeScout\Api\Customers\Entry\Address;
$address = new Address();
$address->setCity('Boston');
// ...
$client->customerEntry()->createAddress($customerId, $address);Update a customer address.
// ...
$address->setCity('Boston');
$client->customerEntry()->updateAddress($customerId, $address);Delete a customer address.
$client->customerEntry()->deleteAddress($customerId);Create a customer phone.
use FreeScout\Api\Customers\Entry\Phone;
$phone = new Phone();
$phone->setValue('123456789');
$phone->setType('work');
// ...
$client->customerEntry()->createPhone($customerId, $phone);Update a customer phone.
// ...
$phone->setType('home');
$client->customerEntry()->updatePhone($customerId, $phone);Delete a customer phone.
$client->customerEntry()->deletePhone($customerId, $phoneId);Create a customer chat.
use FreeScout\Api\Customers\Entry\ChatHandle;
$chat = new ChatHandle();
$chat->setValue('1239134812348');
$chat->setType('icq');
// ...
$client->customerEntry()->createChat($customerId, $chat);Update a customer chat.
// ...
$chat->setValue('1230148584583');
$client->customerEntry()->updateChat($customerId, $chat);Delete a customer chat.
$client->customerEntry()->deleteChat($customerId, $chatId);Create a customer social profile.
use FreeScout\Api\Customers\Entry\SocialProfile;
$socialProfile = new SocialProfile();
$socialProfile->setValue('example');
$socialProfile->setType('twitter');
// ...
$client->customerEntry()->createSocialProfile($customerId, $socialProfile);Update a customer social profile.
// ...
$socialProfile->setType('facebook');
$client->customerEntry()->updateSocialProfile($customerId, $socialProfile);Delete a customer social profile.
$client->customerEntry()->deleteSocialProfile($customerId, $socialProfileId);Create a customer website.
use FreeScout\Api\Customers\Entry\Website;
$website = new Website();
$website->setValue('https://www.example.com');
// ...
$client->customerEntry()->createWebsite($customerId, $website);Update a customer website.
// ...
$website->setValue('https://www.example.net');
$client->customerEntry()->updateWebsite($customerId, $website);Delete a customer website.
$client->customerEntry()->deleteWebsite($customerId, $websiteId);Get a customer's properties and their values.
$customer = $client->customers()->get(418048101);
// ...
foreach ($customer->getProperties() as $property) {
echo $property->getName().': '.$property->getValue().PHP_EOL;
}Update a customer's properties.
use FreeScout\Api\Entity\Collection;
use FreeScout\Api\Entity\Patch;
$operations = new Collection(
[
new Patch('remove', 'property-1'),
new Patch('replace', 'property-2', 'value'),
];
);
$client->customerEntry()->updateProperties($customerId, $operations);Get a mailbox.
$mailbox = $client->mailboxes()->get($mailboxId);Get a mailbox with pre-loaded sub-entities.
A mailbox entity has two related sub-entities:
- Fields
- Folders
Each of these sub-entities can be pre-loaded when fetching a mailbox to remove the need for multiple method calls. The MailboxRequest class is used
to describe which sub-entities should be pre-loaded. For example:
use FreeScout\Api\Mailboxes\MailboxRequest;
$request = (new MailboxRequest)
->withFields()
->withFolders();
$mailbox = $client->mailboxes()->get($mailboxId, $request);
$fields = $mailbox->getFields();
$folders = $mailbox->getFolders();Get mailboxes.
$mailboxes = $client->mailboxes()->list();Get mailboxes with pre-loaded sub-entities.
use FreeScout\Api\Mailboxes\MailboxRequest;
$request = (new MailboxRequest)
->withFields()
->withFolders();
$mailboxes = $client->mailboxes()->list($request);Get a conversation.
$conversation = $client->conversations()->get($conversationId);You can easily eager load additional information/relationships for a conversation. For example:
use FreeScout\Api\Conversations\ConversationRequest;
$request = (new ConversationRequest)
->withMailbox()
->withPrimaryCustomer()
->withCreatedByCustomer()
->withCreatedByUser()
->withClosedBy()
->withThreads()
->withAssignee();
$conversation = $client->conversations()->get($conversationId, $request);
$mailbox = $conversation->getMailbox();
$primaryCustomer = $conversation->getCustomer();Get conversations.
$conversations = $client->conversations()->list();Get conversations with pre-loaded sub-entities.
use FreeScout\Api\Conversations\ConversationRequest;
$request = (new ConversationRequest)
->withMailbox()
->withPrimaryCustomer()
->withCreatedByCustomer()
->withCreatedByUser()
->withClosedBy()
->withThreads()
->withAssignee();
$conversations = $client->conversations()->list(null, $request);Narrow down the list of Conversations based on a set of filters.
use FreeScout\Api\Conversations\ConversationFilters;
$filters = (new ConversationFilters())
->inMailbox(1)
->inFolder(13)
->inStatus('all')
->hasTag('testing')
->assignedTo(1771)
->modifiedSince(new DateTime('2017-05-06T09:04:23+05:00'))
->byNumber(42)
->sortField('createdAt')
->sortOrder('asc')
->withQuery('query')
->byCustomField(123, 'blue');
$conversations = $client->conversations()->list($filters);You can even combine the filters with the pre-loaded sub-entities in one request
use FreeScout\Api\Conversations\ConversationRequest;
use FreeScout\Api\Conversations\ConversationFilters;
$request = (new ConversationRequest)
->withMailbox()
->withThreads();
$filters = (new ConversationFilters())
->inMailbox(1)
->inFolder(13)
->byCustomField(123, 'blue');
$conversations = $client->conversations()->list($filters, $request);Update the custom fields on a conversation:
$customField = new CustomField();
$customField->setId(10524);
$customField->setValue(new DateTime('today'));
$client->conversations()->updateCustomFields($conversationId, [$customField]);Create a new conversation, as if the customer sent an email to your mailbox.
// We can specify either the id or email for the Customer
$customer = new Customer();
$customer->addEmail('my-customer@company.com');
$thread = new CustomerThread();
$thread->setCustomer($customer);
$thread->setText('Test');
$conversation = new Conversation();
$conversation->setSubject('Testing the PHP SDK v2: Phone Thread');
$conversation->setStatus('active');
$conversation->setType('email');
$conversation->setMailboxId(80261);
$conversation->setCustomer($customer);
$conversation->setThreads(new Collection([
$thread,
]));
// You can optionally add tags
$tag = new Tag();
$tag->setName('testing');
$conversation->addTag($tag);
try {
$conversationId = $client->conversations()->create($conversation);
} catch (ValidationErrorException $e) {
var_dump($e->getError()->getErrors());
}Here's some other example scenarios where you might create conversations:
Phone conversation, initiated by a FreeScout user
$user = $client->users()->get(31231);
$customer = new Customer();
$customer->setId(193338443);
$thread = new PhoneThread();
$thread->setCustomer($customer);
$thread->setCreatedByUser($user);
$thread->setText('Test');
$conversation = new Conversation();
$conversation->setSubject('Testing the PHP SDK v2: Phone Thread');
$conversation->setStatus('active');
$conversation->setType('phone');
$conversation->setMailboxId(80261);
$conversation->setCustomer($noteCustomer);
$conversation->setCreatedByUser($user);
$conversation->setThreads(new Collection([
$thread,
]));
Chat conversation, initiated by the Customer
$noteCustomer = new Customer();
$noteCustomer->setId(163315601);
$thread = new ChatThread();
$thread->setCustomer($noteCustomer);
$thread->setText('Test');
$conversation = new Conversation();
$conversation->setSubject('Testing the PHP SDK v2: Chat Thread');
$conversation->setStatus('active');
$conversation->setType('chat');
$conversation->setAssignTo(271315);
$conversation->setMailboxId(138367);
$conversation->setCustomer($noteCustomer);
$conversation->setThreads(new Collection([
$thread,
]));
// Also adding a tag to this conversation
$tag = new Tag();
$tag->setName('testing');
$conversation->addTag($tag);
$conversationId = $client->conversations()->create($conversation);
Delete a conversation:
$client->conversations()->delete($conversationId);Update an existing conversation:
$client->conversations()->move($conversationId, 18);
$client->conversations()->updateSubject($conversationId, 'Need more help please');
$client->conversations()->updateCustomer($conversationId, 6854);
$client->conversations()->publishDraft($conversationId);
$client->conversations()->updateStatus($conversationId, 'closed');
$client->conversations()->assign($conversationId, 127);
$client->conversations()->unassign($conversationId);Create new Chat threads for a conversation.
use FreeScout\Api\Customers\Customer;
use FreeScout\Api\Conversations\Threads\ChatThread;
$thread = new ChatThread();
$customer = new Customer();
$customer->setId(163487350);
$thread->setCustomer($customer);
$thread->setText('Thanks for reaching out to us!');
$client->threads()->create($conversationId, $thread);Create new Customer threads for a conversation.
use FreeScout\Api\Customers\Customer;
use FreeScout\Api\Conversations\Threads\CustomerThread;
$thread = new CustomerThread();
$customer = new Customer();
$customer->setId(163487350);
$thread->setCustomer($customer);
$thread->setText('Please help me figure this out');
$client->threads()->create($conversationId, $thread);Create new Note threads for a conversation.
use FreeScout\Api\Conversations\Threads\NoteThread;
$thread->setText('We are still looking into this');
$client->threads()->create($conversationId, $thread);Create new Phone threads for a conversation.
use FreeScout\Api\Customers\Customer;
use FreeScout\Api\Conversations\Threads\PhoneThread;
$thread = new PhoneThread();
$customer = new Customer();
$customer->setId(163487350);
$thread->setCustomer($customer);
$thread->setText('This customer called and spoke with us directly about the delay on their order');
$client->threads()->create($conversationId, $thread);Create new Reply threads for a conversation.
use FreeScout\Api\Customers\Customer;
use FreeScout\Api\Conversations\Threads\ReplyThread;
$thread = new ReplyThread();
$customer = new Customer();
$customer->setId(163487350);
$thread->setCustomer($customer);
$thread->setText("Thanks, we'll be with you shortly!");
$client->threads()->create($conversationId, $thread);Get threads for a conversation.
$threads = $client->threads()->list($conversationId);Get an attachment.
$attachment = $client->attachments()->get($conversationId, $attachmentId);
$attachment->getData(); // attached file's contentsCreate an attachment:
use FreeScout\Api\Conversations\Threads\Attachments\AttachmentFactory;
use FreeScout\Api\Support\Filesystem;
$attachmentFactory = new AttachmentFactory(new Filesystem());
$attachment = $attachmentFactory->make('path/to/profile.jpg');
$attachment->getMimeType(); // image/jpeg
$attachment->getFilename(); // profile.jpg
$attachment->getData(); // base64 encoded contents of the file
$client->attachments()->create($conversationId, $threadId, $attachment);Delete an attachment:
$client->attachments()->delete($conversationId, $attachmentId);Get a chat
$chat = $client->chats()->get($chatId);List the chat events
$events = $client->chats()->events($chatId);List the tags
$tags = $client->tags()->list();List the teams
$teams = $client->teams()->list();List the members of a team
$users = $client->teams()->members($teamId);Get a user.
$user = $client->users()->get($userId);Get users.
$users = $client->users()->list();Narrow down the list of Users based on a set of filters.
use FreeScout\Api\Users\UserFilters;
$filters = (new UserFilters())
->inMailbox(1)
->byEmail('tester@test.com');
$users = $client->users()->list($filters);Get a webhook.
$webhook = $client->webhooks()->get($webhookId);List webhooks.
$webhooks = $client->webhooks()->list();Create a webhook.
The default state for a newly-created webhook is enabled.
use FreeScout\Api\Webhooks\Webhook;
$data = [
'url' => 'http://bad-url.com',
'events' => ['convo.assigned', 'convo.moved'],
'secret' => 'notARealSecret'
];
$webhook = new Webhook();
$webhook->hydrate($data);
// ...
$client->webhooks()->create($webhook);Update a webhook
This operation replaces the entire webhook entity, so you must provide the secret again. Once updated, the webhook will be in the enabled state again.
$webhook->setUrl('http://bad-url.com/really_really_bad');
$webhook->setSecret('mZ9XbGHodY');
$client->webhooks()->update($webhook);Delete a webhook.
$client->webhooks()->delete($webhookId);You can also use the SDK to easily process an incoming webhook. Signature validation will happen when creating the new object, so no need to check if it is valid or not. If the signatures do not match, the constructor of the IncomingWebhook object will throw an InvalidSignatureException to let you know something is wrong.
// Build it from globals
$incoming = IncomingWebhook::makeFromGlobals($secret);// or build using a request object that satisfies the PSR-7 RequestInterface
/** @var RequestInterface $request */
$request = new Request(...);
$secret = 'superSekretKey';
$incoming = new IncomingWebhook($request, $secret);Once you have the incoming webhook object, you can check the type of payload (customer, conversation, or test) as well as retrieve the data. If a customer or conversation, you can retrieve the model associated. Otherwise, you can get the payload as either an associative array or standard class object.
Fetch a paginated list of all workflows.
$workflows = $client->workflows()->list();Run a manual workflow on a list of conversations.
$convos = [
123,
321
];
$client->workflows()->runWorkflow($id, $convos);Change a workflow status to either "active" or "inactive"
$client->workflows()->updateStatus($id, 'active');Any exception thrown by the client directly will implement FreeScout\Api\Exception and HTTP errors will result in Http\Client\Exception\RequestException being thrown.
If an OAuth2 token is not provided or invalid then a FreeScout\Api\Exception\AuthenticationException is thrown.
You'll encounter a ValidationErrorException if there are any validation errors with the request you submitted to the API. Here's a quick example on how to use that exception:
try {
// do something
} catch (\FreeScout\Api\Exception\ValidationErrorException $e) {
$error = $e->getError();
var_dump(
// A reference id for that request. Including this anytime you contact Free Scout will
// empower us to dig right to the heart of the issue
$error->getCorrelationId(),
// Details about the invalid fields in the request
$error->getErrors()
);
exit;
}When fetching a collection of entities the client will return an instance of FreeScout\Api\Entity\Collection. If the end point supports pagination then it will return an instance of FreeScout\Api\Entity\PagedCollection.
/** @var PagedCollection $users */
$users = $client->users()->list();
// Iterate over the first page of results
foreach ($users as $user) {
echo $users->getFirstName();
}
// The current page number
$users->getPageNumber();
// The total number of pages
$users->getTotalPageCount();
// Load the next page
$nextUsers = $users->getNextPage();
// Load the previous page
$previousUsers = $users->getPreviousPage();
// Load the first page
$firstUsers = $users->getFirstPage();
// Load the last page
$lastUsers = $users->getLastPage();
// Load a specific page
$otherUsers = $users->getPage(12);
// Paged results are accessible as normal arrays, so you can simply iterate over them
foreach ($otherUsers as $user) {
echo $user->getFirstName();
}The SDK comes with a handy mock method on the ApiClient class. To use this, pass in the name of the endpoint you want to mock. You'll get a \Mockery\MockInterface object back. Once you set the mock, any subsequent calls to that endpoint will return the mocked object.
// From within the tests/ApiClientTest.php file...
public function testMockReturnsProperMock()
{
$client = ApiClientFactory::createClient();
$mockedWorkflows = $client->mock('workflows');
$this->assertInstanceOf(WorkflowsEndpoint::class, $mockedWorkflows);
$this->assertInstanceOf(MockInterface::class, $mockedWorkflows);
$this->assertSame(
$mockedWorkflows,
$client->workflows()
);
}Once you've mocked an endpoint, you may want to clear it later on. To do this, you can use the clearMock($endpoint) method on the ApiClient.