laravel-quo maintained by blamodex
Blamodex Laravel Quo
A Laravel wrapper for the Quo (OpenPhone) public API v1. Provides typed, namespaced access to all API resources with full test coverage.
Table of Contents
- Features
- Installation
- Configuration
- Usage
- Error Handling
- Testing
- Project Structure
- Contributing
- License
Features
- Full coverage of the Quo (OpenPhone) API v1 (27 endpoints)
- Namespaced resource services (calls, contacts, messages, etc.)
- Typed DTOs for all API responses
- Built-in error handling with
QuoApiException - Connection timeout and failure handling
- Laravel service provider with auto-discovery
Installation
Install the package with Composer:
composer require blamodex/laravel-quo
Configuration
Set your API key in .env:
QUO_API_KEY=your-api-key-here
Optionally publish the config file:
php artisan vendor:publish --tag=blamodex-quo-config
This creates config/quo.php with the following options:
return [
'api_key' => env('QUO_API_KEY', ''),
'base_url' => env('QUO_BASE_URL', 'https://api.openphone.com'),
];
Usage
Inject QuoService or resolve it from the container:
use Blamodex\Quo\Services\QuoService;
$quo = app(QuoService::class);
Calls
// List calls for a phone number
$result = $quo->calls()->list('PN123', ['+15551234567'], [
'maxResults' => 25,
'createdAfter' => '2025-01-01T00:00:00Z',
]);
// $result['calls'] => array of CallData
// $result['totalItems'] => int
// $result['nextPageToken'] => string|null
// Get a single call
$call = $quo->calls()->find('AC123');
// Get recordings for a call
$recordings = $quo->calls()->getRecordings('AC123');
// Get call summary
$summary = $quo->calls()->getSummary('AC123');
// Get call transcript
$transcript = $quo->calls()->getTranscript('AC123');
// Get voicemail for a call
$voicemail = $quo->calls()->getVoicemail('AC123');
Contacts
// List contacts
$result = $quo->contacts()->list(['maxResults' => 50]);
// Get a contact
$contact = $quo->contacts()->find('CT123');
// Create a contact
$contact = $quo->contacts()->create([
'firstName' => 'John',
'lastName' => 'Doe',
'phoneNumbers' => [['name' => 'Mobile', 'value' => '+15551234567']],
]);
// Update a contact
$contact = $quo->contacts()->update('CT123', [
'defaultFields' => ['firstName' => 'Jane'],
]);
// Delete a contact
$quo->contacts()->delete('CT123');
// Get custom fields
$fields = $quo->contacts()->getCustomFields();
Conversations
// List conversations
$result = $quo->conversations()->list([
'phoneNumbers' => ['+15551234567'],
'excludeInactive' => true,
'maxResults' => 25,
]);
Messages
// List messages
$result = $quo->messages()->list('PN123', ['+15551234567']);
// Get a message
$message = $quo->messages()->find('MSG123');
// Send a message
$message = $quo->messages()->send(
'Hello!',
'+15551234567',
['+15559876543'],
);
Phone Numbers
// List all phone numbers
$numbers = $quo->phoneNumbers()->list();
// List phone numbers for a user
$numbers = $quo->phoneNumbers()->list('US123');
// Get a phone number
$number = $quo->phoneNumbers()->find('PN123');
Users
// List users
$result = $quo->users()->list(['maxResults' => 50]);
// Get a user
$user = $quo->users()->find('US123');
Webhooks
// List webhooks
$webhooks = $quo->webhooks()->list();
// Get a webhook
$webhook = $quo->webhooks()->find('WH123');
// Create webhooks for different resources
$webhook = $quo->webhooks()->createForCalls('https://example.com/hook', ['call.completed']);
$webhook = $quo->webhooks()->createForMessages('https://example.com/hook', ['message.received']);
$webhook = $quo->webhooks()->createForCallSummaries('https://example.com/hook', ['call.summary.completed']);
$webhook = $quo->webhooks()->createForCallTranscripts('https://example.com/hook', ['call.transcript.completed']);
// Delete a webhook
$quo->webhooks()->delete('WH123');
Error Handling
All API errors throw QuoApiException:
use Blamodex\Quo\Exceptions\QuoApiException;
try {
$call = $quo->calls()->find('AC_invalid');
} catch (QuoApiException $e) {
$e->getMessage(); // "Call not found"
$e->statusCode; // 404
$e->errorCode; // "0900404"
$e->docs; // "https://docs.openphone.com/..."
$e->getPrevious(); // Original exception (for connection failures)
}
Connection failures (timeouts, DNS errors) are also wrapped in QuoApiException with statusCode: 0.
Testing
This package uses Orchestra Testbench and PHPUnit.
Run tests:
composer test
Check code style:
composer lint
Run static analysis:
composer analyze
Check coverage (with Xdebug):
composer test:coverage
Run all QA checks:
composer qa
Project Structure
src/
├── QuoServiceProvider.php
├── config/
│ └── quo.php
├── Services/
│ ├── QuoService.php
│ ├── CallService.php
│ ├── ContactService.php
│ ├── ConversationService.php
│ ├── MessageService.php
│ ├── PhoneNumberService.php
│ ├── UserService.php
│ ├── WebhookService.php
│ └── Concerns/
│ └── MakesRequests.php
├── Data/
│ ├── CallData.php
│ ├── CallRecordingData.php
│ ├── CallSummaryData.php
│ ├── CallTranscriptData.php
│ ├── CallVoicemailData.php
│ ├── ContactCustomFieldData.php
│ ├── ContactData.php
│ ├── ConversationData.php
│ ├── DialogueSegmentData.php
│ ├── MessageData.php
│ ├── PhoneNumberData.php
│ ├── UserData.php
│ └── WebhookData.php
└── Exceptions/
└── QuoApiException.php
tests/
├── TestCase.php
└── Unit/
├── CallServiceTest.php
├── ContactServiceTest.php
├── ConversationServiceTest.php
├── MessageServiceTest.php
├── PhoneNumberServiceTest.php
├── QuoServiceTest.php
├── UserServiceTest.php
└── WebhookServiceTest.php
Contributing
We welcome contributions! Please see CONTRIBUTING.md for details.
Changelog
Please see CHANGELOG.md for recent changes.
License
MIT. See the LICENSE file.