aircall-api 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aircall/__init__.py +38 -0
- aircall/client.py +273 -0
- aircall/exceptions.py +55 -0
- aircall/logging_config.py +81 -0
- aircall/models/__init__.py +83 -0
- aircall/models/ai_voice_agent.py +49 -0
- aircall/models/call.py +94 -0
- aircall/models/company.py +14 -0
- aircall/models/contact.py +32 -0
- aircall/models/content.py +42 -0
- aircall/models/conversation_intelligence.py +66 -0
- aircall/models/dialer_campaign.py +21 -0
- aircall/models/integration.py +20 -0
- aircall/models/ivr_option.py +22 -0
- aircall/models/message.py +52 -0
- aircall/models/number.py +55 -0
- aircall/models/participant.py +38 -0
- aircall/models/tag.py +18 -0
- aircall/models/team.py +20 -0
- aircall/models/user.py +48 -0
- aircall/models/webhook.py +22 -0
- aircall/resources/__init__.py +29 -0
- aircall/resources/base.py +77 -0
- aircall/resources/call.py +270 -0
- aircall/resources/company.py +29 -0
- aircall/resources/contact.py +192 -0
- aircall/resources/dialer_campaign.py +90 -0
- aircall/resources/integration.py +53 -0
- aircall/resources/message.py +80 -0
- aircall/resources/number.py +94 -0
- aircall/resources/tag.py +83 -0
- aircall/resources/team.py +92 -0
- aircall/resources/user.py +129 -0
- aircall/resources/webhook.py +82 -0
- aircall_api-1.1.0.dist-info/METADATA +429 -0
- aircall_api-1.1.0.dist-info/RECORD +39 -0
- aircall_api-1.1.0.dist-info/WHEEL +5 -0
- aircall_api-1.1.0.dist-info/licenses/LICENSE +21 -0
- aircall_api-1.1.0.dist-info/top_level.txt +1 -0
aircall/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Aircall API Python SDK."""
|
|
2
|
+
|
|
3
|
+
from aircall.client import AircallClient
|
|
4
|
+
from aircall.exceptions import (
|
|
5
|
+
AircallAPIError,
|
|
6
|
+
AircallConnectionError,
|
|
7
|
+
AircallError,
|
|
8
|
+
AircallPermissionError,
|
|
9
|
+
AircallTimeoutError,
|
|
10
|
+
AuthenticationError,
|
|
11
|
+
NotFoundError,
|
|
12
|
+
RateLimitError,
|
|
13
|
+
ServerError,
|
|
14
|
+
UnprocessableEntityError,
|
|
15
|
+
ValidationError,
|
|
16
|
+
)
|
|
17
|
+
from aircall.logging_config import configure_logging
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
# Client
|
|
21
|
+
"AircallClient",
|
|
22
|
+
# Base exceptions
|
|
23
|
+
"AircallError",
|
|
24
|
+
"AircallAPIError",
|
|
25
|
+
# API exceptions
|
|
26
|
+
"AuthenticationError",
|
|
27
|
+
"AircallPermissionError",
|
|
28
|
+
"NotFoundError",
|
|
29
|
+
"ValidationError",
|
|
30
|
+
"UnprocessableEntityError",
|
|
31
|
+
"RateLimitError",
|
|
32
|
+
"ServerError",
|
|
33
|
+
# Connection exceptions
|
|
34
|
+
"AircallConnectionError",
|
|
35
|
+
"AircallTimeoutError",
|
|
36
|
+
# Logging
|
|
37
|
+
"configure_logging",
|
|
38
|
+
]
|
aircall/client.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"""Aircall API client."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import logging
|
|
5
|
+
import time
|
|
6
|
+
import requests
|
|
7
|
+
from requests.exceptions import Timeout, ConnectionError as RequestsConnectionError
|
|
8
|
+
|
|
9
|
+
from aircall.exceptions import (
|
|
10
|
+
AircallAPIError,
|
|
11
|
+
AircallConnectionError,
|
|
12
|
+
AircallTimeoutError,
|
|
13
|
+
AuthenticationError,
|
|
14
|
+
#AircallPermissionError,
|
|
15
|
+
NotFoundError,
|
|
16
|
+
RateLimitError,
|
|
17
|
+
ServerError,
|
|
18
|
+
UnprocessableEntityError,
|
|
19
|
+
ValidationError,
|
|
20
|
+
)
|
|
21
|
+
from aircall.resources import (
|
|
22
|
+
CallResource,
|
|
23
|
+
CompanyResource,
|
|
24
|
+
ContactResource,
|
|
25
|
+
DialerCampaignResource,
|
|
26
|
+
IntegrationResource,
|
|
27
|
+
MessageResource,
|
|
28
|
+
NumberResource,
|
|
29
|
+
TagResource,
|
|
30
|
+
TeamResource,
|
|
31
|
+
UserResource,
|
|
32
|
+
WebhookResource,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AircallClient:
|
|
37
|
+
"""
|
|
38
|
+
Main client for interacting with the Aircall API.
|
|
39
|
+
|
|
40
|
+
Handles authentication and provides access to all API resources.
|
|
41
|
+
|
|
42
|
+
Example:
|
|
43
|
+
>>> client = AircallClient(api_id="your_id", api_token="your_token")
|
|
44
|
+
>>> numbers = client.number.list()
|
|
45
|
+
>>> number = client.number.get(12345)
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
api_id: str,
|
|
51
|
+
api_token: str,
|
|
52
|
+
timeout: int = 30,
|
|
53
|
+
verbose: bool = False
|
|
54
|
+
) -> None:
|
|
55
|
+
"""
|
|
56
|
+
Initialize the Aircall API client.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
api_id: Your Aircall API ID
|
|
60
|
+
api_token: Your Aircall API token
|
|
61
|
+
timeout: Default request timeout in seconds (default: 30)
|
|
62
|
+
verbose: Enable verbose logging for debugging (default: False)
|
|
63
|
+
When True, sets the logger level to DEBUG
|
|
64
|
+
"""
|
|
65
|
+
self.base_url = "https://api.aircall.io/v1"
|
|
66
|
+
credentials = base64.b64encode(f"{api_id}:{api_token}".encode()).decode('utf-8')
|
|
67
|
+
self.timeout = timeout
|
|
68
|
+
|
|
69
|
+
# Initialize logger
|
|
70
|
+
self.logger = logging.getLogger('aircall.client')
|
|
71
|
+
|
|
72
|
+
# If verbose is enabled, set logger to DEBUG level
|
|
73
|
+
if verbose:
|
|
74
|
+
self.logger.setLevel(logging.DEBUG)
|
|
75
|
+
# Ensure handlers exist for the aircall logger
|
|
76
|
+
aircall_logger = logging.getLogger('aircall')
|
|
77
|
+
if not aircall_logger.handlers:
|
|
78
|
+
handler = logging.StreamHandler()
|
|
79
|
+
formatter = logging.Formatter(
|
|
80
|
+
fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
81
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
82
|
+
)
|
|
83
|
+
handler.setFormatter(formatter)
|
|
84
|
+
aircall_logger.addHandler(handler)
|
|
85
|
+
aircall_logger.setLevel(logging.DEBUG)
|
|
86
|
+
|
|
87
|
+
self.session = requests.Session()
|
|
88
|
+
self.session.headers.update({"Authorization": f"Basic {credentials}"})
|
|
89
|
+
|
|
90
|
+
self.logger.info("Aircall client initialized")
|
|
91
|
+
|
|
92
|
+
# Initialize resources
|
|
93
|
+
self.call = CallResource(self)
|
|
94
|
+
self.company = CompanyResource(self)
|
|
95
|
+
self.contact = ContactResource(self)
|
|
96
|
+
self.dialer_campaign = DialerCampaignResource(self)
|
|
97
|
+
self.integration = IntegrationResource(self)
|
|
98
|
+
self.message = MessageResource(self)
|
|
99
|
+
self.number = NumberResource(self)
|
|
100
|
+
self.tag = TagResource(self)
|
|
101
|
+
self.team = TeamResource(self)
|
|
102
|
+
self.user = UserResource(self)
|
|
103
|
+
self.webhook = WebhookResource(self)
|
|
104
|
+
|
|
105
|
+
def _request(
|
|
106
|
+
self,
|
|
107
|
+
method: str,
|
|
108
|
+
endpoint: str,
|
|
109
|
+
params: dict = None,
|
|
110
|
+
json: dict = None,
|
|
111
|
+
timeout: int = None
|
|
112
|
+
) -> dict:
|
|
113
|
+
"""
|
|
114
|
+
Make an HTTP request to the Aircall API.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
method: HTTP method (GET, POST, PUT, DELETE)
|
|
118
|
+
endpoint: API endpoint (e.g., "/numbers", "/contacts/123")
|
|
119
|
+
params: Query parameters as dict (e.g., {"page": 1, "per_page": 50})
|
|
120
|
+
json: Request body as dict for POST/PUT requests
|
|
121
|
+
timeout: Request timeout in seconds (uses self.timeout if not specified)
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
dict: Parsed JSON response
|
|
125
|
+
|
|
126
|
+
Raises:
|
|
127
|
+
ValidationError: When request validation fails (400)
|
|
128
|
+
AuthenticationError: When authentication fails (401 or 403)
|
|
129
|
+
NotFoundError: When resource is not found (404)
|
|
130
|
+
UnprocessableEntityError: When server cannot process request (422)
|
|
131
|
+
RateLimitError: When rate limit is exceeded (429)
|
|
132
|
+
ServerError: When server returns 5xx error
|
|
133
|
+
AircallConnectionError: When connection to API fails
|
|
134
|
+
AircallTimeoutError: When request times out
|
|
135
|
+
AircallAPIError: For other API errors
|
|
136
|
+
"""
|
|
137
|
+
url = self.base_url + endpoint
|
|
138
|
+
|
|
139
|
+
# Log the request details
|
|
140
|
+
self.logger.debug("Request: %s %s", method, url)
|
|
141
|
+
if params:
|
|
142
|
+
self.logger.debug(" Query params: %s", params)
|
|
143
|
+
if json:
|
|
144
|
+
self.logger.debug(" Request body: %s", json)
|
|
145
|
+
|
|
146
|
+
start_time = time.time()
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
# Use requests library to handle params and json automatically
|
|
150
|
+
response = self.session.request(
|
|
151
|
+
method=method,
|
|
152
|
+
url=url,
|
|
153
|
+
params=params, # requests converts dict to query string
|
|
154
|
+
json=json, # requests converts dict to JSON body and sets Content-Type
|
|
155
|
+
timeout=timeout or self.timeout
|
|
156
|
+
)
|
|
157
|
+
except Timeout as e:
|
|
158
|
+
elapsed = time.time() - start_time
|
|
159
|
+
self.logger.error(
|
|
160
|
+
"Request timeout: %s %s - Failed after %ss (elapsed: %.2fs)",
|
|
161
|
+
method, url, timeout or self.timeout, elapsed
|
|
162
|
+
)
|
|
163
|
+
raise AircallTimeoutError(
|
|
164
|
+
f"Request to {url} timed out after {timeout or self.timeout} seconds"
|
|
165
|
+
) from e
|
|
166
|
+
except RequestsConnectionError as e:
|
|
167
|
+
elapsed = time.time() - start_time
|
|
168
|
+
self.logger.error(
|
|
169
|
+
"Connection error: %s %s - %s (elapsed: %.2fs)",
|
|
170
|
+
method, url, str(e), elapsed
|
|
171
|
+
)
|
|
172
|
+
raise AircallConnectionError(
|
|
173
|
+
f"Failed to connect to {url}: {str(e)}"
|
|
174
|
+
) from e
|
|
175
|
+
|
|
176
|
+
elapsed = time.time() - start_time
|
|
177
|
+
|
|
178
|
+
# Handle successful responses
|
|
179
|
+
if 200 <= response.status_code < 300:
|
|
180
|
+
self.logger.debug(
|
|
181
|
+
"Response: %s %s %s (took %.2fs)",
|
|
182
|
+
response.status_code, method, url, elapsed
|
|
183
|
+
)
|
|
184
|
+
if response.status_code == 204: # No Content
|
|
185
|
+
return {}
|
|
186
|
+
return response.json()
|
|
187
|
+
|
|
188
|
+
# Parse error response
|
|
189
|
+
error_data = None
|
|
190
|
+
error_message = response.reason
|
|
191
|
+
try:
|
|
192
|
+
error_data = response.json()
|
|
193
|
+
if isinstance(error_data, dict):
|
|
194
|
+
# Try to get message from various possible fields
|
|
195
|
+
error_message = error_data.get('message') or error_data.get('error') or error_message
|
|
196
|
+
except Exception:
|
|
197
|
+
# If response is not JSON, use text content
|
|
198
|
+
if response.text:
|
|
199
|
+
error_message = response.text
|
|
200
|
+
|
|
201
|
+
# Map status codes to appropriate exceptions based on Aircall API docs
|
|
202
|
+
status_code = response.status_code
|
|
203
|
+
|
|
204
|
+
# Log the error response
|
|
205
|
+
self.logger.warning(
|
|
206
|
+
"API error: %s %s %s - %s (took %.2fs)",
|
|
207
|
+
status_code, method, url, error_message, elapsed
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if status_code == 400:
|
|
211
|
+
# Invalid payload/Bad Request
|
|
212
|
+
raise ValidationError(
|
|
213
|
+
error_message,
|
|
214
|
+
status_code=status_code,
|
|
215
|
+
response_data=error_data
|
|
216
|
+
)
|
|
217
|
+
if status_code == 401:
|
|
218
|
+
raise AuthenticationError(
|
|
219
|
+
error_message,
|
|
220
|
+
status_code=status_code,
|
|
221
|
+
response_data=error_data
|
|
222
|
+
)
|
|
223
|
+
if status_code == 403:
|
|
224
|
+
# Forbidden - Invalid API key or Bearer access token
|
|
225
|
+
raise AuthenticationError(
|
|
226
|
+
error_message,
|
|
227
|
+
status_code=status_code,
|
|
228
|
+
response_data=error_data
|
|
229
|
+
)
|
|
230
|
+
if status_code == 404:
|
|
231
|
+
# Not found - Id does not exist
|
|
232
|
+
raise NotFoundError(
|
|
233
|
+
error_message,
|
|
234
|
+
status_code=status_code,
|
|
235
|
+
response_data=error_data
|
|
236
|
+
)
|
|
237
|
+
if status_code == 422:
|
|
238
|
+
# Server unable to process the request
|
|
239
|
+
raise UnprocessableEntityError(
|
|
240
|
+
error_message,
|
|
241
|
+
status_code=status_code,
|
|
242
|
+
response_data=error_data
|
|
243
|
+
)
|
|
244
|
+
if status_code == 429:
|
|
245
|
+
# Rate limit exceeded
|
|
246
|
+
retry_after = response.headers.get('Retry-After')
|
|
247
|
+
if retry_after:
|
|
248
|
+
self.logger.warning(
|
|
249
|
+
"Rate limit exceeded: %s %s - Retry after %ss",
|
|
250
|
+
method, url, retry_after
|
|
251
|
+
)
|
|
252
|
+
else:
|
|
253
|
+
self.logger.warning("Rate limit exceeded (no retry-after header)")
|
|
254
|
+
raise RateLimitError(
|
|
255
|
+
error_message,
|
|
256
|
+
status_code=status_code,
|
|
257
|
+
response_data=error_data,
|
|
258
|
+
retry_after=int(retry_after) if retry_after else None
|
|
259
|
+
)
|
|
260
|
+
if 500 <= status_code < 600:
|
|
261
|
+
# Server errors
|
|
262
|
+
raise ServerError(
|
|
263
|
+
error_message,
|
|
264
|
+
status_code=status_code,
|
|
265
|
+
response_data=error_data
|
|
266
|
+
)
|
|
267
|
+
else:
|
|
268
|
+
# Generic API error for other status codes
|
|
269
|
+
raise AircallAPIError(
|
|
270
|
+
error_message,
|
|
271
|
+
status_code=status_code,
|
|
272
|
+
response_data=error_data
|
|
273
|
+
)
|
aircall/exceptions.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Custom exceptions for the Aircall API client."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class AircallError(Exception):
|
|
5
|
+
"""Base exception for all Aircall errors"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AircallAPIError(AircallError):
|
|
9
|
+
"""Raised when the API returns an error response"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, message, status_code=None, response_data=None):
|
|
12
|
+
self.message = message
|
|
13
|
+
self.status_code = status_code
|
|
14
|
+
self.response_data = response_data
|
|
15
|
+
super().__init__(self.message)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AuthenticationError(AircallAPIError):
|
|
19
|
+
"""Raised when authentication fails (401)"""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AircallPermissionError(AircallAPIError):
|
|
23
|
+
"""Raised when user lacks permission (403)"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class NotFoundError(AircallAPIError):
|
|
27
|
+
"""Raised when resource not found (404)"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ValidationError(AircallAPIError):
|
|
31
|
+
"""Raised when request validation fails (400)"""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class UnprocessableEntityError(AircallAPIError):
|
|
35
|
+
"""Raised when server unable to process the request (422)"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class RateLimitError(AircallAPIError):
|
|
39
|
+
"""Raised when rate limit exceeded (429)"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, message, retry_after=None, **kwargs):
|
|
42
|
+
super().__init__(message, **kwargs)
|
|
43
|
+
self.retry_after = retry_after # Seconds until can retry
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ServerError(AircallAPIError):
|
|
47
|
+
"""Raised when server returns 5xx error"""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AircallConnectionError(AircallError):
|
|
51
|
+
"""Raised when connection to API fails"""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class AircallTimeoutError(AircallError):
|
|
55
|
+
"""Raised when request times out"""
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Logging configuration for the Aircall API SDK."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def setup_logger(
|
|
9
|
+
name: str,
|
|
10
|
+
level: Optional[int] = None,
|
|
11
|
+
handler: Optional[logging.Handler] = None
|
|
12
|
+
) -> logging.Logger:
|
|
13
|
+
"""
|
|
14
|
+
Set up a logger with consistent formatting.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
name: Logger name (typically __name__)
|
|
18
|
+
level: Logging level (defaults to WARNING if not set)
|
|
19
|
+
handler: Custom handler (defaults to StreamHandler if not set)
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
logging.Logger: Configured logger instance
|
|
23
|
+
"""
|
|
24
|
+
logger = logging.getLogger(name)
|
|
25
|
+
|
|
26
|
+
# Only configure if no handlers exist (avoid duplicate handlers)
|
|
27
|
+
if not logger.handlers:
|
|
28
|
+
if level is None:
|
|
29
|
+
level = logging.WARNING
|
|
30
|
+
|
|
31
|
+
logger.setLevel(level)
|
|
32
|
+
|
|
33
|
+
if handler is None:
|
|
34
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
35
|
+
|
|
36
|
+
# Create formatter with timestamp, level, and message
|
|
37
|
+
formatter = logging.Formatter(
|
|
38
|
+
fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
39
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
40
|
+
)
|
|
41
|
+
handler.setFormatter(formatter)
|
|
42
|
+
logger.addHandler(handler)
|
|
43
|
+
|
|
44
|
+
return logger
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def configure_logging(level: Optional[int] = None) -> None:
|
|
48
|
+
"""
|
|
49
|
+
Configure logging for the entire Aircall SDK.
|
|
50
|
+
|
|
51
|
+
This sets the logging level for all Aircall loggers.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
level: Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
55
|
+
If None, defaults to WARNING
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
>>> import logging
|
|
59
|
+
>>> from aircall.logging_config import configure_logging
|
|
60
|
+
>>> configure_logging(logging.DEBUG)
|
|
61
|
+
"""
|
|
62
|
+
if level is None:
|
|
63
|
+
level = logging.WARNING
|
|
64
|
+
|
|
65
|
+
# Configure the root aircall logger
|
|
66
|
+
root_logger = logging.getLogger('aircall')
|
|
67
|
+
root_logger.setLevel(level)
|
|
68
|
+
|
|
69
|
+
# If no handlers exist on root, add a default one
|
|
70
|
+
if not root_logger.handlers:
|
|
71
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
72
|
+
formatter = logging.Formatter(
|
|
73
|
+
fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
74
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
75
|
+
)
|
|
76
|
+
handler.setFormatter(formatter)
|
|
77
|
+
root_logger.addHandler(handler)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Create default logger for the SDK
|
|
81
|
+
logger = setup_logger('aircall')
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Aircall API models."""
|
|
2
|
+
|
|
3
|
+
# Core resources
|
|
4
|
+
from aircall.models.call import Call, CallComment
|
|
5
|
+
from aircall.models.company import Company
|
|
6
|
+
from aircall.models.contact import Contact, Email, PhoneNumber
|
|
7
|
+
from aircall.models.number import Number, NumberMessages
|
|
8
|
+
from aircall.models.tag import Tag
|
|
9
|
+
from aircall.models.team import Team
|
|
10
|
+
from aircall.models.user import User, UserAvailability
|
|
11
|
+
|
|
12
|
+
# AI and Intelligence
|
|
13
|
+
from aircall.models.ai_voice_agent import AIVoiceAgent
|
|
14
|
+
from aircall.models.content import (
|
|
15
|
+
ActionItemsContent,
|
|
16
|
+
Content,
|
|
17
|
+
SummaryContent,
|
|
18
|
+
TopicsContent,
|
|
19
|
+
Utterance,
|
|
20
|
+
)
|
|
21
|
+
from aircall.models.conversation_intelligence import (
|
|
22
|
+
ConversationIntelligence,
|
|
23
|
+
RealtimeTranscription,
|
|
24
|
+
RealtimeTranscriptionCall,
|
|
25
|
+
RealtimeTranscriptionUtterance,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# Communication
|
|
29
|
+
from aircall.models.message import MediaDetail, Message
|
|
30
|
+
from aircall.models.webhook import Webhook
|
|
31
|
+
|
|
32
|
+
# Campaign and Compliance
|
|
33
|
+
from aircall.models.dialer_campaign import DialerCampaign, DialerCampaignPhoneNumber
|
|
34
|
+
|
|
35
|
+
# Call-related
|
|
36
|
+
from aircall.models.ivr_option import IVROption
|
|
37
|
+
from aircall.models.participant import (
|
|
38
|
+
ConversationIntelligenceParticipant,
|
|
39
|
+
Participant,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Integration
|
|
43
|
+
from aircall.models.integration import Integration
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
# Core resources
|
|
47
|
+
"User",
|
|
48
|
+
"UserAvailability",
|
|
49
|
+
"Call",
|
|
50
|
+
"CallComment",
|
|
51
|
+
"Contact",
|
|
52
|
+
"PhoneNumber",
|
|
53
|
+
"Email",
|
|
54
|
+
"Number",
|
|
55
|
+
"NumberMessages",
|
|
56
|
+
"Team",
|
|
57
|
+
"Tag",
|
|
58
|
+
"Company",
|
|
59
|
+
# AI and Intelligence
|
|
60
|
+
"AIVoiceAgent",
|
|
61
|
+
"ConversationIntelligence",
|
|
62
|
+
"RealtimeTranscription",
|
|
63
|
+
"RealtimeTranscriptionCall",
|
|
64
|
+
"RealtimeTranscriptionUtterance",
|
|
65
|
+
"Content",
|
|
66
|
+
"Utterance",
|
|
67
|
+
"SummaryContent",
|
|
68
|
+
"TopicsContent",
|
|
69
|
+
"ActionItemsContent",
|
|
70
|
+
# Communication
|
|
71
|
+
"Message",
|
|
72
|
+
"MediaDetail",
|
|
73
|
+
"Webhook",
|
|
74
|
+
# Campaign and Compliance
|
|
75
|
+
"DialerCampaign",
|
|
76
|
+
"DialerCampaignPhoneNumber",
|
|
77
|
+
# Call-related
|
|
78
|
+
"Participant",
|
|
79
|
+
"ConversationIntelligenceParticipant",
|
|
80
|
+
"IVROption",
|
|
81
|
+
# Integration
|
|
82
|
+
"Integration",
|
|
83
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""AI Voice Agent models for Aircall API."""
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Any, Dict, Literal, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AIVoiceAgent(BaseModel):
|
|
9
|
+
"""
|
|
10
|
+
AI Voice Agent object representing calls handled by AI agents.
|
|
11
|
+
|
|
12
|
+
Accessible through webhook events:
|
|
13
|
+
- ai_voice_agent.started
|
|
14
|
+
- ai_voice_agent.ended
|
|
15
|
+
- ai_voice_agent.escalated
|
|
16
|
+
- ai_voice_agent.summary
|
|
17
|
+
|
|
18
|
+
Read-only. Not updatable or destroyable via API.
|
|
19
|
+
"""
|
|
20
|
+
id: int # Same value as call_id
|
|
21
|
+
call_id: int # Same value as id
|
|
22
|
+
call_uuid: str
|
|
23
|
+
ai_voice_agent_id: str
|
|
24
|
+
ai_voice_agent_name: str
|
|
25
|
+
ai_voice_agent_session_id: str
|
|
26
|
+
number_id: int
|
|
27
|
+
|
|
28
|
+
# Only for started/ended events
|
|
29
|
+
external_caller_number: Optional[str] = None
|
|
30
|
+
aircall_number: Optional[str] = None
|
|
31
|
+
|
|
32
|
+
# Timestamps (Unix timestamps)
|
|
33
|
+
created_at: datetime
|
|
34
|
+
started_at: Optional[datetime] = None # Only for started/ended events
|
|
35
|
+
ended_at: Optional[datetime] = None # Only for ended event
|
|
36
|
+
|
|
37
|
+
# Only for ended event
|
|
38
|
+
call_end_reason: Optional[Literal[
|
|
39
|
+
"answered",
|
|
40
|
+
"escalated",
|
|
41
|
+
"disconnected",
|
|
42
|
+
"caller_hung_up"
|
|
43
|
+
]] = None
|
|
44
|
+
|
|
45
|
+
# Only for escalated event
|
|
46
|
+
escalation_reason: Optional[str] = None
|
|
47
|
+
|
|
48
|
+
# Only for summary event - answers to intake questions
|
|
49
|
+
extracted_data: Optional[Dict[str, Any]] = None
|
aircall/models/call.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Call models for Aircall API."""
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from typing import Literal, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from aircall.models.contact import Contact
|
|
8
|
+
from aircall.models.ivr_option import IVROption
|
|
9
|
+
from aircall.models.number import Number
|
|
10
|
+
from aircall.models.participant import Participant
|
|
11
|
+
from aircall.models.tag import Tag
|
|
12
|
+
from aircall.models.team import Team
|
|
13
|
+
from aircall.models.user import User
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CallComment(BaseModel):
|
|
17
|
+
"""
|
|
18
|
+
Comment (Note) on a call.
|
|
19
|
+
|
|
20
|
+
Can be created by Agents or via API.
|
|
21
|
+
"""
|
|
22
|
+
id: int
|
|
23
|
+
content: str
|
|
24
|
+
posted_at: datetime
|
|
25
|
+
posted_by: Optional["User"] = None # Null if posted via API
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Call(BaseModel):
|
|
29
|
+
"""
|
|
30
|
+
Call resource representing phone interactions.
|
|
31
|
+
|
|
32
|
+
Three types:
|
|
33
|
+
- Inbound: External person → Agent
|
|
34
|
+
- Outbound: Agent → External person
|
|
35
|
+
- Internal: Agent → Agent (not in Public API)
|
|
36
|
+
|
|
37
|
+
Note: Call id is Int64 data type.
|
|
38
|
+
"""
|
|
39
|
+
id: int # Int64
|
|
40
|
+
sid: Optional[str] = None # Only in Call APIs (same as call_uuid)
|
|
41
|
+
call_uuid: Optional[str] = None # Only in Webhook events (same as sid)
|
|
42
|
+
direct_link: str
|
|
43
|
+
|
|
44
|
+
# Timestamps (Unix timestamps)
|
|
45
|
+
started_at: datetime
|
|
46
|
+
answered_at: Optional[datetime] = None # Null if not answered
|
|
47
|
+
ended_at: Optional[datetime] = None
|
|
48
|
+
|
|
49
|
+
duration: int # Seconds (ended_at - started_at, includes ringing)
|
|
50
|
+
status: Literal["initial", "answered", "done"]
|
|
51
|
+
direction: Literal["inbound", "outbound"]
|
|
52
|
+
raw_digits: str # International format or "anonymous"
|
|
53
|
+
|
|
54
|
+
# Media URLs (valid for limited time)
|
|
55
|
+
asset: Optional[str] = None # Secured webpage for recording/voicemail
|
|
56
|
+
recording: Optional[str] = None # Direct MP3 URL (1 hour validity)
|
|
57
|
+
recording_short_url: Optional[str] = None # Short URL (3 hours validity)
|
|
58
|
+
voicemail: Optional[str] = None # Direct MP3 URL (1 hour validity)
|
|
59
|
+
voicemail_short_url: Optional[str] = None # Short URL (3 hours validity)
|
|
60
|
+
|
|
61
|
+
archived: Optional[bool] = None
|
|
62
|
+
missed_call_reason: Optional[Literal[
|
|
63
|
+
"out_of_opening_hours",
|
|
64
|
+
"short_abandoned",
|
|
65
|
+
"abandoned_in_ivr",
|
|
66
|
+
"abandoned_in_classic",
|
|
67
|
+
"no_available_agent",
|
|
68
|
+
"agents_did_not_answer"
|
|
69
|
+
]] = None
|
|
70
|
+
|
|
71
|
+
cost: Optional[str] = None # Deprecated - U.S. cents
|
|
72
|
+
|
|
73
|
+
# Related objects
|
|
74
|
+
number: Optional["Number"] = None
|
|
75
|
+
user: Optional["User"] = None # Who took or made the call
|
|
76
|
+
contact: Optional["Contact"] = None
|
|
77
|
+
assigned_to: Optional["User"] = None
|
|
78
|
+
teams: list["Team"] = [] # Only for inbound calls
|
|
79
|
+
|
|
80
|
+
# Transfer information
|
|
81
|
+
transferred_by: Optional["User"] = None
|
|
82
|
+
transferred_to: Optional["User"] = None # First user of team if transferred to team
|
|
83
|
+
external_transferred_to: Optional[str] = None # Only via call.external_transferred event
|
|
84
|
+
external_caller_number: Optional[str] = None # Only via call.external_transferred event
|
|
85
|
+
|
|
86
|
+
# Collections
|
|
87
|
+
comments: list[CallComment] = []
|
|
88
|
+
tags: list["Tag"] = []
|
|
89
|
+
|
|
90
|
+
# Conference participants (referred as conference_participants in APIs)
|
|
91
|
+
participants: list[Participant] = []
|
|
92
|
+
|
|
93
|
+
# IVR options (requires fetch_call_timeline query param)
|
|
94
|
+
ivr_options_selected: list["IVROption"] = []
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Company model for Aircall API."""
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Company(BaseModel):
|
|
6
|
+
"""
|
|
7
|
+
Company object representing an Aircall company.
|
|
8
|
+
|
|
9
|
+
Read-only. Not updatable or destroyable via API.
|
|
10
|
+
Can only be modified via Aircall Dashboard.
|
|
11
|
+
"""
|
|
12
|
+
name: str
|
|
13
|
+
users_count: int
|
|
14
|
+
numbers_count: int
|