blamodex / laravel-quo
A Laravel wrapper for the Quo (OpenPhone) public API v1.
Requires
- php: ^8.2
- illuminate/config: ^12.0
- illuminate/http: ^12.0
- illuminate/support: ^12.0
Requires (Dev)
- larastan/larastan: ^3.0
- orchestra/testbench: ^10.0
- phpunit/phpunit: ^11.5.3
- squizlabs/php_codesniffer: ^4.0
README
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.