allternit-platform 0.2.0__tar.gz → 0.3.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: allternit-platform
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Python SDK for the Allternit Platform API (agents, conversations, numbers, messaging, webhooks, usage).
5
5
  License: Apache-2.0
6
6
  Project-URL: Documentation, https://docs.allternit.com/api/platform/sdks/python
@@ -33,21 +33,37 @@ StockVoice = Literal['af_alloy', 'af_aoede', 'af_bella', 'af_heart', 'af_jessica
33
33
  Conversation = TypedDict('Conversation', {'id': str, 'object': Literal['conversation'], 'agent_id': str, 'account_id': str, 'metadata': Dict[str, Any], 'created_at': str, 'messages': List["ConversationMessage"]}, total=False)
34
34
  ConversationMessage = TypedDict('ConversationMessage', {'id': str, 'object': Literal['conversation.message'], 'conversation_id': str, 'role': Literal['user', 'assistant'], 'content': str, 'status': Literal['completed', 'failed'], 'error': Optional[str], 'created_at': str}, total=False)
35
35
  ModelKey = TypedDict('ModelKey', {'provider': Literal['anthropic', 'openai', 'xai'], 'object': Literal['model_key'], 'masked': str, 'created_at': str, 'updated_at': str}, total=False)
36
- UsageRow = TypedDict('UsageRow', {'group': Optional[str], 'meter': Literal['voice_min_allternit', 'voice_min_byok', 'agent_month', 'tokens_in', 'tokens_out', 'number_local_month', 'number_tollfree_month', 'sms_segment', 'mms', 'registration_passthrough_cents', 'recording_min_month'], 'unit': Optional[str], 'quantity': float, 'events': int}, total=False)
36
+ ProjectTracing = TypedDict('ProjectTracing', {'object': Literal['project.tracing'], 'project_id': str, 'endpoint': str, 'header_names': List[str], 'service_name': str, 'enabled': bool, 'last_export_at': Optional[str], 'last_error': Optional[str], 'created_at': str, 'updated_at': str}, total=False)
37
+ UsageRow = TypedDict('UsageRow', {'group': Optional[str], 'meter': Literal['voice_min_allternit', 'voice_min_byok', 'agent_month', 'tokens_in', 'tokens_out', 'number_local_month', 'number_tollfree_month', 'sms_segment', 'mms', 'registration_passthrough_cents', 'recording_min_month', 'computer_minute', 'computer_action', 'mailbox_month', 'identity_month'], 'unit': Optional[str], 'quantity': float, 'events': int}, total=False)
37
38
  PhoneNumber = TypedDict('PhoneNumber', {'id': str, 'object': Literal['phone_number'], 'account_id': Optional[str], 'e164': str, 'type': Literal['local', 'toll_free'], 'sms_state': Literal['pending_registration', 'active', 'rejected', 'blocked'], 'simulated': bool, 'created_at': str}, total=False)
38
39
  PhoneNumberList = TypedDict('PhoneNumberList', {'data': List["PhoneNumber"], 'has_more': bool, 'next_cursor': Optional[str]}, total=False)
39
40
  Message = TypedDict('Message', {'id': str, 'object': Literal['message'], 'account_id': Optional[str], 'number_id': str, 'direction': Literal['inbound', 'outbound'], 'from': str, 'to': str, 'body': str, 'segments': int, 'status': Literal['sent', 'simulated', 'received', 'failed'], 'error_code': Optional[str], 'media': List[Dict[str, Any]], 'created_at': str}, total=False)
40
41
  MessageList = TypedDict('MessageList', {'data': List["Message"], 'has_more': bool, 'next_cursor': Optional[str]}, total=False)
41
42
  WebhookEndpoint = TypedDict('WebhookEndpoint', {'id': str, 'object': Literal['webhook_endpoint'], 'url': str, 'events': List[str], 'description': Optional[str], 'created_at': str}, total=False)
42
- Event = TypedDict('Event', {'id': str, 'object': Literal['event'], 'type': Literal['approval.requested', 'message.received', 'message.status', 'registration.updated', 'inbox.item.created', 'call.started', 'call.ended', 'call.transcript.ready', 'usage.threshold', 'webhook.test'], 'created': int, 'project_id': str, 'account_id': Optional[str], 'data': Dict[str, Any]}, total=False)
43
+ Event = TypedDict('Event', {'id': str, 'object': Literal['event'], 'type': Literal['approval.requested', 'message.received', 'message.status', 'registration.updated', 'inbox.item.created', 'call.started', 'call.ended', 'call.transcript.ready', 'usage.threshold', 'phone.step', 'phone.run.completed', 'phone.fleet_run.completed', 'mail.received', 'webhook.test'], 'created': int, 'project_id': str, 'account_id': Optional[str], 'data': Dict[str, Any]}, total=False)
43
44
  Computer = TypedDict('Computer', {'id': str, 'object': Literal['computer'], 'name': str, 'status': Literal['provisioning', 'starting', 'running', 'stopping', 'stopped', 'error', 'deleted'], 'account_id': Optional[str], 'key_id': str, 'created_at': str, 'started_at': Optional[str], 'metadata': Dict[str, Any]}, total=False)
44
45
  ComputerList = TypedDict('ComputerList', {'data': List["Computer"], 'has_more': bool, 'next_cursor': Optional[str]}, total=False)
45
46
  ComputerEvent = TypedDict('ComputerEvent', {'id': str, 'type': Literal['computer.action'], 'ts': str, 'data': Dict[str, Any]}, total=False)
46
47
  ComputerEventList = TypedDict('ComputerEventList', {'data': List["ComputerEvent"], 'has_more': bool, 'next_cursor': Optional[str]}, total=False)
47
48
  ComputerSettings = TypedDict('ComputerSettings', {'hosted_driver_enabled': bool, 'approval_mode': Literal['owner', 'api_key'], 'per_key_concurrency': int, 'browser_toolset': bool}, total=False)
48
- ToolsetCall = TypedDict('ToolsetCall', {'toolset': Literal['computer', 'browser'], 'member': str, 'input': Dict[str, Any], 'run_id': str, 'turn_id': str, 'call_index': int, 'model_frame': Dict[str, Any], 'coordinate_space': Literal['pixels', 'normalized_1000'], 'approval_grant': str, 'browser_session_id': str, 'enable': List[str]}, total=False)
49
+ ToolsetCall = TypedDict('ToolsetCall', {'toolset': Literal['computer', 'browser'], 'member': str, 'input': Dict[str, Any], 'run_id': str, 'turn_id': str, 'call_index': int, 'model_frame': Dict[str, Any], 'coordinate_space': Literal['pixels', 'normalized_1000'], 'approval_grant': str, 'browser_session_id': str, 'enable': List[str], 'agent_id': str, 'agent_name': str, 'step': str}, total=False)
49
50
  ToolsetResult = TypedDict('ToolsetResult', {'is_error': bool, 'content': List[Dict[str, Any]], 'browser_state': Dict[str, Any], 'screen': Dict[str, Any], 'error': str}, total=False)
50
51
  ToolsetApprovalRequired = TypedDict('ToolsetApprovalRequired', {'error': Dict[str, Any], 'approval': Dict[str, Any], 'result': "ToolsetResult"}, total=False)
52
+ Mailbox = TypedDict('Mailbox', {'id': str, 'object': Literal['mailbox'], 'account_id': str, 'agent_id': Optional[str], 'address': str, 'display_name': Optional[str], 'created_at': str}, total=False)
53
+ MailboxList = TypedDict('MailboxList', {'data': List["Mailbox"], 'has_more': bool, 'next_cursor': Optional[str]}, total=False)
54
+ MailMessage = TypedDict('MailMessage', {'id': str, 'object': Literal['mail_message'], 'mailbox_id': str, 'from': str, 'to': str, 'subject': Optional[str], 'snippet': Optional[str], 'codes': List[str], 'links': List[str], 'is_verification': bool, 'received_at': str}, total=False)
55
+ MailMessageList = TypedDict('MailMessageList', {'data': List["MailMessage"], 'has_more': bool, 'next_cursor': Optional[str]}, total=False)
56
+ Identity = TypedDict('Identity', {'id': str, 'object': Literal['identity'], 'account_id': str, 'agent_id': Optional[str], 'number': Union["PhoneNumber", Optional[Any]], 'mailbox': Union["Mailbox", Optional[Any]], 'phone_serial': Optional[str], 'bundle': Dict[str, Any], 'created_at': str}, total=False)
57
+ IdentityList = TypedDict('IdentityList', {'data': List["Identity"], 'has_more': bool, 'next_cursor': Optional[str]}, total=False)
58
+ Phone = TypedDict('Phone', {'object': Literal['phone'], 'serial': str, 'name': Optional[str], 'model': Optional[str], 'online': bool, 'desktop_online': bool, 'last_seen_at': Optional[str], 'call_bridge': Optional[Dict[str, Any]]}, total=False)
59
+ PhoneList = TypedDict('PhoneList', {'data': List["Phone"], 'has_more': bool, 'next_cursor': Optional[str]}, total=False)
60
+ PhoneSkillList = TypedDict('PhoneSkillList', {'data': List[Dict[str, Any]], 'has_more': bool, 'next_cursor': Optional[str]}, total=False)
61
+ PhoneStep = TypedDict('PhoneStep', {'object': Literal['phone.step'], 'id': str, 'run_id': str, 'serial': str, 'at': str, 'tool': str, 'label': str, 'args': Dict[str, Any], 'ok': bool, 'error': Optional[str], 'duration_ms': int, 'source': Optional[str], 'snapshot': bool}, total=False)
62
+ PhoneHistory = TypedDict('PhoneHistory', {'object': Literal['phone.history'], 'serial': str, 'steps': List["PhoneStep"], 'runs': List[Dict[str, Any]], 'retention_days': int, 'max_steps': int}, total=False)
63
+ PhoneLiveView = TypedDict('PhoneLiveView', {'object': Literal['phone.live_view'], 'id': str, 'serial': str, 'view_only': Literal[True], 'expires_in': int, 'expires_at': str, 'viewer_url': str, 'stream_url': str}, total=False)
64
+ PhoneFleetRun = TypedDict('PhoneFleetRun', {'object': Literal['phone.fleet_run'], 'id': str, 'kind': Literal['task', 'skill'], 'goal': Optional[str], 'skill': Optional[str], 'status': Literal['running', 'completed'], 'created_at': str, 'completed_at': Optional[str], 'counts': Dict[str, Any], 'runs': List[Dict[str, Any]], 'not_started': List[Dict[str, Any]]}, total=False)
65
+ PhoneRunStarted = TypedDict('PhoneRunStarted', {'object': Literal['phone.run'], 'run_id': str, 'serial': Optional[str], 'skill': Optional[str], 'status': Literal['running']}, total=False)
66
+ PhoneToolResult = TypedDict('PhoneToolResult', {'object': Literal['phone.tool_result'], 'ok': bool, 'run_id': str, 'tool': str, 'error': str, 'message': str}, total=False)
51
67
 
52
68
 
53
69
  class AccountsResource:
@@ -377,6 +393,44 @@ class ConversationsResource:
377
393
  _body['metadata'] = metadata
378
394
  return self._client.request("POST", f"/v1/agents/{_q(id)}/conversations", body=_body, idempotency_key=idempotency_key, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
379
395
 
396
+ def run_agent_ag_ui_stream(self, id: str, *, messages: List[Dict[str, Any]], thread_id: str = NOT_GIVEN, run_id: str = NOT_GIVEN, parent_run_id: str = NOT_GIVEN, tools: List[Dict[str, Any]] = NOT_GIVEN, context: List[Dict[str, Any]] = NOT_GIVEN, state: Any = NOT_GIVEN, forwarded_props: Any = NOT_GIVEN, idempotency_key: Optional[str] = None, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> Iterator[SSEEvent]:
397
+ """Run the agent for an AG-UI frontend
398
+
399
+ Runs one conversation turn over the AG-UI protocol (https://docs.ag-ui.com), so AG-UI
400
+ clients such as CopilotKit's `HttpAgent` can drive the agent. The body is AG-UI's
401
+ `RunAgentInput`. `threadId` is one of the agent's conversation ids, or any other string,
402
+ which is kept on the conversation the first run opens so later runs continue it; no
403
+ `threadId` opens a new conversation. The server keeps the history: the turn's input is
404
+ the last `user` message's text. Client-side `tools`, `context` and `state` are not used.
405
+ The answer is server-sent events, one `data:` JSON AG-UI event each: `RUN_STARTED`,
406
+ `TEXT_MESSAGE_START` / `TEXT_MESSAGE_CONTENT` / `TEXT_MESSAGE_END`, `TOOL_CALL_START` /
407
+ `TOOL_CALL_ARGS` / `TOOL_CALL_END` / `TOOL_CALL_RESULT` for each tool the agent uses,
408
+ then `RUN_FINISHED` or `RUN_ERROR` (`{message, code}`). `runId` is echoed (generated when
409
+ missing). Refusals before the stream starts are plain errors, as for messages:
410
+ `402 spend_cap_reached` / `payment_method_required`, `409 conversation_busy`,
411
+ `503 runtime_starting`. The reply is stored and metered like any conversation message.
412
+
413
+ ``POST /v1/agents/{id}/ag-ui``
414
+ """
415
+ _body: Dict[str, Any] = {}
416
+ if thread_id is not NOT_GIVEN:
417
+ _body['threadId'] = thread_id
418
+ if run_id is not NOT_GIVEN:
419
+ _body['runId'] = run_id
420
+ if parent_run_id is not NOT_GIVEN:
421
+ _body['parentRunId'] = parent_run_id
422
+ if messages is not NOT_GIVEN:
423
+ _body['messages'] = messages
424
+ if tools is not NOT_GIVEN:
425
+ _body['tools'] = tools
426
+ if context is not NOT_GIVEN:
427
+ _body['context'] = context
428
+ if state is not NOT_GIVEN:
429
+ _body['state'] = state
430
+ if forwarded_props is not NOT_GIVEN:
431
+ _body['forwardedProps'] = forwarded_props
432
+ return self._client.stream_request("POST", f"/v1/agents/{_q(id)}/ag-ui", body=_body, idempotency_key=idempotency_key, timeout=timeout, extra_headers=extra_headers)
433
+
380
434
  def get(self, id: str, *, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "Conversation":
381
435
  """Retrieve a conversation
382
436
 
@@ -390,8 +444,11 @@ class ConversationsResource:
390
444
  """Send a message
391
445
 
392
446
  Sends a message and answers with the agent's reply. With `stream: true` the answer is
393
- server-sent events: `message.delta` (`{"delta": "..."}`) while the agent writes, then
394
- `message.completed` (the stored reply) or `error`. The first message starts the
447
+ server-sent events: `message.delta` (`{"delta": "..."}`) while the agent writes,
448
+ `tool.started` / `tool.completed` / `tool.failed` (`{"tool_call_id", "name", "input"?,
449
+ "output"?, "error"?}`; arguments and results are cut at 8,000 characters) as it uses
450
+ tools, then `message.completed` (the stored reply) or `error`. Clients should skip
451
+ event names they don't know. The first message starts the
395
452
  project's hosted runtime: `503 runtime_starting` means retry in a few seconds.
396
453
  One message at a time per conversation (`409 conversation_busy`). A project at its
397
454
  monthly spend cap gets `402 spend_cap_reached`; one with no card on file gets
@@ -418,6 +475,49 @@ class UsageResource:
418
475
  def __init__(self, client: Transport) -> None:
419
476
  self._client = client
420
477
 
478
+ def get_project_tracing(self, id: str, *, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "ProjectTracing":
479
+ """The project's trace export settings
480
+
481
+ Where the project's spans go. Header values are never returned, only their names. `404 tracing_not_configured` when none is set. Requires the `usage` scope.
482
+
483
+ ``GET /v1/projects/{id}/tracing``
484
+ """
485
+ return self._client.request("GET", f"/v1/projects/{_q(id)}/tracing", timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
486
+
487
+ def put_project_tracing(self, id: str, *, endpoint: str, headers: Dict[str, Any] = NOT_GIVEN, service_name: str = NOT_GIVEN, enabled: bool = NOT_GIVEN, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "ProjectTracing":
488
+ """Set the project's trace export
489
+
490
+ Send a span for every agent turn, phone run and call of this project to an OTLP/HTTP collector (Langfuse, Arize Phoenix, any OpenTelemetry collector), as OTLP protobuf following the OpenTelemetry GenAI conventions. `endpoint` is the collector's OTLP base URL (`/v1/traces` is appended) or its full traces URL; it must be a public https URL. `headers` are encrypted at rest; leave them out to keep the stored ones. Export runs in the background and never fails or slows a request. Not available to account-bound keys.
491
+
492
+ ``PUT /v1/projects/{id}/tracing``
493
+ """
494
+ _body: Dict[str, Any] = {}
495
+ if endpoint is not NOT_GIVEN:
496
+ _body['endpoint'] = endpoint
497
+ if headers is not NOT_GIVEN:
498
+ _body['headers'] = headers
499
+ if service_name is not NOT_GIVEN:
500
+ _body['service_name'] = service_name
501
+ if enabled is not NOT_GIVEN:
502
+ _body['enabled'] = enabled
503
+ return self._client.request("PUT", f"/v1/projects/{_q(id)}/tracing", body=_body, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
504
+
505
+ def delete_project_tracing(self, id: str, *, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
506
+ """Turn off and forget the project's trace export
507
+
508
+ ``DELETE /v1/projects/{id}/tracing``
509
+ """
510
+ return self._client.request("DELETE", f"/v1/projects/{_q(id)}/tracing", timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
511
+
512
+ def test_project_tracing(self, id: str, *, idempotency_key: Optional[str] = None, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
513
+ """Send one test span
514
+
515
+ Sends one `allternit.tracing.test` span to the collector now and reports its answer.
516
+
517
+ ``POST /v1/projects/{id}/tracing/test``
518
+ """
519
+ return self._client.request("POST", f"/v1/projects/{_q(id)}/tracing/test", idempotency_key=idempotency_key, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
520
+
421
521
  def get_spend_cap(self, *, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "SpendCap":
422
522
  """The project's monthly spend cap and spend so far
423
523
 
@@ -702,7 +802,7 @@ class WebhooksResource:
702
802
  return self._client.request("GET", f"/v1/webhooks", query=q, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
703
803
  return self._client.paginate(_fetch, after if after is not NOT_GIVEN else None)
704
804
 
705
- def create(self, *, url: str, events: List[Literal['*', 'approval.requested', 'message.received', 'message.status', 'registration.updated', 'inbox.item.created', 'call.started', 'call.ended', 'call.transcript.ready', 'usage.threshold']], description: str = NOT_GIVEN, idempotency_key: Optional[str] = None, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
805
+ def create(self, *, url: str, events: List[Literal['*', 'approval.requested', 'message.received', 'message.status', 'registration.updated', 'inbox.item.created', 'call.started', 'call.ended', 'call.transcript.ready', 'usage.threshold', 'phone.step', 'phone.run.completed', 'phone.fleet_run.completed', 'mail.received']], description: str = NOT_GIVEN, idempotency_key: Optional[str] = None, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
706
806
  """Create a webhook endpoint
707
807
 
708
808
  `url` must be public https. The response carries `secret` once.
@@ -1123,7 +1223,7 @@ class ComputersResource:
1123
1223
  """
1124
1224
  return self._client.request("POST", f"/v1/computers/{_q(id)}/stop", idempotency_key=idempotency_key, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1125
1225
 
1126
- def call_toolset(self, id: str, *, toolset: Literal['computer', 'browser'], member: str, input: Dict[str, Any] = NOT_GIVEN, run_id: str = NOT_GIVEN, turn_id: str = NOT_GIVEN, call_index: int = NOT_GIVEN, model_frame: Dict[str, Any] = NOT_GIVEN, coordinate_space: Literal['pixels', 'normalized_1000'] = NOT_GIVEN, approval_grant: str = NOT_GIVEN, browser_session_id: str = NOT_GIVEN, enable: List[str] = NOT_GIVEN, idempotency_key: Optional[str] = None, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "ToolsetResult":
1226
+ def call_toolset(self, id: str, *, toolset: Literal['computer', 'browser'], member: str, input: Dict[str, Any] = NOT_GIVEN, run_id: str = NOT_GIVEN, turn_id: str = NOT_GIVEN, call_index: int = NOT_GIVEN, model_frame: Dict[str, Any] = NOT_GIVEN, coordinate_space: Literal['pixels', 'normalized_1000'] = NOT_GIVEN, approval_grant: str = NOT_GIVEN, browser_session_id: str = NOT_GIVEN, enable: List[str] = NOT_GIVEN, agent_id: str = NOT_GIVEN, agent_name: str = NOT_GIVEN, step: str = NOT_GIVEN, idempotency_key: Optional[str] = None, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "ToolsetResult":
1127
1227
  """Run one toolset call
1128
1228
 
1129
1229
  One `allternit.computer.v1` / `allternit.browser.v1` call (member names and inputs match Anthropic's `computer_toolset_20260801` / `browser_toolset_20260801`). Runs through the computer's executor: contract validation, control lease, policy, approval, audit, then the action. Action failures are 200 with `is_error: true`.
@@ -1154,6 +1254,12 @@ class ComputersResource:
1154
1254
  _body['browser_session_id'] = browser_session_id
1155
1255
  if enable is not NOT_GIVEN:
1156
1256
  _body['enable'] = enable
1257
+ if agent_id is not NOT_GIVEN:
1258
+ _body['agent_id'] = agent_id
1259
+ if agent_name is not NOT_GIVEN:
1260
+ _body['agent_name'] = agent_name
1261
+ if step is not NOT_GIVEN:
1262
+ _body['step'] = step
1157
1263
  return self._client.request("POST", f"/v1/computers/{_q(id)}/toolset", body=_body, idempotency_key=idempotency_key, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1158
1264
 
1159
1265
  def get_toolset_schema(self, id: str, *, toolset: Literal['computer', 'browser'] = NOT_GIVEN, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
@@ -1229,6 +1335,351 @@ class ComputersResource:
1229
1335
  return self._client.request("PATCH", f"/v1/computer_settings", body=_body, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1230
1336
 
1231
1337
 
1338
+ class PhonesResource:
1339
+ def __init__(self, client: Transport) -> None:
1340
+ self._client = client
1341
+
1342
+ def list(self, *, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "PhoneList":
1343
+ """List the owner's phones
1344
+
1345
+ The project owner's own phones, linked to their Allternit Desktop (Phone Link, adb or a USB Agent). Requires the `phones` scope and a key that is not bound to an account (403 `account_bound_key`).
1346
+ While the Desktop is offline the phones the cloud last saw are listed with `online: false` and `desktop_online: false`. Not paginated (a person links a handful).
1347
+
1348
+ ``GET /v1/phones``
1349
+ """
1350
+ return self._client.request("GET", f"/v1/phones", timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1351
+
1352
+ def list_skills(self, *, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "PhoneSkillList":
1353
+ """List saved phone skills
1354
+
1355
+ Recorded phone runs that replay with no model. Each lists the `inputs` a run must pass (typed text is never stored). 503 `phone_desktop_offline` while the Desktop is offline.
1356
+
1357
+ ``GET /v1/phones/skills``
1358
+ """
1359
+ return self._client.request("GET", f"/v1/phones/skills", timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1360
+
1361
+ def run_skill(self, name: str, *, serial: str = NOT_GIVEN, inputs: Dict[str, Any] = NOT_GIVEN, bot_id: str = NOT_GIVEN, brain: str = NOT_GIVEN, idempotency_key: Optional[str] = None, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "PhoneRunStarted":
1362
+ """Replay a phone skill
1363
+
1364
+ Starts the replay in the background and answers 202 with its `run_id`. Each step is a `phone.step` webhook; the end is `phone.run.completed`. A step whose element moved re-decides once on `bot_id`'s brain (or `brain`). Skills never send texts or place calls.
1365
+
1366
+ ``POST /v1/phones/skills/{name}/run``
1367
+ """
1368
+ _body: Dict[str, Any] = {}
1369
+ if serial is not NOT_GIVEN:
1370
+ _body['serial'] = serial
1371
+ if inputs is not NOT_GIVEN:
1372
+ _body['inputs'] = inputs
1373
+ if bot_id is not NOT_GIVEN:
1374
+ _body['bot_id'] = bot_id
1375
+ if brain is not NOT_GIVEN:
1376
+ _body['brain'] = brain
1377
+ return self._client.request("POST", f"/v1/phones/skills/{_q(name)}/run", body=_body, idempotency_key=idempotency_key, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1378
+
1379
+ def get_history(self, serial: str, *, run_id: str = NOT_GIVEN, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "PhoneHistory":
1380
+ """A phone's run history
1381
+
1382
+ Every phone tool call on this phone (500 steps / 30 days), newest run first. Typed text, numbers and message bodies are redacted on the Desktop; frames and screen text stay there.
1383
+
1384
+ ``GET /v1/phones/{serial}/history``
1385
+ """
1386
+ _query: Dict[str, Any] = {}
1387
+ if run_id is not NOT_GIVEN:
1388
+ _query['run_id'] = run_id
1389
+ return self._client.request("GET", f"/v1/phones/{_q(serial)}/history", query=_query, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1390
+
1391
+ def call_tool(self, serial: str, *, name: str, arguments: Dict[str, Any] = NOT_GIVEN, run_id: str = NOT_GIVEN, idempotency_key: Optional[str] = None, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "PhoneToolResult":
1392
+ """Run one phone tool
1393
+
1394
+ One phone tool through the Desktop phone gateway, exactly as a bot calls it: `phone.devices`, `phone.screenshot`, `phone.read_ui`, `phone.screen_text`, `phone.tap`, `phone.type`, `phone.find_and_tap`, `phone.swipe`, `phone.home`, `phone.back`, `phone.key`, `phone.open_app`, `phone.sms.read`, `phone.sms.send`, `phone.call.dial`. An iPhone running the Allternit app (serial `app:<phoneId>`) is screen only plus its in-app browser: `phone.screenshot`, `phone.devices` and `phone.web.open`, `phone.web.snapshot`, `phone.web.click`, `phone.web.type`, `phone.web.scroll`, `phone.web.back`, `phone.web.wait` (the app must be open; taps on native apps answer `not_supported`).
1395
+ `phone.sms.send` and `phone.call.dial` wait for the person to approve on their Desktop (up to 80 s; then 503 `phone_timeout`); a refusal is a 200 result with `ok: false, error: approval_denied`. Tool failures are 200 with `ok: false`. 409 `phone_offline` when the phone isn't connected, 503 `phone_desktop_offline` / `phone_gateway_unavailable` when the Desktop or its phone gateway isn't running. The call is a `phone.step` webhook like any other.
1396
+
1397
+ ``POST /v1/phones/{serial}/tool``
1398
+ """
1399
+ _body: Dict[str, Any] = {}
1400
+ if name is not NOT_GIVEN:
1401
+ _body['name'] = name
1402
+ if arguments is not NOT_GIVEN:
1403
+ _body['arguments'] = arguments
1404
+ if run_id is not NOT_GIVEN:
1405
+ _body['run_id'] = run_id
1406
+ return self._client.request("POST", f"/v1/phones/{_q(serial)}/tool", body=_body, idempotency_key=idempotency_key, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1407
+
1408
+ def run_task(self, serial: str, *, goal: str, max_steps: int = NOT_GIVEN, bot_id: str = NOT_GIVEN, brain: str = NOT_GIVEN, idempotency_key: Optional[str] = None, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "PhoneRunStarted":
1409
+ """Give a phone a goal
1410
+
1411
+ The phone task loop on the Desktop (read the screen, one action, repeat) with `bot_id`'s brain, `brain`, or the default. Answers 202 with the `run_id`; each step is a `phone.step` webhook and the end is `phone.run.completed`. Tasks never send texts or place calls. At most 5 minutes.
1412
+
1413
+ ``POST /v1/phones/{serial}/task``
1414
+ """
1415
+ _body: Dict[str, Any] = {}
1416
+ if goal is not NOT_GIVEN:
1417
+ _body['goal'] = goal
1418
+ if max_steps is not NOT_GIVEN:
1419
+ _body['max_steps'] = max_steps
1420
+ if bot_id is not NOT_GIVEN:
1421
+ _body['bot_id'] = bot_id
1422
+ if brain is not NOT_GIVEN:
1423
+ _body['brain'] = brain
1424
+ return self._client.request("POST", f"/v1/phones/{_q(serial)}/task", body=_body, idempotency_key=idempotency_key, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1425
+
1426
+ def get_live_view(self, serial: str, *, idempotency_key: Optional[str] = None, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "PhoneLiveView":
1427
+ """Watch a phone live
1428
+
1429
+ A view-only live view of the phone's screen for 10 minutes: `viewer_url` is a page you can open or put in an iframe, and `stream_url` is the raw stream (`{type:"meta"}` JSON, then H.264 Annex-B frames) for your own player. Both URLs carry the live view id as their only credential and stop working after `expires_in` seconds; neither can send input. A new view needs a new call. 409 `phone_offline` when the phone isn't connected to the Desktop.
1430
+
1431
+ ``POST /v1/phones/{serial}/live``
1432
+ """
1433
+ return self._client.request("POST", f"/v1/phones/{_q(serial)}/live", idempotency_key=idempotency_key, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1434
+
1435
+ def start_fleet_run(self, *, idempotency_key: Optional[str] = None, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
1436
+ """Run one task or skill on many phones
1437
+
1438
+ Starts one background run per listed phone: a task (`goal`) or a saved skill (`skill`). Answers 202 with the fleet run; phones whose run could not start are in `not_started` with the error. Every run counts against the plan's concurrent phone-run limit (Sandbox 1, Pay as you go 5, Growth 25, Enterprise by contract); asking for more than the room left is 429 `phone_run_limit` and starts nothing. Each run ends with `phone.run.completed`; the whole fleet run with `phone.fleet_run.completed`.
1439
+
1440
+ ``POST /v1/phones/runs``
1441
+ """
1442
+ return self._client.request("POST", f"/v1/phones/runs", idempotency_key=idempotency_key, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1443
+
1444
+ def get_fleet_run(self, id: str, *, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
1445
+ """A fleet run
1446
+
1447
+ The fleet run and each phone's run (status, ok, start and end).
1448
+
1449
+ ``GET /v1/phones/runs/{id}``
1450
+ """
1451
+ return self._client.request("GET", f"/v1/phones/runs/{_q(id)}", timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1452
+
1453
+
1454
+ class MailboxesResource:
1455
+ def __init__(self, client: Transport) -> None:
1456
+ self._client = client
1457
+
1458
+ def list(self, *, limit: int = NOT_GIVEN, after: str = NOT_GIVEN, account_id: str = NOT_GIVEN, agent_id: str = NOT_GIVEN, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "MailboxList":
1459
+ """List mailboxes
1460
+
1461
+ ``GET /v1/mailboxes``
1462
+ """
1463
+ _query: Dict[str, Any] = {}
1464
+ if limit is not NOT_GIVEN:
1465
+ _query['limit'] = limit
1466
+ if after is not NOT_GIVEN:
1467
+ _query['after'] = after
1468
+ if account_id is not NOT_GIVEN:
1469
+ _query['account_id'] = account_id
1470
+ if agent_id is not NOT_GIVEN:
1471
+ _query['agent_id'] = agent_id
1472
+ return self._client.request("GET", f"/v1/mailboxes", query=_query, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1473
+
1474
+ def list_all(self, *, limit: int = NOT_GIVEN, after: str = NOT_GIVEN, account_id: str = NOT_GIVEN, agent_id: str = NOT_GIVEN, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> Iterator["Mailbox"]:
1475
+ """Every item of ``list``, fetching pages as you iterate."""
1476
+ _query: Dict[str, Any] = {}
1477
+ if limit is not NOT_GIVEN:
1478
+ _query['limit'] = limit
1479
+ if after is not NOT_GIVEN:
1480
+ _query['after'] = after
1481
+ if account_id is not NOT_GIVEN:
1482
+ _query['account_id'] = account_id
1483
+ if agent_id is not NOT_GIVEN:
1484
+ _query['agent_id'] = agent_id
1485
+ def _fetch(cursor: Optional[str]) -> Page:
1486
+ q = dict(_query)
1487
+ if cursor is not None:
1488
+ q['after'] = cursor
1489
+ return self._client.request("GET", f"/v1/mailboxes", query=q, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1490
+ return self._client.paginate(_fetch, after if after is not NOT_GIVEN else None)
1491
+
1492
+ def create_mailbox(self, *, account_id: str = NOT_GIVEN, agent_id: str = NOT_GIVEN, local_part: str = NOT_GIVEN, display_name: str = NOT_GIVEN, idempotency_key: Optional[str] = None, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "Mailbox":
1493
+ """Create a mailbox for an account or agent
1494
+
1495
+ An address on Allternit Mail (`local_part@bots.allternit.com`; `-2`, `-3`… when taken). With `agent_id`, the account is the agent's. Needs a card on file; live projects are billed `mailbox_month` ($2 a month). Requires the `mail` scope.
1496
+
1497
+ ``POST /v1/mailboxes``
1498
+ """
1499
+ _body: Dict[str, Any] = {}
1500
+ if account_id is not NOT_GIVEN:
1501
+ _body['account_id'] = account_id
1502
+ if agent_id is not NOT_GIVEN:
1503
+ _body['agent_id'] = agent_id
1504
+ if local_part is not NOT_GIVEN:
1505
+ _body['local_part'] = local_part
1506
+ if display_name is not NOT_GIVEN:
1507
+ _body['display_name'] = display_name
1508
+ return self._client.request("POST", f"/v1/mailboxes", body=_body, idempotency_key=idempotency_key, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1509
+
1510
+ def get_mailbox(self, id: str, *, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "Mailbox":
1511
+ """Retrieve a mailbox
1512
+
1513
+ ``GET /v1/mailboxes/{id}``
1514
+ """
1515
+ return self._client.request("GET", f"/v1/mailboxes/{_q(id)}", timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1516
+
1517
+ def delete_mailbox(self, id: str, *, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
1518
+ """Delete a mailbox
1519
+
1520
+ Removes the address. A mailbox inside an identity answers `409 mailbox_in_identity`; delete the identity instead.
1521
+
1522
+ ``DELETE /v1/mailboxes/{id}``
1523
+ """
1524
+ return self._client.request("DELETE", f"/v1/mailboxes/{_q(id)}", timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1525
+
1526
+ def list_mail_messages(self, id: str, *, limit: int = NOT_GIVEN, after: str = NOT_GIVEN, verification: bool = NOT_GIVEN, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "MailMessageList":
1527
+ """List a mailbox's messages
1528
+
1529
+ Newest first. Each carries the one-time codes and sign-in links found in it.
1530
+
1531
+ ``GET /v1/mailboxes/{id}/messages``
1532
+ """
1533
+ _query: Dict[str, Any] = {}
1534
+ if limit is not NOT_GIVEN:
1535
+ _query['limit'] = limit
1536
+ if after is not NOT_GIVEN:
1537
+ _query['after'] = after
1538
+ if verification is not NOT_GIVEN:
1539
+ _query['verification'] = verification
1540
+ return self._client.request("GET", f"/v1/mailboxes/{_q(id)}/messages", query=_query, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1541
+
1542
+ def list_mail_messages_all(self, id: str, *, limit: int = NOT_GIVEN, after: str = NOT_GIVEN, verification: bool = NOT_GIVEN, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> Iterator["MailMessage"]:
1543
+ """Every item of ``list_mail_messages``, fetching pages as you iterate."""
1544
+ _query: Dict[str, Any] = {}
1545
+ if limit is not NOT_GIVEN:
1546
+ _query['limit'] = limit
1547
+ if after is not NOT_GIVEN:
1548
+ _query['after'] = after
1549
+ if verification is not NOT_GIVEN:
1550
+ _query['verification'] = verification
1551
+ def _fetch(cursor: Optional[str]) -> Page:
1552
+ q = dict(_query)
1553
+ if cursor is not None:
1554
+ q['after'] = cursor
1555
+ return self._client.request("GET", f"/v1/mailboxes/{_q(id)}/messages", query=q, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1556
+ return self._client.paginate(_fetch, after if after is not NOT_GIVEN else None)
1557
+
1558
+ def get_mail_message(self, id: str, mid: str, *, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
1559
+ """Read one message
1560
+
1561
+ ``GET /v1/mailboxes/{id}/messages/{mid}``
1562
+ """
1563
+ return self._client.request("GET", f"/v1/mailboxes/{_q(id)}/messages/{_q(mid)}", timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1564
+
1565
+ def read_mail_otp(self, id: str, *, wait_seconds: int = NOT_GIVEN, since: str = NOT_GIVEN, idempotency_key: Optional[str] = None, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
1566
+ """Wait for the newest one-time code
1567
+
1568
+ The newest message with a code or sign-in link received after `since` (default the last 10 minutes), waiting up to `wait_seconds` for one to arrive. `found` is false when none came.
1569
+
1570
+ ``POST /v1/mailboxes/{id}/otp``
1571
+ """
1572
+ _body: Dict[str, Any] = {}
1573
+ if wait_seconds is not NOT_GIVEN:
1574
+ _body['wait_seconds'] = wait_seconds
1575
+ if since is not NOT_GIVEN:
1576
+ _body['since'] = since
1577
+ return self._client.request("POST", f"/v1/mailboxes/{_q(id)}/otp", body=_body, idempotency_key=idempotency_key, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1578
+
1579
+ def send_mail(self, id: str, *, serials: List[str], goal: str = NOT_GIVEN, skill: str = NOT_GIVEN, inputs: Dict[str, Any] = NOT_GIVEN, max_steps: int = NOT_GIVEN, bot_id: str = NOT_GIVEN, brain: str = NOT_GIVEN, idempotency_key: Optional[str] = None, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "PhoneFleetRun":
1580
+ """Send an email from a mailbox
1581
+
1582
+ Live projects only. The call is the approval, so the send is queued at once; `pending_approval` only when Allternit Mail held it and could not release it.
1583
+
1584
+ ``POST /v1/mailboxes/{id}/send``
1585
+ """
1586
+ _body: Dict[str, Any] = {}
1587
+ if serials is not NOT_GIVEN:
1588
+ _body['serials'] = serials
1589
+ if goal is not NOT_GIVEN:
1590
+ _body['goal'] = goal
1591
+ if skill is not NOT_GIVEN:
1592
+ _body['skill'] = skill
1593
+ if inputs is not NOT_GIVEN:
1594
+ _body['inputs'] = inputs
1595
+ if max_steps is not NOT_GIVEN:
1596
+ _body['max_steps'] = max_steps
1597
+ if bot_id is not NOT_GIVEN:
1598
+ _body['bot_id'] = bot_id
1599
+ if brain is not NOT_GIVEN:
1600
+ _body['brain'] = brain
1601
+ return self._client.request("POST", f"/v1/mailboxes/{_q(id)}/send", body=_body, idempotency_key=idempotency_key, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1602
+
1603
+
1604
+ class IdentitiesResource:
1605
+ def __init__(self, client: Transport) -> None:
1606
+ self._client = client
1607
+
1608
+ def list(self, *, limit: int = NOT_GIVEN, after: str = NOT_GIVEN, account_id: str = NOT_GIVEN, agent_id: str = NOT_GIVEN, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "IdentityList":
1609
+ """List agent identities
1610
+
1611
+ ``GET /v1/identities``
1612
+ """
1613
+ _query: Dict[str, Any] = {}
1614
+ if limit is not NOT_GIVEN:
1615
+ _query['limit'] = limit
1616
+ if after is not NOT_GIVEN:
1617
+ _query['after'] = after
1618
+ if account_id is not NOT_GIVEN:
1619
+ _query['account_id'] = account_id
1620
+ if agent_id is not NOT_GIVEN:
1621
+ _query['agent_id'] = agent_id
1622
+ return self._client.request("GET", f"/v1/identities", query=_query, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1623
+
1624
+ def list_all(self, *, limit: int = NOT_GIVEN, after: str = NOT_GIVEN, account_id: str = NOT_GIVEN, agent_id: str = NOT_GIVEN, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> Iterator["Identity"]:
1625
+ """Every item of ``list``, fetching pages as you iterate."""
1626
+ _query: Dict[str, Any] = {}
1627
+ if limit is not NOT_GIVEN:
1628
+ _query['limit'] = limit
1629
+ if after is not NOT_GIVEN:
1630
+ _query['after'] = after
1631
+ if account_id is not NOT_GIVEN:
1632
+ _query['account_id'] = account_id
1633
+ if agent_id is not NOT_GIVEN:
1634
+ _query['agent_id'] = agent_id
1635
+ def _fetch(cursor: Optional[str]) -> Page:
1636
+ q = dict(_query)
1637
+ if cursor is not None:
1638
+ q['after'] = cursor
1639
+ return self._client.request("GET", f"/v1/identities", query=q, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1640
+ return self._client.paginate(_fetch, after if after is not NOT_GIVEN else None)
1641
+
1642
+ def create_identity(self, *, account_id: str = NOT_GIVEN, agent_id: str = NOT_GIVEN, area_code: str = NOT_GIVEN, number_type: Literal['local', 'toll_free'] = NOT_GIVEN, phone_serial: str = NOT_GIVEN, local_part: str = NOT_GIVEN, display_name: str = NOT_GIVEN, idempotency_key: Optional[str] = None, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "Identity":
1643
+ """Give an agent a number, a mailbox and a phone in one call
1644
+
1645
+ Gets a number (simulated in sandbox; in live the first available, in `area_code` when given), a mailbox and, with `phone_serial`, binds one of the owner's linked phones. Requires the `numbers` and `mail` scopes, plus `phones` (and an unbound key) with `phone_serial`. Billed as the Agent identity bundle ($9 a month, meter `identity_month`).
1646
+
1647
+ ``POST /v1/identities``
1648
+ """
1649
+ _body: Dict[str, Any] = {}
1650
+ if account_id is not NOT_GIVEN:
1651
+ _body['account_id'] = account_id
1652
+ if agent_id is not NOT_GIVEN:
1653
+ _body['agent_id'] = agent_id
1654
+ if area_code is not NOT_GIVEN:
1655
+ _body['area_code'] = area_code
1656
+ if number_type is not NOT_GIVEN:
1657
+ _body['number_type'] = number_type
1658
+ if phone_serial is not NOT_GIVEN:
1659
+ _body['phone_serial'] = phone_serial
1660
+ if local_part is not NOT_GIVEN:
1661
+ _body['local_part'] = local_part
1662
+ if display_name is not NOT_GIVEN:
1663
+ _body['display_name'] = display_name
1664
+ return self._client.request("POST", f"/v1/identities", body=_body, idempotency_key=idempotency_key, timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1665
+
1666
+ def get_identity(self, id: str, *, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> "Identity":
1667
+ """Retrieve an identity
1668
+
1669
+ ``GET /v1/identities/{id}``
1670
+ """
1671
+ return self._client.request("GET", f"/v1/identities/{_q(id)}", timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1672
+
1673
+ def delete_identity(self, id: str, *, timeout: Optional[float] = None, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, Any]:
1674
+ """Delete an identity
1675
+
1676
+ Releases the number and removes the mailbox.
1677
+
1678
+ ``DELETE /v1/identities/{id}``
1679
+ """
1680
+ return self._client.request("DELETE", f"/v1/identities/{_q(id)}", timeout=timeout, extra_headers=extra_headers) # type: ignore[return-value]
1681
+
1682
+
1232
1683
  def _q(v: str) -> str:
1233
1684
  from urllib.parse import quote
1234
1685
  return quote(str(v), safe='')
@@ -1250,3 +1701,6 @@ class GeneratedResources:
1250
1701
  self.channels = ChannelsResource(client)
1251
1702
  self.twin = TwinResource(client)
1252
1703
  self.computers = ComputersResource(client)
1704
+ self.phones = PhonesResource(client)
1705
+ self.mailboxes = MailboxesResource(client)
1706
+ self.identities = IdentitiesResource(client)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: allternit-platform
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Python SDK for the Allternit Platform API (agents, conversations, numbers, messaging, webhooks, usage).
5
5
  License: Apache-2.0
6
6
  Project-URL: Documentation, https://docs.allternit.com/api/platform/sdks/python
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "allternit-platform"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Python SDK for the Allternit Platform API (agents, conversations, numbers, messaging, webhooks, usage)."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"