Install
openclaw skills install quoQuo API integration with managed OAuth. Manage calls, messages, contacts, and conversations for your business phone system. Use this skill when users want to send SMS, list calls, manage contacts, or retrieve call recordings/transcripts. For other third party apps, use the api-gateway skill (https://clawhub.ai/byungkyu/api-gateway). Requires network access and valid Maton API key.
openclaw skills install quoAccess the Quo API with managed OAuth authentication. Send SMS messages, manage calls and contacts, and retrieve call recordings and transcripts.
# List phone numbers
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/quo/v1/phone-numbers')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('User-Agent', 'Maton/1.0')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
https://api.maton.ai/quo/{native-api-path}
Maton proxies requests to api.openphone.com and automatically injects your OAuth token.
All requests require the Maton API key in the Authorization header and a User-Agent header:
Authorization: Bearer $MATON_API_KEY
User-Agent: Maton/1.0
Environment Variable: Set your API key as MATON_API_KEY:
export MATON_API_KEY="YOUR_API_KEY"
Manage your Quo OAuth connections at https://api.maton.ai.
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections?app=quo&status=ACTIVE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
python <<'EOF'
import urllib.request, os, json
data = json.dumps({'app': 'quo'}).encode()
req = urllib.request.Request('https://api.maton.ai/connections', data=data, method='POST')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('Content-Type', 'application/json')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections/{connection_id}')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
Response:
{
"connection": {
"connection_id": "{connection_id}",
"status": "ACTIVE",
"creation_time": "2025-12-08T07:20:53.488460Z",
"last_updated_time": "2026-01-31T20:03:32.593153Z",
"url": "https://connect.maton.ai/?session_token=...",
"app": "quo",
"metadata": {}
}
}
Open the returned url in a browser to complete OAuth authorization.
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections/{connection_id}', method='DELETE')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If you have multiple Quo connections, specify which one to use with the Maton-Connection header:
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/quo/v1/phone-numbers')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
req.add_header('User-Agent', 'Maton/1.0')
req.add_header('Maton-Connection', '{connection_id}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
If you have multiple connections, always include this header to ensure requests go to the intended account.
GET /quo/v1/phone-numbers
Optional query parameter:
userId - Filter by user ID (pattern: ^US(.*)$)Response:
{
"data": [
{
"id": "PN123abc",
"number": "+15555555555",
"formattedNumber": "(555) 555-5555",
"name": "Main Line",
"users": [
{
"id": "US123abc",
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe",
"role": "admin"
}
],
"createdAt": "2022-01-01T00:00:00Z",
"updatedAt": "2022-01-01T00:00:00Z"
}
]
}
GET /quo/v1/users?maxResults=50
Query parameters:
maxResults (required) - Results per page (1-50, default: 10)pageToken - Pagination tokenResponse:
{
"data": [
{
"id": "US123abc",
"email": "user@example.com",
"firstName": "John",
"lastName": "Doe",
"role": "owner",
"createdAt": "2022-01-01T00:00:00Z",
"updatedAt": "2022-01-01T00:00:00Z"
}
],
"totalItems": 10,
"nextPageToken": null
}
GET /quo/v1/users/{userId}
POST /quo/v1/messages
Content-Type: application/json
{
"content": "Hello, world!",
"from": "PN123abc",
"to": ["+15555555555"]
}
Request body:
content (required) - Message text (1-1600 characters)from (required) - Phone number ID (PN*) or E.164 formatto (required) - Array with single recipient in E.164 formatuserId - User ID (defaults to phone owner)setInboxStatus - Set to "done" to mark conversation completeResponse (202):
{
"id": "AC123abc",
"to": ["+15555555555"],
"from": "+15555555555",
"text": "Hello, world!",
"phoneNumberId": "PN123abc",
"direction": "outgoing",
"userId": "US123abc",
"status": "queued",
"createdAt": "2022-01-01T00:00:00Z",
"updatedAt": "2022-01-01T00:00:00Z"
}
GET /quo/v1/messages?phoneNumberId=PN123abc&participants[]=+15555555555&maxResults=100
Query parameters:
phoneNumberId (required) - Phone number IDparticipants (required) - Array of participant phone numbers in E.164 formatmaxResults (required) - Results per page (1-100, default: 10)userId - Filter by user IDcreatedAfter - ISO 8601 timestampcreatedBefore - ISO 8601 timestamppageToken - Pagination tokenGET /quo/v1/messages/{messageId}
GET /quo/v1/calls?phoneNumberId=PN123abc&participants[]=+15555555555&maxResults=100
Query parameters:
phoneNumberId (required) - Phone number IDparticipants (required) - Array with single participant phone number in E.164 format (max 1)maxResults (required) - Results per page (1-100, default: 10)userId - Filter by user IDcreatedAfter - ISO 8601 timestampcreatedBefore - ISO 8601 timestamppageToken - Pagination tokenResponse:
{
"data": [
{
"id": "AC123abc",
"phoneNumberId": "PN123abc",
"userId": "US123abc",
"direction": "incoming",
"status": "completed",
"duration": 120,
"participants": ["+15555555555"],
"answeredAt": "2022-01-01T00:00:00Z",
"completedAt": "2022-01-01T00:02:00Z",
"createdAt": "2022-01-01T00:00:00Z",
"updatedAt": "2022-01-01T00:02:00Z"
}
],
"totalItems": 50,
"nextPageToken": "..."
}
GET /quo/v1/calls/{callId}
GET /quo/v1/call-recordings/{callId}
Response:
{
"data": [
{
"id": "REC123abc",
"duration": 120,
"startTime": "2022-01-01T00:00:00Z",
"status": "completed",
"type": "voicemail",
"url": "https://..."
}
]
}
Recording status values: absent, completed, deleted, failed, in-progress, paused, processing, stopped, stopping
GET /quo/v1/call-summaries/{callId}
GET /quo/v1/call-transcripts/{callId}
GET /quo/v1/call-voicemails/{callId}
GET /quo/v1/contacts?maxResults=50
Query parameters:
maxResults (required) - Results per page (1-50, default: 10)externalIds - Array of external identifierssources - Array of source indicatorspageToken - Pagination tokenResponse:
{
"data": [
{
"id": "CT123abc",
"externalId": null,
"source": null,
"defaultFields": {
"company": "Acme Corp",
"firstName": "Jane",
"lastName": "Doe",
"role": "Manager",
"emails": [{"name": "work", "value": "jane@example.com", "id": "EM1"}],
"phoneNumbers": [{"name": "mobile", "value": "+15555555555", "id": "PH1"}]
},
"customFields": [],
"createdAt": "2022-01-01T00:00:00Z",
"updatedAt": "2022-01-01T00:00:00Z",
"createdByUserId": "US123abc"
}
],
"totalItems": 100,
"nextPageToken": "..."
}
GET /quo/v1/contacts/{contactId}
POST /quo/v1/contacts
Content-Type: application/json
{
"defaultFields": {
"firstName": "Jane",
"lastName": "Doe",
"company": "Acme Corp",
"phoneNumbers": [{"name": "mobile", "value": "+15555555555"}],
"emails": [{"name": "work", "value": "jane@example.com"}]
}
}
PATCH /quo/v1/contacts/{contactId}
Content-Type: application/json
{
"defaultFields": {
"company": "New Company"
}
}
DELETE /quo/v1/contacts/{contactId}
GET /quo/v1/contact-custom-fields
GET /quo/v1/conversations?maxResults=100
Query parameters:
maxResults (required) - Results per page (1-100, default: 10)phoneNumbers - Array of phone number IDs or E.164 numbers (1-100 items)userId - Filter by user IDcreatedAfter - ISO 8601 timestampcreatedBefore - ISO 8601 timestampupdatedAfter - ISO 8601 timestampupdatedBefore - ISO 8601 timestampexcludeInactive - Boolean to exclude inactive conversationspageToken - Pagination tokenResponse:
{
"data": [
{
"id": "CV123abc",
"phoneNumberId": "PN123abc",
"name": "Jane Doe",
"participants": ["+15555555555"],
"assignedTo": "US123abc",
"lastActivityAt": "2022-01-01T00:00:00Z",
"createdAt": "2022-01-01T00:00:00Z",
"updatedAt": "2022-01-01T00:00:00Z"
}
],
"totalItems": 50,
"nextPageToken": "..."
}
Quo uses token-based pagination. Include maxResults to set page size and use pageToken to retrieve subsequent pages.
GET /quo/v1/contacts?maxResults=50&pageToken=eyJsYXN0SWQiOi...
Response includes pagination info:
{
"data": [...],
"totalItems": 150,
"nextPageToken": "eyJsYXN0SWQiOi..."
}
When nextPageToken is null, you've reached the last page.
const response = await fetch(
'https://api.maton.ai/quo/v1/phone-numbers',
{
headers: {
'Authorization': `Bearer ${process.env.MATON_API_KEY}`,
'User-Agent': 'Maton/1.0'
}
}
);
const data = await response.json();
import os
import requests
response = requests.get(
'https://api.maton.ai/quo/v1/phone-numbers',
headers={
'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}',
'User-Agent': 'Maton/1.0'
}
)
data = response.json()
import os
import requests
response = requests.post(
'https://api.maton.ai/quo/v1/messages',
headers={
'Authorization': f'Bearer {os.environ["MATON_API_KEY"]}',
'User-Agent': 'Maton/1.0',
'Content-Type': 'application/json'
},
json={
'content': 'Hello from Quo!',
'from': 'PN123abc',
'to': ['+15555555555']
}
)
data = response.json()
PNUSAC+15555555555)User-Agent header (e.g., User-Agent: Maton/1.0). Requests without this header will be blocked.curl -g when URLs contain brackets (participants[]) to disable glob parsingjq or other commands, environment variables like $MATON_API_KEY may not expand correctly in some shell environments| Status | Meaning |
|---|---|
| 400 | Bad request (e.g., too many participants, invalid format) |
| 401 | Invalid or missing Maton API key |
| 402 | Insufficient credits for SMS |
| 403 | Not authorized for this phone number |
| 404 | Resource not found |
| 429 | Rate limited |
| 500 | Server error |
MATON_API_KEY environment variable is set:echo $MATON_API_KEY
python <<'EOF'
import urllib.request, os, json
req = urllib.request.Request('https://api.maton.ai/connections')
req.add_header('Authorization', f'Bearer {os.environ["MATON_API_KEY"]}')
print(json.dumps(json.load(urllib.request.urlopen(req)), indent=2))
EOF
quo. For example:https://api.maton.ai/quo/v1/phone-numbershttps://api.maton.ai/openphone/v1/phone-numbers