diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e26248..6caa2fa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,19 +1,20 @@
# Changelog
-## Unreleased
-
-- Clarify request-level webhook configuration and purchased-number requirements for batch calls.
-
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
-## [Unreleased]
+## [1.0.0] - 2026-09-23
### Changed
-- Clarify placeholder API keys and redaction of credentials and private call data in SDK documentation.
+- Move `client.calls` to the single-target Calls API with required phone, result schema and idempotency key.
+- Support optional region/language hints, cancellation before submission, and detailed call events.
+- Expose Billing call ID, call outcome, result readiness and recorded transcript turns.
+- Stop Calls and Goal Run wait helpers when result readiness is final, including unavailable results with no error.
+- This is a breaking Calls migration. Retain SDK 0.7.x for historical legacy call-task IDs; see the public migration guide.
+- Clarify webhook configuration, legacy batch-call requirements, and redaction of credentials and private call data.
## [0.7.1] - 2026-09-04
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index c3d08ce..7c23b59 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -51,14 +51,13 @@ In scope:
- List call events.
- List and read published Goals.
- Create a Goal Run with a durable idempotency key.
-- Poll until a Goal Run has either a result or an error.
+- Poll until a Goal Run's `result_status` is no longer `pending`.
- Receive finalized terminal webhook events without requiring signature
material.
Out of scope:
- Async client support.
-- Cancel calls.
- Recurring or scheduled calls.
- Goal authoring and publishing.
- Project-level webhook management.
diff --git a/README.md b/README.md
index f73f2b8..4e15610 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,11 @@ Python server SDK for the CALL-E Developer API.
Use this SDK from backend services, workers, and other trusted server
environments. Do not expose CALL-E API keys in browser code.
+SDK 1.0 uses the single-target Calls API and the same result model as Goal Runs. A required
+closed scalar-object result schema defines the business fields. SDK wait helpers
+continue while `result_status` is `pending`, even after execution reaches `completed`.
+Empty `{}` is a ready result. Webhook data matches the persisted GET snapshot.
+
## Documentation
- Developer docs:
@@ -32,7 +37,7 @@ environments. Do not expose CALL-E API keys in browser code.
Install the stable package from PyPI:
```bash
-pip install calle-ai
+pip install calle-ai==1.0.0
```
For reproducible deployments, pin the package version selected by your
@@ -194,47 +199,64 @@ else:
```
Persist the idempotency key before the first request and reuse it for network
-retries. `wait_for_result` returns when either `result` or `error` is non-null;
+retries. `wait_for_result` returns when `result_status` is no longer `pending`;
an execution `status` of `completed` can still be waiting for result
materialization.
-The generic one-shot call API remains available independently:
-
-```python
-import os
-from calle import CalleClient
+## Migration to SDK 1.0
-client = CalleClient(
- api_key=os.environ["CALLE_API_KEY"],
- base_url="https://api.heycall-e.com",
-)
+The `calls` wrapper now submits one explicit phone to `/v2/calls` and requires
+an idempotency key. Keep the key for retries. The backend continues serving
+legacy integrations until their planned retirement at the end of 2026; keep
+SDK 0.7.x for historical legacy call IDs. Upgrade Goal Run integrations to 1.0
+as well: old wait helpers can time out when both `result` and `error` are null.
+```python
call = client.calls.create_and_wait(
- task="Call each recipient and ask whether they can attend Friday lunch in San Francisco.",
- recipients=[{"phones": ["+14155550100"], "region": "US", "locale": "en-US"}],
+ task="Ask whether Friday lunch is confirmed.",
+ phone="", region="US", locale="en-US",
result_schema={
- "type": "object",
- "required": ["completed_count"],
- "properties": {
- "completed_count": {"type": "integer"},
- },
- },
- recipient_result_schema={
- "type": "object",
- "required": ["can_attend"],
- "properties": {
- "can_attend": {"type": "string", "enum": ["yes", "no", "unknown"]},
- },
+ "type": "object", "additionalProperties": False, "required": ["answer"],
+ "properties": {"answer": {"type": "string", "enum": ["yes", "no", "unknown"]}},
},
- metadata={"workflow_run_id": "wf_123"},
- idempotency_key="wf_123_friday_lunch",
+ idempotency_key="lunch:friday:confirmation:v1",
)
-
-print(call["status"], call["structured_result"])
-print(call["task_completed"], call["completion_confidence"], call["evidence"])
-print(call["recipients"][0]["structured_result"])
+if call["error"] is None:
+ print(call["result"])
+else:
+ print(call["error"])
+# Cancel a queued call before provider submission:
+# client.calls.cancel(call_id)
```
+Replace `recipient` / `recipients` with `phone` and optional `region` and `locale`.
+Define the required `result_schema` (`resultSchema` in TypeScript) using Goal's
+`calle.result.scalar-object.v1` profile: at most 32 scalar properties and
+`additionalProperties: false`. Flatten old nested fields; arrays and null values
+are unsupported. Old `structured_result`, `result_error`, summary,
+confidence and provider attempt fields are removed. Request business summaries or
+completion flags explicitly as scalar fields in the schema when needed.
+The Calls API does not support batch, scheduled or recurring calls.
+
+Calls and Goal Runs always expose `transcript`, an array of recorded turns with
+`speaker` (`bot`, `user`, `unknown`), nullable `offset_seconds`, and `text`.
+Before execution ends or without available transcript, the array is empty.
+A pending, unavailable or failed business result does not remove an available
+terminal transcript. Webhook data uses the same shape.
+
+The returned object is `call`. Consume `result` and `error` exactly as for Goal Runs.
+Poll while result_status is pending (resultStatus in TypeScript). No-answer, busy
+and declined are ordinary call_outcome values (callOutcome in TypeScript).
+Unavailable business evidence uses result_status=unavailable and error=null.
+Explicit schema-valid task fallbacks remain results. Technical failures use error.
+Cancellation uses result_status=not_applicable and no error. GET reads committed state and
+terminal webhooks contain the same ready snapshot. Deduplicate by event id.
+
+Cancellation returns `409 call_cannot_cancel` after provider submission begins.
+Calls accept immediate execution only. If authorization expires after submission,
+`error.detail_code=authorization_expired` means the provider may still complete the call; do not
+create an automatic replacement call.
+
## Error handling
The SDK exports errors for API responses, authentication, rate limits,
diff --git a/examples/create_and_wait.py b/examples/create_and_wait.py
index b60596f..8ffaa82 100644
--- a/examples/create_and_wait.py
+++ b/examples/create_and_wait.py
@@ -11,23 +11,12 @@ def main() -> None:
)
call = client.calls.create_and_wait(
- task="Call each recipient and ask whether they can attend Friday lunch in San Francisco.",
- recipients=[
- {
- "phones": [os.environ.get("CALLE_EXAMPLE_PHONE", "+14155550100")],
- "region": "US",
- "locale": "en-US",
- }
- ],
+ task="Call the recipient and ask whether they can attend Friday lunch in San Francisco.",
+ phone=os.environ.get("CALLE_EXAMPLE_PHONE", "+14155550100"),
+ region="US", locale="en-US",
result_schema={
"type": "object",
- "required": ["completed_count"],
- "properties": {
- "completed_count": {"type": "integer"},
- },
- },
- recipient_result_schema={
- "type": "object",
+ "additionalProperties": False,
"required": ["can_attend"],
"properties": {
"can_attend": {"type": "string", "enum": ["yes", "no", "unknown"]},
diff --git a/examples/webhook_server.py b/examples/webhook_server.py
index eb0cee1..32a9e3a 100644
--- a/examples/webhook_server.py
+++ b/examples/webhook_server.py
@@ -87,11 +87,8 @@ def do_POST(self) -> None:
"Call completed",
{
"call_id": call_id,
- "result": call.get("structured_result"),
- "summary": call.get("summary"),
- "task_completed": call.get("task_completed"),
- "completion_confidence": call.get("completion_confidence"),
- "evidence": call.get("evidence"),
+ "result": call.get("result"),
+ "error": call.get("error"),
},
)
else:
diff --git a/openapi-python-client.yml b/openapi-python-client.yml
index 2ece704..8d028b7 100644
--- a/openapi-python-client.yml
+++ b/openapi-python-client.yml
@@ -1,6 +1,6 @@
project_name_override: calle-generated
package_name_override: generated
-package_version_override: 0.7.1
+package_version_override: 1.0.0
literal_enums: true
generate_all_tags: true
use_path_prefixes_for_title_model_names: false
diff --git a/openapi/calle.openapi.yaml b/openapi/calle.openapi.yaml
index 00bd716..9df3972 100644
--- a/openapi/calle.openapi.yaml
+++ b/openapi/calle.openapi.yaml
@@ -1,21 +1,252 @@
openapi: 3.1.0
info:
title: CALL-E Developer API
- version: 0.7.1
- description: Developer API contract used by the CALL-E TypeScript and Python SDKs.
+ version: 1.0.0
+ description: >-
+ Developer API contract used by the CALL-E TypeScript and Python SDKs.
+ Calls and Goal Runs expose call_outcome, result_status and a fixed transcript
+ array. SDK 1.0 supports the single-target Calls API. Legacy call tasks remain
+ available during the migration period; see the public migration guide.
servers:
- url: https://api.heycall-e.com
- description: Placeholder developer API base URL.
+ description: Production developer API.
security:
- bearerAuth: []
+tags:
+ - name: calls
+ x-displayName: Calls
+ description: Create, track, and read single-target calls.
+ - name: legacy-calls
+ x-displayName: Legacy Calls (Deprecated)
+ description: >-
+ Deprecated. v1 Calls will retire on December 31, 2026 (2026-12-31 16:00 UTC / 2027-01-01 00:00 Asia/Shanghai). See the [migration guide](/migration). Goal APIs are unaffected.
paths:
+ /v2/calls:
+ post:
+ operationId: createAgenticCall
+ tags:
+ - calls
+ summary: Create Call
+ description: >-
+ Prepare one phone task, then return 202 after durable acceptance. Dialing runs
+ in the background; acceptance does not prove connection or business success.
+ Region and locale are optional or null. Infer region from the phone and spoken
+ locale from task intent and supported regional languages; responses contain
+ the resolved values. Explicit locale is preserved. Conflicting phone/region
+ or ambiguous language returns 422 input_incomplete with details.missing_inputs;
+ phone/region conflicts also include details.fields, region and inferred_region.
+ Unsupported targets return 422 unsupported_region or unsupported_language with
+ details.field, region and locale. Explicit targets are checked before preparation;
+ inferred locale is checked after inference. These rejections create no Call.
+ Unsupported result_schema types, including nullable unions, return 400
+ result_schema_invalid. Preparation is bounded to 25 seconds; configuration access
+ failures, conflicting profiles, model failures or timeouts return 503 provider_unavailable.
+ Accepted instructions and input are immutable and reused on replay. Calls accept
+ one phone and immediate execution only: no batch recipients, scheduling, recurrence
+ or recipient_result_schema.
+ parameters:
+ - name: Idempotency-Key
+ in: header
+ required: true
+ description: >-
+ Persist one key and the original request per logical call. Same project,
+ authenticated owner, key and input return 202 with the original Call ID
+ and its current saved state, including after completion, failure or cancellation.
+ Replay does not prepare or dial again. An intentionally new call requires
+ a new key. Changes to task, phone, region, locale, result_schema, metadata
+ or webhook_url return 409 idempotency_conflict; JSON object-key order is
+ not significant. Keep omitted region/locale omitted on retries; adding
+ inferred response values changes the input. Concurrent creation returns
+ 409 idempotency_conflict with details.reason_code=creation_in_progress:
+ back off and retry unchanged. A definitive validation rejection before
+ acceptance creates no Call and permits corrected input with the same key.
+ After a timeout or uncertain response, reuse the original key and body,
+ not a new key. Keys must be 1-255 characters after trimming.
+ schema:
+ type: string
+ minLength: 1
+ maxLength: 255
+ responses:
+ '202':
+ description: Task prepared and Call durably accepted, or an exact replay returning the same Call ID and current saved state. No duplicate dialing on replay.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AgenticCall'
+ '400': &id001
+ $ref: '#/components/responses/ErrorResponse'
+ '401': *id001
+ '402': *id001
+ '403': *id001
+ '409':
+ description: Creation is still in progress, or the key belongs to another request or owner. Do not automatically retry with a new key.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorEnvelope'
+ examples:
+ creation_in_progress:
+ value:
+ error:
+ code: idempotency_conflict
+ message: Call creation is still in progress. Retry with the same idempotency key and request.
+ details:
+ reason_code: creation_in_progress
+ changed_request:
+ value:
+ error:
+ code: idempotency_conflict
+ message: Idempotency key belongs to another request.
+ details: {}
+ '422':
+ description: The requested region/locale is unsupported, essential task information is missing, or the request violates calling policy. No Call is created.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ErrorEnvelope'
+ examples:
+ unsupported_region:
+ value:
+ error:
+ code: unsupported_region
+ message: Region 'ZZ' is not supported for calls. Choose a supported region and locale.
+ details:
+ field: region
+ region: ZZ
+ locale: en-US
+ unsupported_language:
+ value:
+ error:
+ code: unsupported_language
+ message: Locale 'xx-XX' is not supported for region 'CN'. Choose a supported locale for this region.
+ details:
+ field: locale
+ region: CN
+ locale: xx-XX
+ input_incomplete:
+ value:
+ error:
+ code: input_incomplete
+ message: The task lacks information required to make this call.
+ details:
+ missing_inputs:
+ - Provide the appointment date.
+ - Provide the appointment time.
+ '429': *id001
+ '503': *id001
+ '500': *id001
+ requestBody:
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CreateAgenticCallRequest'
+ /v2/calls/{call_id}:
+ get:
+ operationId: getAgenticCall
+ tags:
+ - calls
+ summary: Get Call
+ description: Polling reads persisted state and never performs extraction or initiates a call.
+ parameters:
+ - &id002
+ name: call_id
+ in: path
+ required: true
+ schema:
+ type: string
+ description: Developer Call id returned by v2.
+ responses:
+ '200':
+ description: Committed response.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AgenticCall'
+ '400': *id001
+ '401': *id001
+ '403': *id001
+ '404': *id001
+ '409': *id001
+ '500': *id001
+ /v2/calls/{call_id}/cancel:
+ post:
+ operationId: cancelAgenticCall
+ tags:
+ - calls
+ summary: Cancel Call
+ description: Cancellation is idempotent for terminal calls. Once provider submission starts, returns 409 call_cannot_cancel; it does not hang up an active call.
+ parameters:
+ - *id002
+ responses:
+ '200':
+ description: Committed response.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/AgenticCall'
+ '400': *id001
+ '401': *id001
+ '403': *id001
+ '404': *id001
+ '409': *id001
+ '500': *id001
+ /v2/calls/{call_id}/events:
+ get:
+ operationId: listAgenticCallEvents
+ tags:
+ - calls
+ summary: List Events
+ description: >-
+ Poll durable JSON event pages, not SSE. Events include call.accepted,
+ call.in_progress, call.ringing, call.connected, call.asr, call.speech,
+ call.interrupted, call.dtmf, call.ended, call.result_ready, and
+ call.completed/failed/canceled. Not every call emits every event type.
+ Speech details preserve observed text, speaker, turn and optional occurred_at.
+ ASR may contain partial fragments or revised hypotheses; no is_final flag
+ or final-sentence guarantee is provided. Do not concatenate every update into
+ a transcript. Deduplicate by event ID, not turn or text. Persistence order
+ includes late arrivals; IDs remain stable. Follow next_cursor for additional
+ available pages. Retain the last event ID for later polls even when next_cursor
+ is null, and keep it on empty pages. Empty pages do not mean completion.
+ Existing lifecycle cursors remain valid. Invalid or foreign cursors return
+ 400 invalid_request. Reads do not dial, infer or persist anything.
+ parameters:
+ - *id002
+ - name: cursor
+ in: query
+ description: Opaque next_cursor or last returned event ID; scoped to this call. Retain the saved cursor on an empty page.
+ schema:
+ type: string
+ - name: limit
+ in: query
+ description: Page size, 1-100. Invalid or out-of-range values return 400 invalid_request.
+ schema:
+ type: integer
+ default: 50
+ minimum: 1
+ maximum: 100
+ responses:
+ '200':
+ description: Committed response.
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/EventList'
+ '400': *id001
+ '401': *id001
+ '403': *id001
+ '404': *id001
+ '409': *id001
+ '500': *id001
/v1/calls:
post:
operationId: createCall
tags:
- - calls
+ - legacy-calls
+ deprecated: true
summary: Create Call
- description: Create an asynchronous call. Use `result_schema` and `recipient_result_schema` to ask CALL-E to extract structured JSON results from terminal call evidence.
+ description: Create an asynchronous call. Use `result_schema` and `recipient_result_schema` to ask CALL-E to extract structured JSON results from terminal call evidence. Shared platform outbound lines support one phone number per task. Batch calls require an eligible purchased number selected as the account default outbound number; otherwise creation returns `422 call_not_ready`. Account concurrency and LLM token usage are controlled by the effective account configuration. Task concurrency defaults to 1 on shared platform lines and 10 on eligible dedicated purchased numbers; selecting a purchased number as the account default does not make it a shared platform line.
parameters:
- $ref: "#/components/parameters/IdempotencyKey"
requestBody:
@@ -26,7 +257,7 @@ paths:
$ref: "#/components/schemas/CreateCallRequest"
examples:
batchLunch:
- summary: Check Friday lunch availability for two recipients.
+ summary: Check Friday lunch availability for two recipients using an eligible purchased outbound number.
value:
task: Call each recipient and ask whether they can attend Friday lunch in San Francisco.
recipients:
@@ -172,14 +403,36 @@ paths:
"422":
$ref: "#/components/responses/ErrorResponse"
"429":
- $ref: "#/components/responses/ErrorResponse"
+ description: An account concurrency limit (`account_concurrency_exceeded`), or LLM token budget (`llm_token_budget_exceeded`) was reached. Account admission is checked before planning. Concurrency errors identify the effective line type and limit. Shared-line errors include KYC and dedicated-number purchase guidance in the message and `details.upgrade`; purchased-line errors ask the user to wait for capacity without another purchase suggestion. Configured limits override the default 1 / 10 values.
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorEnvelope"
+ example:
+ error:
+ code: account_concurrency_exceeded
+ message: Your default shared line (such as us/all) is at its account concurrency limit of 1. This limit is shared across API, MCP, and Dashboard. Wait for an active task to finish, then retry. For up to 10 concurrent tasks, complete identity verification (KYC), purchase a dedicated phone number, and select the activated number as your default outbound number. [Verify identity and buy a number](https://dashboard.heycall-e.com/account/numbers/buy).
+ details:
+ line_type: platform_default
+ max_active_tasks: 1
+ upgrade:
+ max_active_tasks: 10
+ kyc_required: true
+ purchase_url: https://dashboard.heycall-e.com/account/numbers/buy
"500":
$ref: "#/components/responses/ErrorResponse"
+ "503":
+ description: The provider or an account control is unavailable (`provider_unavailable`, `account_concurrency_unavailable`, or `llm_token_budget_unavailable`).
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/ErrorEnvelope"
/v1/calls/{call_id}:
get:
operationId: getCall
tags:
- - calls
+ - legacy-calls
+ deprecated: true
summary: Get Call
description: Get a call by id.
parameters:
@@ -255,8 +508,9 @@ paths:
get:
operationId: listCallEvents
tags:
- - calls
- summary: List Call Events
+ - legacy-calls
+ deprecated: true
+ summary: List Events
description: List developer-facing call events.
parameters:
- $ref: "#/components/parameters/CallId"
@@ -525,6 +779,9 @@ paths:
error: null
created_at: "2026-07-22T10:00:00Z"
completed_at: null
+ call_outcome: null
+ result_status: pending
+ transcript: []
"400":
$ref: "#/components/responses/ErrorResponse"
"401":
@@ -560,10 +817,9 @@ paths:
Goal pointer, dispatch work, or start result materialization. Use the `GoalRun.id` returned
by create as `goal_run_id`; the nested telephone `run_id` is not valid in this path.
- Poll until either `result` or `error` is non-null. A non-null `result` is the parsed object
- validated against the published result schema. A non-null `error` means this Run will not
- produce a result. `status: completed` with both fields null means result processing is still
- in progress.
+ Poll while `result_status` is `pending`. A non-null `result` is the parsed object validated
+ against the published result schema. `unavailable` means evidence did not support a business
+ result; no-answer, busy and declined calls are ordinary `call_outcome` values, not errors.
parameters:
- $ref: "#/components/parameters/GoalId"
- $ref: "#/components/parameters/GoalRunId"
@@ -596,6 +852,8 @@ paths:
run_id: run_delivery_ord_8472
call_id: calling_call_delivery_ord_8472
status: completed
+ call_outcome: completed
+ result_status: available
run_spec:
id: rspec_delivery_v4
version: 4
@@ -605,6 +863,7 @@ paths:
error: null
created_at: "2026-07-22T10:00:00Z"
completed_at: "2026-07-22T10:01:12Z"
+ transcript: []
deliveryConfirmationNoAnswer:
summary: No human answered, so no business result is available.
value:
@@ -613,17 +872,17 @@ paths:
goal_id: goal_delivery_confirmation
run_id: run_delivery_ord_8472
call_id: null
- status: failed
+ status: completed
+ call_outcome: no_answer
+ result_status: unavailable
run_spec:
id: rspec_delivery_v4
version: 4
result: null
- error:
- code: no_answer
- message: No human answered the call.
- detail_code: no_human_answered
+ error: null
created_at: "2026-07-22T10:00:00Z"
completed_at: "2026-07-22T10:01:12Z"
+ transcript: []
"401":
$ref: "#/components/responses/ErrorResponse"
"403":
@@ -644,7 +903,7 @@ paths:
tags:
- webhooks
summary: Server Message
- description: CALL-E sends this request after a call reaches a terminal state and its post-call outcome and requested structured results are finalized. Configure your receiver URL with `webhook_url` when creating a call.
+ description: CALL-E sends this request after a call reaches a terminal state and its post-call outcome and requested structured results are finalized. Configure this URL with `webhook_url` on create call or through project-level webhook settings.
security: []
servers:
- url: https://{yourserver}
@@ -660,7 +919,7 @@ paths:
content:
application/json:
schema:
- $ref: "#/components/schemas/WebhookEvent"
+ $ref: "#/components/schemas/TerminalWebhookEvent"
examples:
callCompleted:
summary: Completed US Friday lunch webhook.
@@ -738,7 +997,7 @@ components:
IdempotencyKey:
name: Idempotency-Key
in: header
- description: Stable caller-provided key used to make create-call retries safe. Reusing the same key with the same request returns the original call instead of creating a duplicate.
+ description: Stable caller-provided key used to make create-call retries safe. Reusing the same key with the same request returns the original call instead of creating a duplicate. A persisted creation failure replays its original HTTP status and error body; use a new key for a new attempt after resolving the error. While creation is still in progress, a duplicate returns `409 idempotency_conflict` with `details.reason_code=creation_in_progress`. A different request using the same key returns `409 idempotency_conflict`.
required: false
schema:
type: string
@@ -842,6 +1101,155 @@ components:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
schemas:
+ CreateAgenticCallRequest:
+ additionalProperties: false
+ properties:
+ task:
+ minLength: 1
+ type: string
+ phone:
+ pattern: ^\+[1-9][0-9]{7,14}$
+ type: string
+ region:
+ anyOf:
+ - type: string
+ pattern: ^[A-Z]{2}$
+ - type: 'null'
+ description: Optional destination region. Inferred from the phone when omitted.
+ An explicit region conflicting with the phone requires corrected input.
+ locale:
+ anyOf:
+ - type: string
+ minLength: 2
+ maxLength: 64
+ - type: 'null'
+ description: Optional spoken BCP-47 locale. Inferred from task intent and available
+ regional languages when omitted.
+ result_schema:
+ type: object
+ additionalProperties: true
+ description: Required closed flat JSON Schema using the same calle.result.scalar-object.v1
+ profile as Goal. At most 32 string, boolean, integer or number properties; additionalProperties
+ must be false. Nested objects, arrays, null values and schema combinators are
+ unsupported.
+ metadata:
+ additionalProperties: true
+ type: object
+ webhook_url:
+ anyOf:
+ - format: uri
+ minLength: 1
+ type: string
+ - type: 'null'
+ required:
+ - task
+ - phone
+ - result_schema
+ type: object
+ description: One phone, optional region and spoken locale, and a required Goal-compatible
+ result schema.
+ AgenticCall:
+ additionalProperties: false
+ properties:
+ id:
+ type: string
+ description: API Call resource ID. Use this ID for API paths and event correlation, not the telephone ID in Billing.
+ call_id:
+ type: [string, 'null']
+ description: Telephone call ID shown in Billing, using the same identity as Goal Run call_id. Always present; null until recorded, including cancellation before dialing. Independent of business result availability. Does not indicate whether charges have settled. Use id, not this field, for API paths.
+ object:
+ const: call
+ type: string
+ status:
+ $ref: '#/components/schemas/GoalRunStatus'
+ call_outcome:
+ $ref: '#/components/schemas/CallOutcome'
+ result_status:
+ $ref: '#/components/schemas/BusinessResultStatus'
+ transcript:
+ type: array
+ items:
+ $ref: '#/components/schemas/CallTranscriptTurn'
+ description: Recorded conversation turns in order, independent of the business result. Always present; empty before terminal execution or when no transcript is available. Never generated from result_schema.
+ task:
+ type: string
+ phone:
+ type: string
+ region:
+ type: string
+ locale:
+ type: string
+ result:
+ type:
+ - object
+ - 'null'
+ description: Result validated against result_schema and durably persisted. Null while pending, unavailable, not applicable, or on a technical error. Explicit schema-valid task fallbacks are preserved.
+ additionalProperties:
+ $ref: '#/components/schemas/GoalScalar'
+ error:
+ description: Technical execution or result-processing error, or null. No-answer, busy, declined, cancellation and insufficient business evidence do not populate error.
+ oneOf:
+ - $ref: '#/components/schemas/GoalRunError'
+ - type: 'null'
+ metadata:
+ additionalProperties: true
+ type: object
+ created_at:
+ format: date-time
+ type: string
+ completed_at:
+ type:
+ - string
+ - 'null'
+ format: date-time
+ description: UTC telephone-execution completion time, or `null` while execution is non-terminal.
+ required:
+ - id
+ - call_id
+ - object
+ - status
+ - call_outcome
+ - result_status
+ - transcript
+ - task
+ - phone
+ - region
+ - locale
+ - result
+ - error
+ - metadata
+ - created_at
+ - completed_at
+ type: object
+ description: Persisted one-shot snapshot sharing execution, call_outcome and result_status with Goal Run. Poll only while result_status is pending. Completed execution does not imply business success. Terminal webhooks are sent when result_status is no longer pending, including unavailable results.
+ TerminalWebhookEvent:
+ description: Terminal event for either a legacy v1 call task or an Agentic v2 call. Inspect data.object to distinguish the payload.
+ anyOf:
+ - $ref: '#/components/schemas/WebhookEvent'
+ - $ref: '#/components/schemas/AgenticWebhookEvent'
+ AgenticWebhookEvent:
+ type: object
+ required:
+ - id
+ - type
+ - created_at
+ - data
+ additionalProperties: false
+ properties:
+ id:
+ type: string
+ type:
+ type: string
+ enum:
+ - call.completed
+ - call.failed
+ - call.canceled
+ created_at:
+ type: string
+ format: date-time
+ data:
+ $ref: '#/components/schemas/AgenticCall'
+ description: v2 terminal webhook. data is identical to the terminal GET response. A valid call completion can carry a failed custom result. Retries reuse the same event id.
GoalList:
type: object
description: |-
@@ -988,9 +1396,8 @@ components:
GoalRun:
type: object
description: |-
- Public projection of one phone-specific execution of a published Goal. A non-null `result`
- is a successfully parsed and persisted object. A non-null `error` means the Run will not
- produce a result. When both are null, continue polling.
+ Public projection of one phone-specific execution of a published Goal. Execution, telephone
+ outcome and business result readiness are independent. Poll only while result_status is pending.
additionalProperties: false
required:
- object
@@ -1000,6 +1407,9 @@ components:
- call_id
- run_spec
- status
+ - call_outcome
+ - result_status
+ - transcript
- result
- error
- created_at
@@ -1037,19 +1447,28 @@ components:
$ref: "#/components/schemas/GoalRunSpecSnapshot"
status:
$ref: "#/components/schemas/GoalRunStatus"
+ call_outcome:
+ $ref: '#/components/schemas/CallOutcome'
+ result_status:
+ $ref: '#/components/schemas/BusinessResultStatus'
+ transcript:
+ type: array
+ items:
+ $ref: '#/components/schemas/CallTranscriptTurn'
+ description: Recorded conversation turns in order, independent of business result readiness or errors. Always present; empty before terminal execution or when no transcript is available.
result:
type:
- object
- "null"
description: |-
Parsed result validated against the published result schema and durably persisted, or
- `null` while processing or when the Run has an error. Its keys vary by Goal.
+ `null` while pending, unavailable, not applicable, or on a technical error. Its keys vary by Goal.
additionalProperties:
$ref: "#/components/schemas/GoalScalar"
error:
description: |-
- Unified execution or result-processing error, or `null`. Branch on `code`; keep `message`
- for logs and operators. A non-null error is final and is mutually exclusive with `result`.
+ Technical execution or result-processing error, or null. Ordinary telephone outcomes,
+ cancellation and insufficient business evidence are represented by call_outcome/result_status.
oneOf:
- $ref: "#/components/schemas/GoalRunError"
- type: "null"
@@ -1082,18 +1501,26 @@ components:
GoalRunStatus:
type: string
description: |-
- Stable telephone execution state. `queued` and `in_progress` are non-terminal; `completed`,
- `failed`, and `canceled` are terminal. A completed call can still have `result: null` and
- `error: null` briefly while CALL-E parses and saves the result.
+ Stable execution state. No-answer, busy and declined calls complete execution normally.
+ Technical execution failures are failed; explicit cancellation is canceled. Completed execution
+ may still have result_status=pending while its business result is being processed.
enum:
- queued
- in_progress
- completed
- failed
- canceled
+ CallOutcome:
+ type: [ string, 'null' ]
+ enum: [ completed, no_answer, busy, declined, null ]
+ description: Reported telephone outcome, separate from business success and billing connection evidence. Null before a telephone outcome is known or when execution is canceled or fails technically.
+ BusinessResultStatus:
+ type: string
+ enum: [ pending, available, unavailable, not_applicable ]
+ description: Pending means keep polling. Available includes an empty result object. Unavailable means no schema-valid business result could be produced; inspect error for technical failures. Not applicable is used for cancellation and technical execution failure.
GoalRunError:
type: object
- description: Unified safe error returned when a Goal Run cannot produce a usable result.
+ description: Technical execution or result-processing error. Ordinary call outcomes are not errors.
additionalProperties: false
required:
- code
@@ -1104,12 +1531,8 @@ components:
type: string
enum:
- call_failed
- - no_answer
- - declined
- timed_out
- - canceled
- result_invalid
- - result_unavailable
- result_failed
message:
type: string
@@ -1135,7 +1558,7 @@ components:
type:
- array
- "null"
- description: Optional explicit recipients for this call task. Omit it when the task text already contains the phone targets CALL-E should use.
+ description: Optional explicit recipients for this call task. Omit it when the task text already contains the phone targets CALL-E should use. The default outbound line permits one phone number in total across all recipients. Multiple targets require an eligible purchased outbound number; this also applies to targets inferred from task text.
minItems: 1
items:
$ref: "#/components/schemas/CallTaskRecipientRequest"
@@ -1175,7 +1598,7 @@ components:
additionalProperties: true
webhook_url:
type: string
- description: Optional per-request HTTPS webhook URL. When provided, CALL-E sends terminal call events to this URL after result finalization.
+ description: Optional per-request HTTPS webhook URL. When provided, CALL-E sends terminal call events to this URL in addition to project-level webhook delivery.
format: uri
CallTaskRecipientRequest:
type: object
@@ -1574,8 +1997,7 @@ components:
$ref: "#/components/schemas/WebhookCallData"
WebhookCallData:
description: Complete terminal call task state included in webhook events. This shape is the same stable `call_task` object returned by the calls API after post-call outcome and requested structured-result finalization.
- allOf:
- - $ref: "#/components/schemas/CallTask"
+ $ref: "#/components/schemas/CallTask"
WebhookAcknowledgement:
type: object
description: Example acknowledgement response from your webhook receiver. CALL-E treats any 2xx response as delivered and ignores the response body.
@@ -1603,10 +2025,16 @@ components:
code:
type: string
enum:
+ - call_cannot_cancel
- invalid_request
+ - input_incomplete
- unauthorized
- forbidden
- rate_limit_exceeded
+ - account_concurrency_exceeded
+ - account_concurrency_unavailable
+ - llm_token_budget_exceeded
+ - llm_token_budget_unavailable
- insufficient_balance
- unsupported_region
- unsupported_language
diff --git a/pyproject.toml b/pyproject.toml
index 6b9c9aa..9a38862 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name = "calle-ai"
-version = "0.7.1"
+version = "1.0.0"
description = "Python server SDK for the CALL-E Developer API."
readme = "README.md"
license = "MIT"
diff --git a/scripts/verify_openapi_contract.py b/scripts/verify_openapi_contract.py
index 6904716..27a63b7 100644
--- a/scripts/verify_openapi_contract.py
+++ b/scripts/verify_openapi_contract.py
@@ -49,7 +49,7 @@ def main() -> None:
"unexpected title",
)
assert_contract(
- spec.get("info", {}).get("version") == "0.7.1",
+ spec.get("info", {}).get("version") == "1.0.0",
"unexpected API version",
)
@@ -123,7 +123,7 @@ def main() -> None:
"path": "/calle/webhook",
"method": "post",
"operation_id": "receiveWebhookEvent",
- "request_schema": "#/components/schemas/WebhookEvent",
+ "request_schema": "#/components/schemas/TerminalWebhookEvent",
"response_schema": "#/components/schemas/WebhookAcknowledgement",
"error_statuses": [],
},
@@ -155,7 +155,8 @@ def main() -> None:
for status in operation["error_statuses"]:
assert_contract(
endpoint.get("responses", {}).get(status, {}).get("$ref")
- == "#/components/responses/ErrorResponse",
+ == "#/components/responses/ErrorResponse"
+ or response_schema_ref(spec, path, method, status) == "#/components/schemas/ErrorEnvelope",
f"missing stable {status} error response for {label}",
)
@@ -209,8 +210,8 @@ def main() -> None:
assert_contract(schema_name in schemas, f"missing schema {schema_name}")
assert_contract(
- schemas["WebhookCallData"].get("allOf")
- == [{"$ref": "#/components/schemas/CallTask"}],
+ schemas["WebhookCallData"].get("$ref")
+ == "#/components/schemas/CallTask",
"webhook data must reuse the complete call task shape",
)
terminal_result_fields = {
diff --git a/src/calle/calls.py b/src/calle/calls.py
index a3cb6ee..f855cb1 100644
--- a/src/calle/calls.py
+++ b/src/calle/calls.py
@@ -18,31 +18,31 @@ def create(
self,
*,
task: str,
- recipient: JsonObject | None = None,
- recipients: list[JsonObject] | None = None,
- result_schema: JsonObject | None = None,
- recipient_result_schema: JsonObject | None = None,
+ phone: str,
+ region: str | None = None,
+ locale: str | None = None,
+ idempotency_key: str,
+ result_schema: JsonObject,
metadata: JsonObject | None = None,
webhook_url: str | None = None,
- idempotency_key: str | None = None,
) -> JsonObject:
- if recipient is not None and recipients is not None:
- raise ValueError("Pass either recipient or recipients, not both.")
+ if not idempotency_key.strip():
+ raise ValueError("A stable idempotency_key is required.")
body = {
- "task": task,
- "recipients": [_normalize_recipient(recipient)] if recipient is not None else recipients,
+ "task": task, "phone": phone, "region": region, "locale": locale,
"result_schema": result_schema,
- "recipient_result_schema": recipient_result_schema,
- "metadata": metadata,
- "webhook_url": webhook_url,
+ "metadata": metadata, "webhook_url": webhook_url,
}
payload = {key: value for key, value in body.items() if value is not None}
- headers = {"Idempotency-Key": idempotency_key} if idempotency_key else None
- return self._request("POST", "/v1/calls", json=payload, headers=headers)
+ headers = {"Idempotency-Key": idempotency_key}
+ return self._request("POST", "/v2/calls", json=payload, headers=headers)
def get(self, call_id: str) -> JsonObject:
return self._request("GET", _call_path(call_id))
+ def cancel(self, call_id: str) -> JsonObject:
+ return self._request("POST", f"{_call_path(call_id)}/cancel")
+
def list_events(self, call_id: str, *, cursor: str | None = None, limit: int | None = None) -> JsonObject:
params = {key: value for key, value in {"cursor": cursor, "limit": limit}.items() if value is not None}
return self._request("GET", f"{_call_path(call_id)}/events", params=params)
@@ -57,7 +57,7 @@ def wait_for_result(
deadline = time.monotonic() + timeout_seconds
while time.monotonic() <= deadline:
call = self.get(call_id)
- if call.get("status") in {"completed", "failed", "canceled"}:
+ if call["result_status"] != "pending":
return call
time.sleep(interval_seconds)
raise CalleTimeoutError(f"Timed out waiting for CALL-E call {call_id}.")
@@ -88,15 +88,6 @@ def _request(self, method: str, path: str, **kwargs: Any) -> JsonObject:
return payload
-def _normalize_recipient(recipient: JsonObject) -> JsonObject:
- if "phones" in recipient:
- return recipient
- phone = recipient.get("phone")
- normalized = {key: value for key, value in recipient.items() if key != "phone"}
- normalized["phones"] = [phone] if phone is not None else []
- return normalized
-
-
def _call_path(call_id: str) -> str:
encoded_call_id = quote(call_id, safe="").replace(".", "%2E")
- return f"/v1/calls/{encoded_call_id}"
+ return f"/v2/calls/{encoded_call_id}"
diff --git a/src/calle/generated/api/calls/cancel_agentic_call.py b/src/calle/generated/api/calls/cancel_agentic_call.py
new file mode 100644
index 0000000..cbbed15
--- /dev/null
+++ b/src/calle/generated/api/calls/cancel_agentic_call.py
@@ -0,0 +1,199 @@
+from http import HTTPStatus
+from typing import Any
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response
+from ... import errors
+
+from ...models.agentic_call import AgenticCall
+from ...models.error_envelope import ErrorEnvelope
+
+
+def _get_kwargs(
+ call_id: str,
+) -> dict[str, Any]:
+
+ _kwargs: dict[str, Any] = {
+ "method": "post",
+ "url": "/v2/calls/{call_id}/cancel".format(
+ call_id=quote(str(call_id), safe="").replace(".", "%2E"),
+ ),
+ }
+
+ return _kwargs
+
+
+def _parse_response(
+ *, client: AuthenticatedClient | Client, response: httpx.Response
+) -> AgenticCall | ErrorEnvelope | None:
+ if response.status_code == 200:
+ response_200 = AgenticCall.from_dict(response.json())
+
+ return response_200
+
+ if response.status_code == 400:
+ response_400 = ErrorEnvelope.from_dict(response.json())
+
+ return response_400
+
+ if response.status_code == 401:
+ response_401 = ErrorEnvelope.from_dict(response.json())
+
+ return response_401
+
+ if response.status_code == 403:
+ response_403 = ErrorEnvelope.from_dict(response.json())
+
+ return response_403
+
+ if response.status_code == 404:
+ response_404 = ErrorEnvelope.from_dict(response.json())
+
+ return response_404
+
+ if response.status_code == 409:
+ response_409 = ErrorEnvelope.from_dict(response.json())
+
+ return response_409
+
+ if response.status_code == 500:
+ response_500 = ErrorEnvelope.from_dict(response.json())
+
+ return response_500
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def _build_response(
+ *, client: AuthenticatedClient | Client, response: httpx.Response
+) -> Response[AgenticCall | ErrorEnvelope]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ call_id: str,
+ *,
+ client: AuthenticatedClient | Client,
+) -> Response[AgenticCall | ErrorEnvelope]:
+ """Cancel Call
+
+ Cancellation is idempotent for terminal calls. Once provider submission starts, returns 409
+ call_cannot_cancel; it does not hang up an active call.
+
+ Args:
+ call_id (str):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[AgenticCall | ErrorEnvelope]
+ """
+
+ kwargs = _get_kwargs(
+ call_id=call_id,
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return _build_response(client=client, response=response)
+
+
+def sync(
+ call_id: str,
+ *,
+ client: AuthenticatedClient | Client,
+) -> AgenticCall | ErrorEnvelope | None:
+ """Cancel Call
+
+ Cancellation is idempotent for terminal calls. Once provider submission starts, returns 409
+ call_cannot_cancel; it does not hang up an active call.
+
+ Args:
+ call_id (str):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ AgenticCall | ErrorEnvelope
+ """
+
+ return sync_detailed(
+ call_id=call_id,
+ client=client,
+ ).parsed
+
+
+async def asyncio_detailed(
+ call_id: str,
+ *,
+ client: AuthenticatedClient | Client,
+) -> Response[AgenticCall | ErrorEnvelope]:
+ """Cancel Call
+
+ Cancellation is idempotent for terminal calls. Once provider submission starts, returns 409
+ call_cannot_cancel; it does not hang up an active call.
+
+ Args:
+ call_id (str):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[AgenticCall | ErrorEnvelope]
+ """
+
+ kwargs = _get_kwargs(
+ call_id=call_id,
+ )
+
+ response = await client.get_async_httpx_client().request(**kwargs)
+
+ return _build_response(client=client, response=response)
+
+
+async def asyncio(
+ call_id: str,
+ *,
+ client: AuthenticatedClient | Client,
+) -> AgenticCall | ErrorEnvelope | None:
+ """Cancel Call
+
+ Cancellation is idempotent for terminal calls. Once provider submission starts, returns 409
+ call_cannot_cancel; it does not hang up an active call.
+
+ Args:
+ call_id (str):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ AgenticCall | ErrorEnvelope
+ """
+
+ return (
+ await asyncio_detailed(
+ call_id=call_id,
+ client=client,
+ )
+ ).parsed
diff --git a/src/calle/generated/api/calls/create_agentic_call.py b/src/calle/generated/api/calls/create_agentic_call.py
new file mode 100644
index 0000000..5cfefda
--- /dev/null
+++ b/src/calle/generated/api/calls/create_agentic_call.py
@@ -0,0 +1,269 @@
+from http import HTTPStatus
+from typing import Any
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response
+from ... import errors
+
+from ...models.agentic_call import AgenticCall
+from ...models.create_agentic_call_request import CreateAgenticCallRequest
+from ...models.error_envelope import ErrorEnvelope
+
+
+def _get_kwargs(
+ *,
+ body: CreateAgenticCallRequest,
+ idempotency_key: str,
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+ headers["Idempotency-Key"] = idempotency_key
+
+ _kwargs: dict[str, Any] = {
+ "method": "post",
+ "url": "/v2/calls",
+ }
+
+ _kwargs["json"] = body.to_dict()
+
+ headers["Content-Type"] = "application/json"
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+def _parse_response(
+ *, client: AuthenticatedClient | Client, response: httpx.Response
+) -> AgenticCall | ErrorEnvelope | None:
+ if response.status_code == 202:
+ response_202 = AgenticCall.from_dict(response.json())
+
+ return response_202
+
+ if response.status_code == 400:
+ response_400 = ErrorEnvelope.from_dict(response.json())
+
+ return response_400
+
+ if response.status_code == 401:
+ response_401 = ErrorEnvelope.from_dict(response.json())
+
+ return response_401
+
+ if response.status_code == 402:
+ response_402 = ErrorEnvelope.from_dict(response.json())
+
+ return response_402
+
+ if response.status_code == 403:
+ response_403 = ErrorEnvelope.from_dict(response.json())
+
+ return response_403
+
+ if response.status_code == 409:
+ response_409 = ErrorEnvelope.from_dict(response.json())
+
+ return response_409
+
+ if response.status_code == 422:
+ response_422 = ErrorEnvelope.from_dict(response.json())
+
+ return response_422
+
+ if response.status_code == 429:
+ response_429 = ErrorEnvelope.from_dict(response.json())
+
+ return response_429
+
+ if response.status_code == 500:
+ response_500 = ErrorEnvelope.from_dict(response.json())
+
+ return response_500
+
+ if response.status_code == 503:
+ response_503 = ErrorEnvelope.from_dict(response.json())
+
+ return response_503
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def _build_response(
+ *, client: AuthenticatedClient | Client, response: httpx.Response
+) -> Response[AgenticCall | ErrorEnvelope]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ *,
+ client: AuthenticatedClient | Client,
+ body: CreateAgenticCallRequest,
+ idempotency_key: str,
+) -> Response[AgenticCall | ErrorEnvelope]:
+ """Create Call
+
+ Prepare one phone task before accepting it for execution. Missing or conflicting essential facts
+ return 422 input_incomplete with details.missing_inputs; no Call is created. Preparation is bounded
+ to 25 seconds; preparation failure or timeout returns 503 provider_unavailable. Successful
+ preparation is saved and reused during background execution. Batch recipients, recurrence and
+ recipient_result_schema are not accepted. The accepted request and result_schema are immutable.
+ Calls accept immediate execution only. Region and locale are optional or null. Infer region from the
+ phone and spoken locale from task intent and supported regional languages; accepted responses return
+ the resolved values. Conflicting phone/region or an ambiguous language returns 422 input_incomplete
+ with details.missing_inputs. Explicit locale is preserved. Unsupported inferred targets return 422
+ unsupported_region or unsupported_language.
+
+ Args:
+ idempotency_key (str):
+ body (CreateAgenticCallRequest): One phone, optional region and spoken locale, and a
+ required Goal-compatible result schema.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[AgenticCall | ErrorEnvelope]
+ """
+
+ kwargs = _get_kwargs(
+ body=body,
+ idempotency_key=idempotency_key,
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return _build_response(client=client, response=response)
+
+
+def sync(
+ *,
+ client: AuthenticatedClient | Client,
+ body: CreateAgenticCallRequest,
+ idempotency_key: str,
+) -> AgenticCall | ErrorEnvelope | None:
+ """Create Call
+
+ Prepare one phone task before accepting it for execution. Missing or conflicting essential facts
+ return 422 input_incomplete with details.missing_inputs; no Call is created. Preparation is bounded
+ to 25 seconds; preparation failure or timeout returns 503 provider_unavailable. Successful
+ preparation is saved and reused during background execution. Batch recipients, recurrence and
+ recipient_result_schema are not accepted. The accepted request and result_schema are immutable.
+ Calls accept immediate execution only. Region and locale are optional or null. Infer region from the
+ phone and spoken locale from task intent and supported regional languages; accepted responses return
+ the resolved values. Conflicting phone/region or an ambiguous language returns 422 input_incomplete
+ with details.missing_inputs. Explicit locale is preserved. Unsupported inferred targets return 422
+ unsupported_region or unsupported_language.
+
+ Args:
+ idempotency_key (str):
+ body (CreateAgenticCallRequest): One phone, optional region and spoken locale, and a
+ required Goal-compatible result schema.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ AgenticCall | ErrorEnvelope
+ """
+
+ return sync_detailed(
+ client=client,
+ body=body,
+ idempotency_key=idempotency_key,
+ ).parsed
+
+
+async def asyncio_detailed(
+ *,
+ client: AuthenticatedClient | Client,
+ body: CreateAgenticCallRequest,
+ idempotency_key: str,
+) -> Response[AgenticCall | ErrorEnvelope]:
+ """Create Call
+
+ Prepare one phone task before accepting it for execution. Missing or conflicting essential facts
+ return 422 input_incomplete with details.missing_inputs; no Call is created. Preparation is bounded
+ to 25 seconds; preparation failure or timeout returns 503 provider_unavailable. Successful
+ preparation is saved and reused during background execution. Batch recipients, recurrence and
+ recipient_result_schema are not accepted. The accepted request and result_schema are immutable.
+ Calls accept immediate execution only. Region and locale are optional or null. Infer region from the
+ phone and spoken locale from task intent and supported regional languages; accepted responses return
+ the resolved values. Conflicting phone/region or an ambiguous language returns 422 input_incomplete
+ with details.missing_inputs. Explicit locale is preserved. Unsupported inferred targets return 422
+ unsupported_region or unsupported_language.
+
+ Args:
+ idempotency_key (str):
+ body (CreateAgenticCallRequest): One phone, optional region and spoken locale, and a
+ required Goal-compatible result schema.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[AgenticCall | ErrorEnvelope]
+ """
+
+ kwargs = _get_kwargs(
+ body=body,
+ idempotency_key=idempotency_key,
+ )
+
+ response = await client.get_async_httpx_client().request(**kwargs)
+
+ return _build_response(client=client, response=response)
+
+
+async def asyncio(
+ *,
+ client: AuthenticatedClient | Client,
+ body: CreateAgenticCallRequest,
+ idempotency_key: str,
+) -> AgenticCall | ErrorEnvelope | None:
+ """Create Call
+
+ Prepare one phone task before accepting it for execution. Missing or conflicting essential facts
+ return 422 input_incomplete with details.missing_inputs; no Call is created. Preparation is bounded
+ to 25 seconds; preparation failure or timeout returns 503 provider_unavailable. Successful
+ preparation is saved and reused during background execution. Batch recipients, recurrence and
+ recipient_result_schema are not accepted. The accepted request and result_schema are immutable.
+ Calls accept immediate execution only. Region and locale are optional or null. Infer region from the
+ phone and spoken locale from task intent and supported regional languages; accepted responses return
+ the resolved values. Conflicting phone/region or an ambiguous language returns 422 input_incomplete
+ with details.missing_inputs. Explicit locale is preserved. Unsupported inferred targets return 422
+ unsupported_region or unsupported_language.
+
+ Args:
+ idempotency_key (str):
+ body (CreateAgenticCallRequest): One phone, optional region and spoken locale, and a
+ required Goal-compatible result schema.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ AgenticCall | ErrorEnvelope
+ """
+
+ return (
+ await asyncio_detailed(
+ client=client,
+ body=body,
+ idempotency_key=idempotency_key,
+ )
+ ).parsed
diff --git a/src/calle/generated/api/calls/get_agentic_call.py b/src/calle/generated/api/calls/get_agentic_call.py
new file mode 100644
index 0000000..16869ac
--- /dev/null
+++ b/src/calle/generated/api/calls/get_agentic_call.py
@@ -0,0 +1,195 @@
+from http import HTTPStatus
+from typing import Any
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response
+from ... import errors
+
+from ...models.agentic_call import AgenticCall
+from ...models.error_envelope import ErrorEnvelope
+
+
+def _get_kwargs(
+ call_id: str,
+) -> dict[str, Any]:
+
+ _kwargs: dict[str, Any] = {
+ "method": "get",
+ "url": "/v2/calls/{call_id}".format(
+ call_id=quote(str(call_id), safe="").replace(".", "%2E"),
+ ),
+ }
+
+ return _kwargs
+
+
+def _parse_response(
+ *, client: AuthenticatedClient | Client, response: httpx.Response
+) -> AgenticCall | ErrorEnvelope | None:
+ if response.status_code == 200:
+ response_200 = AgenticCall.from_dict(response.json())
+
+ return response_200
+
+ if response.status_code == 400:
+ response_400 = ErrorEnvelope.from_dict(response.json())
+
+ return response_400
+
+ if response.status_code == 401:
+ response_401 = ErrorEnvelope.from_dict(response.json())
+
+ return response_401
+
+ if response.status_code == 403:
+ response_403 = ErrorEnvelope.from_dict(response.json())
+
+ return response_403
+
+ if response.status_code == 404:
+ response_404 = ErrorEnvelope.from_dict(response.json())
+
+ return response_404
+
+ if response.status_code == 409:
+ response_409 = ErrorEnvelope.from_dict(response.json())
+
+ return response_409
+
+ if response.status_code == 500:
+ response_500 = ErrorEnvelope.from_dict(response.json())
+
+ return response_500
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def _build_response(
+ *, client: AuthenticatedClient | Client, response: httpx.Response
+) -> Response[AgenticCall | ErrorEnvelope]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ call_id: str,
+ *,
+ client: AuthenticatedClient | Client,
+) -> Response[AgenticCall | ErrorEnvelope]:
+ """Get Call
+
+ Polling reads persisted state and never performs extraction or initiates a call.
+
+ Args:
+ call_id (str):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[AgenticCall | ErrorEnvelope]
+ """
+
+ kwargs = _get_kwargs(
+ call_id=call_id,
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return _build_response(client=client, response=response)
+
+
+def sync(
+ call_id: str,
+ *,
+ client: AuthenticatedClient | Client,
+) -> AgenticCall | ErrorEnvelope | None:
+ """Get Call
+
+ Polling reads persisted state and never performs extraction or initiates a call.
+
+ Args:
+ call_id (str):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ AgenticCall | ErrorEnvelope
+ """
+
+ return sync_detailed(
+ call_id=call_id,
+ client=client,
+ ).parsed
+
+
+async def asyncio_detailed(
+ call_id: str,
+ *,
+ client: AuthenticatedClient | Client,
+) -> Response[AgenticCall | ErrorEnvelope]:
+ """Get Call
+
+ Polling reads persisted state and never performs extraction or initiates a call.
+
+ Args:
+ call_id (str):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[AgenticCall | ErrorEnvelope]
+ """
+
+ kwargs = _get_kwargs(
+ call_id=call_id,
+ )
+
+ response = await client.get_async_httpx_client().request(**kwargs)
+
+ return _build_response(client=client, response=response)
+
+
+async def asyncio(
+ call_id: str,
+ *,
+ client: AuthenticatedClient | Client,
+) -> AgenticCall | ErrorEnvelope | None:
+ """Get Call
+
+ Polling reads persisted state and never performs extraction or initiates a call.
+
+ Args:
+ call_id (str):
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ AgenticCall | ErrorEnvelope
+ """
+
+ return (
+ await asyncio_detailed(
+ call_id=call_id,
+ client=client,
+ )
+ ).parsed
diff --git a/src/calle/generated/api/calls/list_agentic_call_events.py b/src/calle/generated/api/calls/list_agentic_call_events.py
new file mode 100644
index 0000000..b7cd23f
--- /dev/null
+++ b/src/calle/generated/api/calls/list_agentic_call_events.py
@@ -0,0 +1,256 @@
+from http import HTTPStatus
+from typing import Any
+from urllib.parse import quote
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response, UNSET
+from ... import errors
+
+from ...models.error_envelope import ErrorEnvelope
+from ...models.event_list import EventList
+from ...types import Unset
+
+
+def _get_kwargs(
+ call_id: str,
+ *,
+ cursor: str | Unset = UNSET,
+ limit: int | Unset = 50,
+) -> dict[str, Any]:
+
+ params: dict[str, Any] = {}
+
+ params["cursor"] = cursor
+
+ params["limit"] = limit
+
+ params = {k: v for k, v in params.items() if v is not UNSET and v is not None}
+
+ _kwargs: dict[str, Any] = {
+ "method": "get",
+ "url": "/v2/calls/{call_id}/events".format(
+ call_id=quote(str(call_id), safe="").replace(".", "%2E"),
+ ),
+ "params": params,
+ }
+
+ return _kwargs
+
+
+def _parse_response(
+ *, client: AuthenticatedClient | Client, response: httpx.Response
+) -> ErrorEnvelope | EventList | None:
+ if response.status_code == 200:
+ response_200 = EventList.from_dict(response.json())
+
+ return response_200
+
+ if response.status_code == 400:
+ response_400 = ErrorEnvelope.from_dict(response.json())
+
+ return response_400
+
+ if response.status_code == 401:
+ response_401 = ErrorEnvelope.from_dict(response.json())
+
+ return response_401
+
+ if response.status_code == 403:
+ response_403 = ErrorEnvelope.from_dict(response.json())
+
+ return response_403
+
+ if response.status_code == 404:
+ response_404 = ErrorEnvelope.from_dict(response.json())
+
+ return response_404
+
+ if response.status_code == 409:
+ response_409 = ErrorEnvelope.from_dict(response.json())
+
+ return response_409
+
+ if response.status_code == 500:
+ response_500 = ErrorEnvelope.from_dict(response.json())
+
+ return response_500
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def _build_response(
+ *, client: AuthenticatedClient | Client, response: httpx.Response
+) -> Response[ErrorEnvelope | EventList]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ call_id: str,
+ *,
+ client: AuthenticatedClient | Client,
+ cursor: str | Unset = UNSET,
+ limit: int | Unset = 50,
+) -> Response[ErrorEnvelope | EventList]:
+ """List Events
+
+ Poll durable call events with cursor and limit. Events include call.accepted, call.in_progress,
+ call.ringing, call.connected, call.asr, call.speech, call.interrupted, call.dtmf, call.ended,
+ call.result_ready, and call.completed/failed/canceled. Sentence details include full text, speaker,
+ turn and an optional occurred_at timestamp. Events are ordered by persistence, including late
+ arrivals; event IDs are stable and deduplicated. Follow next_cursor for additional available pages.
+ To poll for later events, retain the last returned event ID as cursor even when next_cursor is null.
+ Existing lifecycle cursors remain valid. Reads do not dial, infer or persist anything.
+
+ Args:
+ call_id (str):
+ cursor (str | Unset):
+ limit (int | Unset): Default: 50.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[ErrorEnvelope | EventList]
+ """
+
+ kwargs = _get_kwargs(
+ call_id=call_id,
+ cursor=cursor,
+ limit=limit,
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return _build_response(client=client, response=response)
+
+
+def sync(
+ call_id: str,
+ *,
+ client: AuthenticatedClient | Client,
+ cursor: str | Unset = UNSET,
+ limit: int | Unset = 50,
+) -> ErrorEnvelope | EventList | None:
+ """List Events
+
+ Poll durable call events with cursor and limit. Events include call.accepted, call.in_progress,
+ call.ringing, call.connected, call.asr, call.speech, call.interrupted, call.dtmf, call.ended,
+ call.result_ready, and call.completed/failed/canceled. Sentence details include full text, speaker,
+ turn and an optional occurred_at timestamp. Events are ordered by persistence, including late
+ arrivals; event IDs are stable and deduplicated. Follow next_cursor for additional available pages.
+ To poll for later events, retain the last returned event ID as cursor even when next_cursor is null.
+ Existing lifecycle cursors remain valid. Reads do not dial, infer or persist anything.
+
+ Args:
+ call_id (str):
+ cursor (str | Unset):
+ limit (int | Unset): Default: 50.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ ErrorEnvelope | EventList
+ """
+
+ return sync_detailed(
+ call_id=call_id,
+ client=client,
+ cursor=cursor,
+ limit=limit,
+ ).parsed
+
+
+async def asyncio_detailed(
+ call_id: str,
+ *,
+ client: AuthenticatedClient | Client,
+ cursor: str | Unset = UNSET,
+ limit: int | Unset = 50,
+) -> Response[ErrorEnvelope | EventList]:
+ """List Events
+
+ Poll durable call events with cursor and limit. Events include call.accepted, call.in_progress,
+ call.ringing, call.connected, call.asr, call.speech, call.interrupted, call.dtmf, call.ended,
+ call.result_ready, and call.completed/failed/canceled. Sentence details include full text, speaker,
+ turn and an optional occurred_at timestamp. Events are ordered by persistence, including late
+ arrivals; event IDs are stable and deduplicated. Follow next_cursor for additional available pages.
+ To poll for later events, retain the last returned event ID as cursor even when next_cursor is null.
+ Existing lifecycle cursors remain valid. Reads do not dial, infer or persist anything.
+
+ Args:
+ call_id (str):
+ cursor (str | Unset):
+ limit (int | Unset): Default: 50.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[ErrorEnvelope | EventList]
+ """
+
+ kwargs = _get_kwargs(
+ call_id=call_id,
+ cursor=cursor,
+ limit=limit,
+ )
+
+ response = await client.get_async_httpx_client().request(**kwargs)
+
+ return _build_response(client=client, response=response)
+
+
+async def asyncio(
+ call_id: str,
+ *,
+ client: AuthenticatedClient | Client,
+ cursor: str | Unset = UNSET,
+ limit: int | Unset = 50,
+) -> ErrorEnvelope | EventList | None:
+ """List Events
+
+ Poll durable call events with cursor and limit. Events include call.accepted, call.in_progress,
+ call.ringing, call.connected, call.asr, call.speech, call.interrupted, call.dtmf, call.ended,
+ call.result_ready, and call.completed/failed/canceled. Sentence details include full text, speaker,
+ turn and an optional occurred_at timestamp. Events are ordered by persistence, including late
+ arrivals; event IDs are stable and deduplicated. Follow next_cursor for additional available pages.
+ To poll for later events, retain the last returned event ID as cursor even when next_cursor is null.
+ Existing lifecycle cursors remain valid. Reads do not dial, infer or persist anything.
+
+ Args:
+ call_id (str):
+ cursor (str | Unset):
+ limit (int | Unset): Default: 50.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ ErrorEnvelope | EventList
+ """
+
+ return (
+ await asyncio_detailed(
+ call_id=call_id,
+ client=client,
+ cursor=cursor,
+ limit=limit,
+ )
+ ).parsed
diff --git a/src/calle/generated/api/goal_runs/get_goal_run.py b/src/calle/generated/api/goal_runs/get_goal_run.py
index 6c31ca6..e4a9fa4 100644
--- a/src/calle/generated/api/goal_runs/get_goal_run.py
+++ b/src/calle/generated/api/goal_runs/get_goal_run.py
@@ -102,10 +102,9 @@ def sync_detailed(
Goal pointer, dispatch work, or start result materialization. Use the `GoalRun.id` returned
by create as `goal_run_id`; the nested telephone `run_id` is not valid in this path.
- Poll until either `result` or `error` is non-null. A non-null `result` is the parsed object
- validated against the published result schema. A non-null `error` means this Run will not
- produce a result. `status: completed` with both fields null means result processing is still
- in progress.
+ Poll while `result_status` is `pending`. A non-null `result` is the parsed object validated
+ against the published result schema. `unavailable` means evidence did not support a business
+ result; no-answer, busy and declined calls are ordinary `call_outcome` values, not errors.
Args:
goal_id (str):
@@ -145,10 +144,9 @@ def sync(
Goal pointer, dispatch work, or start result materialization. Use the `GoalRun.id` returned
by create as `goal_run_id`; the nested telephone `run_id` is not valid in this path.
- Poll until either `result` or `error` is non-null. A non-null `result` is the parsed object
- validated against the published result schema. A non-null `error` means this Run will not
- produce a result. `status: completed` with both fields null means result processing is still
- in progress.
+ Poll while `result_status` is `pending`. A non-null `result` is the parsed object validated
+ against the published result schema. `unavailable` means evidence did not support a business
+ result; no-answer, busy and declined calls are ordinary `call_outcome` values, not errors.
Args:
goal_id (str):
@@ -183,10 +181,9 @@ async def asyncio_detailed(
Goal pointer, dispatch work, or start result materialization. Use the `GoalRun.id` returned
by create as `goal_run_id`; the nested telephone `run_id` is not valid in this path.
- Poll until either `result` or `error` is non-null. A non-null `result` is the parsed object
- validated against the published result schema. A non-null `error` means this Run will not
- produce a result. `status: completed` with both fields null means result processing is still
- in progress.
+ Poll while `result_status` is `pending`. A non-null `result` is the parsed object validated
+ against the published result schema. `unavailable` means evidence did not support a business
+ result; no-answer, busy and declined calls are ordinary `call_outcome` values, not errors.
Args:
goal_id (str):
@@ -224,10 +221,9 @@ async def asyncio(
Goal pointer, dispatch work, or start result materialization. Use the `GoalRun.id` returned
by create as `goal_run_id`; the nested telephone `run_id` is not valid in this path.
- Poll until either `result` or `error` is non-null. A non-null `result` is the parsed object
- validated against the published result schema. A non-null `error` means this Run will not
- produce a result. `status: completed` with both fields null means result processing is still
- in progress.
+ Poll while `result_status` is `pending`. A non-null `result` is the parsed object validated
+ against the published result schema. `unavailable` means evidence did not support a business
+ result; no-answer, busy and declined calls are ordinary `call_outcome` values, not errors.
Args:
goal_id (str):
diff --git a/src/calle/generated/api/legacy_calls/__init__.py b/src/calle/generated/api/legacy_calls/__init__.py
new file mode 100644
index 0000000..2d7c0b2
--- /dev/null
+++ b/src/calle/generated/api/legacy_calls/__init__.py
@@ -0,0 +1 @@
+"""Contains endpoint functions for accessing the API"""
diff --git a/src/calle/generated/api/calls/create_call.py b/src/calle/generated/api/legacy_calls/create_call.py
similarity index 66%
rename from src/calle/generated/api/calls/create_call.py
rename to src/calle/generated/api/legacy_calls/create_call.py
index 5fbead5..2b55158 100644
--- a/src/calle/generated/api/calls/create_call.py
+++ b/src/calle/generated/api/legacy_calls/create_call.py
@@ -7,6 +7,7 @@
from ...types import Response, UNSET
from ... import errors
+from ...models.call_task import CallTask
from ...models.create_call_request import CreateCallRequest
from ...models.error_envelope import ErrorEnvelope
from ...types import Unset
@@ -36,7 +37,12 @@ def _get_kwargs(
def _parse_response(
*, client: AuthenticatedClient | Client, response: httpx.Response
-) -> ErrorEnvelope | None:
+) -> CallTask | ErrorEnvelope | None:
+ if response.status_code == 201:
+ response_201 = CallTask.from_dict(response.json())
+
+ return response_201
+
if response.status_code == 400:
response_400 = ErrorEnvelope.from_dict(response.json())
@@ -72,6 +78,11 @@ def _parse_response(
return response_500
+ if response.status_code == 503:
+ response_503 = ErrorEnvelope.from_dict(response.json())
+
+ return response_503
+
if client.raise_on_unexpected_status:
raise errors.UnexpectedStatus(response.status_code, response.content)
else:
@@ -80,7 +91,7 @@ def _parse_response(
def _build_response(
*, client: AuthenticatedClient | Client, response: httpx.Response
-) -> Response[ErrorEnvelope]:
+) -> Response[CallTask | ErrorEnvelope]:
return Response(
status_code=HTTPStatus(response.status_code),
content=response.content,
@@ -94,11 +105,16 @@ def sync_detailed(
client: AuthenticatedClient | Client,
body: CreateCallRequest,
idempotency_key: str | Unset = UNSET,
-) -> Response[ErrorEnvelope]:
+) -> Response[CallTask | ErrorEnvelope]:
"""Create Call
Create an asynchronous call. Use `result_schema` and `recipient_result_schema` to ask CALL-E to
- extract structured JSON results from terminal call evidence.
+ extract structured JSON results from terminal call evidence. Shared platform outbound lines support
+ one phone number per task. Batch calls require an eligible purchased number selected as the account
+ default outbound number; otherwise creation returns `422 call_not_ready`. Account concurrency and
+ LLM token usage are controlled by the effective account configuration. Task concurrency defaults to
+ 1 on shared platform lines and 10 on eligible dedicated purchased numbers; selecting a purchased
+ number as the account default does not make it a shared platform line.
Args:
idempotency_key (str | Unset):
@@ -109,7 +125,7 @@ def sync_detailed(
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
- Response[ErrorEnvelope]
+ Response[CallTask | ErrorEnvelope]
"""
kwargs = _get_kwargs(
@@ -129,11 +145,16 @@ def sync(
client: AuthenticatedClient | Client,
body: CreateCallRequest,
idempotency_key: str | Unset = UNSET,
-) -> ErrorEnvelope | None:
+) -> CallTask | ErrorEnvelope | None:
"""Create Call
Create an asynchronous call. Use `result_schema` and `recipient_result_schema` to ask CALL-E to
- extract structured JSON results from terminal call evidence.
+ extract structured JSON results from terminal call evidence. Shared platform outbound lines support
+ one phone number per task. Batch calls require an eligible purchased number selected as the account
+ default outbound number; otherwise creation returns `422 call_not_ready`. Account concurrency and
+ LLM token usage are controlled by the effective account configuration. Task concurrency defaults to
+ 1 on shared platform lines and 10 on eligible dedicated purchased numbers; selecting a purchased
+ number as the account default does not make it a shared platform line.
Args:
idempotency_key (str | Unset):
@@ -144,7 +165,7 @@ def sync(
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
- ErrorEnvelope
+ CallTask | ErrorEnvelope
"""
return sync_detailed(
@@ -159,11 +180,16 @@ async def asyncio_detailed(
client: AuthenticatedClient | Client,
body: CreateCallRequest,
idempotency_key: str | Unset = UNSET,
-) -> Response[ErrorEnvelope]:
+) -> Response[CallTask | ErrorEnvelope]:
"""Create Call
Create an asynchronous call. Use `result_schema` and `recipient_result_schema` to ask CALL-E to
- extract structured JSON results from terminal call evidence.
+ extract structured JSON results from terminal call evidence. Shared platform outbound lines support
+ one phone number per task. Batch calls require an eligible purchased number selected as the account
+ default outbound number; otherwise creation returns `422 call_not_ready`. Account concurrency and
+ LLM token usage are controlled by the effective account configuration. Task concurrency defaults to
+ 1 on shared platform lines and 10 on eligible dedicated purchased numbers; selecting a purchased
+ number as the account default does not make it a shared platform line.
Args:
idempotency_key (str | Unset):
@@ -174,7 +200,7 @@ async def asyncio_detailed(
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
- Response[ErrorEnvelope]
+ Response[CallTask | ErrorEnvelope]
"""
kwargs = _get_kwargs(
@@ -192,11 +218,16 @@ async def asyncio(
client: AuthenticatedClient | Client,
body: CreateCallRequest,
idempotency_key: str | Unset = UNSET,
-) -> ErrorEnvelope | None:
+) -> CallTask | ErrorEnvelope | None:
"""Create Call
Create an asynchronous call. Use `result_schema` and `recipient_result_schema` to ask CALL-E to
- extract structured JSON results from terminal call evidence.
+ extract structured JSON results from terminal call evidence. Shared platform outbound lines support
+ one phone number per task. Batch calls require an eligible purchased number selected as the account
+ default outbound number; otherwise creation returns `422 call_not_ready`. Account concurrency and
+ LLM token usage are controlled by the effective account configuration. Task concurrency defaults to
+ 1 on shared platform lines and 10 on eligible dedicated purchased numbers; selecting a purchased
+ number as the account default does not make it a shared platform line.
Args:
idempotency_key (str | Unset):
@@ -207,7 +238,7 @@ async def asyncio(
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
- ErrorEnvelope
+ CallTask | ErrorEnvelope
"""
return (
diff --git a/src/calle/generated/api/calls/get_call.py b/src/calle/generated/api/legacy_calls/get_call.py
similarity index 87%
rename from src/calle/generated/api/calls/get_call.py
rename to src/calle/generated/api/legacy_calls/get_call.py
index 1f4b601..7152448 100644
--- a/src/calle/generated/api/calls/get_call.py
+++ b/src/calle/generated/api/legacy_calls/get_call.py
@@ -8,6 +8,7 @@
from ...types import Response
from ... import errors
+from ...models.call_task import CallTask
from ...models.error_envelope import ErrorEnvelope
@@ -27,7 +28,12 @@ def _get_kwargs(
def _parse_response(
*, client: AuthenticatedClient | Client, response: httpx.Response
-) -> ErrorEnvelope | None:
+) -> CallTask | ErrorEnvelope | None:
+ if response.status_code == 200:
+ response_200 = CallTask.from_dict(response.json())
+
+ return response_200
+
if response.status_code == 401:
response_401 = ErrorEnvelope.from_dict(response.json())
@@ -61,7 +67,7 @@ def _parse_response(
def _build_response(
*, client: AuthenticatedClient | Client, response: httpx.Response
-) -> Response[ErrorEnvelope]:
+) -> Response[CallTask | ErrorEnvelope]:
return Response(
status_code=HTTPStatus(response.status_code),
content=response.content,
@@ -74,7 +80,7 @@ def sync_detailed(
call_id: str,
*,
client: AuthenticatedClient | Client,
-) -> Response[ErrorEnvelope]:
+) -> Response[CallTask | ErrorEnvelope]:
"""Get Call
Get a call by id.
@@ -87,7 +93,7 @@ def sync_detailed(
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
- Response[ErrorEnvelope]
+ Response[CallTask | ErrorEnvelope]
"""
kwargs = _get_kwargs(
@@ -105,7 +111,7 @@ def sync(
call_id: str,
*,
client: AuthenticatedClient | Client,
-) -> ErrorEnvelope | None:
+) -> CallTask | ErrorEnvelope | None:
"""Get Call
Get a call by id.
@@ -118,7 +124,7 @@ def sync(
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
- ErrorEnvelope
+ CallTask | ErrorEnvelope
"""
return sync_detailed(
@@ -131,7 +137,7 @@ async def asyncio_detailed(
call_id: str,
*,
client: AuthenticatedClient | Client,
-) -> Response[ErrorEnvelope]:
+) -> Response[CallTask | ErrorEnvelope]:
"""Get Call
Get a call by id.
@@ -144,7 +150,7 @@ async def asyncio_detailed(
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
- Response[ErrorEnvelope]
+ Response[CallTask | ErrorEnvelope]
"""
kwargs = _get_kwargs(
@@ -160,7 +166,7 @@ async def asyncio(
call_id: str,
*,
client: AuthenticatedClient | Client,
-) -> ErrorEnvelope | None:
+) -> CallTask | ErrorEnvelope | None:
"""Get Call
Get a call by id.
@@ -173,7 +179,7 @@ async def asyncio(
httpx.TimeoutException: If the request takes longer than Client.timeout.
Returns:
- ErrorEnvelope
+ CallTask | ErrorEnvelope
"""
return (
diff --git a/src/calle/generated/api/calls/list_call_events.py b/src/calle/generated/api/legacy_calls/list_call_events.py
similarity index 98%
rename from src/calle/generated/api/calls/list_call_events.py
rename to src/calle/generated/api/legacy_calls/list_call_events.py
index 15cc831..44fc27e 100644
--- a/src/calle/generated/api/calls/list_call_events.py
+++ b/src/calle/generated/api/legacy_calls/list_call_events.py
@@ -96,7 +96,7 @@ def sync_detailed(
cursor: str | Unset = UNSET,
limit: int | Unset = 50,
) -> Response[ErrorEnvelope | EventList]:
- """List Call Events
+ """List Events
List developer-facing call events.
@@ -133,7 +133,7 @@ def sync(
cursor: str | Unset = UNSET,
limit: int | Unset = 50,
) -> ErrorEnvelope | EventList | None:
- """List Call Events
+ """List Events
List developer-facing call events.
@@ -165,7 +165,7 @@ async def asyncio_detailed(
cursor: str | Unset = UNSET,
limit: int | Unset = 50,
) -> Response[ErrorEnvelope | EventList]:
- """List Call Events
+ """List Events
List developer-facing call events.
@@ -200,7 +200,7 @@ async def asyncio(
cursor: str | Unset = UNSET,
limit: int | Unset = 50,
) -> ErrorEnvelope | EventList | None:
- """List Call Events
+ """List Events
List developer-facing call events.
diff --git a/src/calle/generated/api/webhooks/receive_webhook_event.py b/src/calle/generated/api/webhooks/receive_webhook_event.py
new file mode 100644
index 0000000..fe04cf5
--- /dev/null
+++ b/src/calle/generated/api/webhooks/receive_webhook_event.py
@@ -0,0 +1,203 @@
+from http import HTTPStatus
+from typing import Any, cast
+
+import httpx
+
+from ...client import AuthenticatedClient, Client
+from ...types import Response
+from ... import errors
+
+from ...models.agentic_webhook_event import AgenticWebhookEvent
+from ...models.webhook_acknowledgement import WebhookAcknowledgement
+from ...models.webhook_event import WebhookEvent
+
+
+def _get_kwargs(
+ *,
+ body: AgenticWebhookEvent | WebhookEvent,
+ call_e_event_id: str,
+) -> dict[str, Any]:
+ headers: dict[str, Any] = {}
+ headers["CALL-E-Event-Id"] = call_e_event_id
+
+ _kwargs: dict[str, Any] = {
+ "method": "post",
+ "url": "/calle/webhook",
+ }
+
+ if isinstance(body, WebhookEvent):
+ _kwargs["json"] = body.to_dict()
+ else:
+ _kwargs["json"] = body.to_dict()
+
+ headers["Content-Type"] = "application/json"
+
+ _kwargs["headers"] = headers
+ return _kwargs
+
+
+def _parse_response(
+ *, client: AuthenticatedClient | Client, response: httpx.Response
+) -> Any | WebhookAcknowledgement | None:
+ if response.status_code == 200:
+ response_200 = WebhookAcknowledgement.from_dict(response.json())
+
+ return response_200
+
+ if response.status_code == 400:
+ response_400 = cast(Any, None)
+ return response_400
+
+ if client.raise_on_unexpected_status:
+ raise errors.UnexpectedStatus(response.status_code, response.content)
+ else:
+ return None
+
+
+def _build_response(
+ *, client: AuthenticatedClient | Client, response: httpx.Response
+) -> Response[Any | WebhookAcknowledgement]:
+ return Response(
+ status_code=HTTPStatus(response.status_code),
+ content=response.content,
+ headers=response.headers,
+ parsed=_parse_response(client=client, response=response),
+ )
+
+
+def sync_detailed(
+ *,
+ client: AuthenticatedClient | Client,
+ body: AgenticWebhookEvent | WebhookEvent,
+ call_e_event_id: str,
+) -> Response[Any | WebhookAcknowledgement]:
+ """Server Message
+
+ CALL-E sends this request after a call reaches a terminal state and its post-call outcome and
+ requested structured results are finalized. Configure this URL with `webhook_url` on create call or
+ through project-level webhook settings.
+
+ Args:
+ call_e_event_id (str):
+ body (AgenticWebhookEvent | WebhookEvent): Terminal event for either a legacy v1 call task
+ or an Agentic v2 call. Inspect data.object to distinguish the payload.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | WebhookAcknowledgement]
+ """
+
+ kwargs = _get_kwargs(
+ body=body,
+ call_e_event_id=call_e_event_id,
+ )
+
+ response = client.get_httpx_client().request(
+ **kwargs,
+ )
+
+ return _build_response(client=client, response=response)
+
+
+def sync(
+ *,
+ client: AuthenticatedClient | Client,
+ body: AgenticWebhookEvent | WebhookEvent,
+ call_e_event_id: str,
+) -> Any | WebhookAcknowledgement | None:
+ """Server Message
+
+ CALL-E sends this request after a call reaches a terminal state and its post-call outcome and
+ requested structured results are finalized. Configure this URL with `webhook_url` on create call or
+ through project-level webhook settings.
+
+ Args:
+ call_e_event_id (str):
+ body (AgenticWebhookEvent | WebhookEvent): Terminal event for either a legacy v1 call task
+ or an Agentic v2 call. Inspect data.object to distinguish the payload.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | WebhookAcknowledgement
+ """
+
+ return sync_detailed(
+ client=client,
+ body=body,
+ call_e_event_id=call_e_event_id,
+ ).parsed
+
+
+async def asyncio_detailed(
+ *,
+ client: AuthenticatedClient | Client,
+ body: AgenticWebhookEvent | WebhookEvent,
+ call_e_event_id: str,
+) -> Response[Any | WebhookAcknowledgement]:
+ """Server Message
+
+ CALL-E sends this request after a call reaches a terminal state and its post-call outcome and
+ requested structured results are finalized. Configure this URL with `webhook_url` on create call or
+ through project-level webhook settings.
+
+ Args:
+ call_e_event_id (str):
+ body (AgenticWebhookEvent | WebhookEvent): Terminal event for either a legacy v1 call task
+ or an Agentic v2 call. Inspect data.object to distinguish the payload.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Response[Any | WebhookAcknowledgement]
+ """
+
+ kwargs = _get_kwargs(
+ body=body,
+ call_e_event_id=call_e_event_id,
+ )
+
+ response = await client.get_async_httpx_client().request(**kwargs)
+
+ return _build_response(client=client, response=response)
+
+
+async def asyncio(
+ *,
+ client: AuthenticatedClient | Client,
+ body: AgenticWebhookEvent | WebhookEvent,
+ call_e_event_id: str,
+) -> Any | WebhookAcknowledgement | None:
+ """Server Message
+
+ CALL-E sends this request after a call reaches a terminal state and its post-call outcome and
+ requested structured results are finalized. Configure this URL with `webhook_url` on create call or
+ through project-level webhook settings.
+
+ Args:
+ call_e_event_id (str):
+ body (AgenticWebhookEvent | WebhookEvent): Terminal event for either a legacy v1 call task
+ or an Agentic v2 call. Inspect data.object to distinguish the payload.
+
+ Raises:
+ errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True.
+ httpx.TimeoutException: If the request takes longer than Client.timeout.
+
+ Returns:
+ Any | WebhookAcknowledgement
+ """
+
+ return (
+ await asyncio_detailed(
+ client=client,
+ body=body,
+ call_e_event_id=call_e_event_id,
+ )
+ ).parsed
diff --git a/src/calle/generated/models/__init__.py b/src/calle/generated/models/__init__.py
index 0676857..aff6f2b 100644
--- a/src/calle/generated/models/__init__.py
+++ b/src/calle/generated/models/__init__.py
@@ -1,11 +1,22 @@
"""Contains all the data models used in inputs/outputs"""
+from .agentic_call import AgenticCall
+from .agentic_call_metadata import AgenticCallMetadata
+from .agentic_call_result_type_0 import AgenticCallResultType0
+from .agentic_webhook_event import AgenticWebhookEvent
+from .agentic_webhook_event_type import AgenticWebhookEventType
from .api_error import APIError
from .api_error_code import APIErrorCode
from .api_error_details import APIErrorDetails
from .attempt_status import AttemptStatus
+from .business_result_status import BusinessResultStatus
+from .call_outcome_type_1 import CallOutcomeType1
+from .call_outcome_type_2_type_1 import CallOutcomeType2Type1
+from .call_outcome_type_3_type_1 import CallOutcomeType3Type1
from .call_status import CallStatus
+from .call_task import CallTask
from .call_task_attempt import CallTaskAttempt
+from .call_task_metadata import CallTaskMetadata
from .call_task_object import CallTaskObject
from .call_task_recipient import CallTaskRecipient
from .call_task_recipient_request import CallTaskRecipientRequest
@@ -15,6 +26,11 @@
from .call_task_structured_result_type_0 import CallTaskStructuredResultType0
from .call_transcript_turn import CallTranscriptTurn
from .completion_confidence import CompletionConfidence
+from .create_agentic_call_request import CreateAgenticCallRequest
+from .create_agentic_call_request_metadata import CreateAgenticCallRequestMetadata
+from .create_agentic_call_request_result_schema import (
+ CreateAgenticCallRequestResultSchema,
+)
from .create_call_request import CreateCallRequest
from .create_call_request_metadata import CreateCallRequestMetadata
from .create_call_request_recipient_result_schema_type_0 import (
@@ -47,15 +63,33 @@
from .recipient_status import RecipientStatus
from .transcript_speaker import TranscriptSpeaker
from .webhook_acknowledgement import WebhookAcknowledgement
+from .webhook_call_data import WebhookCallData
+from .webhook_call_data_metadata import WebhookCallDataMetadata
+from .webhook_call_data_object import WebhookCallDataObject
+from .webhook_call_data_structured_result_type_0 import (
+ WebhookCallDataStructuredResultType0,
+)
+from .webhook_event import WebhookEvent
from .webhook_event_type import WebhookEventType
__all__ = (
+ "AgenticCall",
+ "AgenticCallMetadata",
+ "AgenticCallResultType0",
+ "AgenticWebhookEvent",
+ "AgenticWebhookEventType",
"APIError",
"APIErrorCode",
"APIErrorDetails",
"AttemptStatus",
+ "BusinessResultStatus",
+ "CallOutcomeType1",
+ "CallOutcomeType2Type1",
+ "CallOutcomeType3Type1",
"CallStatus",
+ "CallTask",
"CallTaskAttempt",
+ "CallTaskMetadata",
"CallTaskObject",
"CallTaskRecipient",
"CallTaskRecipientRequest",
@@ -63,6 +97,9 @@
"CallTaskStructuredResultType0",
"CallTranscriptTurn",
"CompletionConfidence",
+ "CreateAgenticCallRequest",
+ "CreateAgenticCallRequestMetadata",
+ "CreateAgenticCallRequestResultSchema",
"CreateCallRequest",
"CreateCallRequestMetadata",
"CreateCallRequestRecipientResultSchemaType0",
@@ -93,5 +130,10 @@
"RecipientStatus",
"TranscriptSpeaker",
"WebhookAcknowledgement",
+ "WebhookCallData",
+ "WebhookCallDataMetadata",
+ "WebhookCallDataObject",
+ "WebhookCallDataStructuredResultType0",
+ "WebhookEvent",
"WebhookEventType",
)
diff --git a/src/calle/generated/models/agentic_call.py b/src/calle/generated/models/agentic_call.py
new file mode 100644
index 0000000..f49f1bf
--- /dev/null
+++ b/src/calle/generated/models/agentic_call.py
@@ -0,0 +1,325 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, TYPE_CHECKING
+
+from attrs import define as _attrs_define
+
+
+from ..models.business_result_status import BusinessResultStatus
+from ..models.business_result_status import check_business_result_status
+from ..models.call_outcome_type_1 import CallOutcomeType1
+from ..models.call_outcome_type_1 import check_call_outcome_type_1
+from ..models.call_outcome_type_2_type_1 import CallOutcomeType2Type1
+from ..models.call_outcome_type_2_type_1 import check_call_outcome_type_2_type_1
+from ..models.call_outcome_type_3_type_1 import CallOutcomeType3Type1
+from ..models.call_outcome_type_3_type_1 import check_call_outcome_type_3_type_1
+from ..models.goal_run_status import check_goal_run_status
+from ..models.goal_run_status import GoalRunStatus
+from typing import cast
+from typing import Literal
+import datetime
+
+if TYPE_CHECKING:
+ from ..models.agentic_call_metadata import AgenticCallMetadata
+ from ..models.agentic_call_result_type_0 import AgenticCallResultType0
+ from ..models.call_transcript_turn import CallTranscriptTurn
+ from ..models.goal_run_error import GoalRunError
+
+
+T = TypeVar("T", bound="AgenticCall")
+
+
+@_attrs_define
+class AgenticCall:
+ """Persisted one-shot snapshot sharing execution, call_outcome and result_status with Goal Run. Poll only while
+ result_status is pending. Completed execution does not imply business success. Terminal webhooks are sent when
+ result_status is no longer pending, including unavailable results.
+
+ Attributes:
+ id (str): API Call resource ID. Use this ID for API paths and event correlation, not the telephone ID in
+ Billing.
+ call_id (None | str): Telephone call ID shown in Billing, using the same identity as Goal Run call_id. Always
+ present; null until recorded, including cancellation before dialing. Independent of business result
+ availability. Does not indicate whether charges have settled. Use id, not this field, for API paths.
+ object_ (Literal['call']):
+ status (GoalRunStatus): Stable execution state. No-answer, busy and declined calls complete execution normally.
+ Technical execution failures are failed; explicit cancellation is canceled. Completed execution
+ may still have result_status=pending while its business result is being processed.
+ call_outcome (CallOutcomeType1 | CallOutcomeType2Type1 | CallOutcomeType3Type1 | None): Reported telephone
+ outcome, separate from business success and billing connection evidence. Null before a telephone outcome is
+ known or when execution is canceled or fails technically.
+ result_status (BusinessResultStatus): Pending means keep polling. Available includes an empty result object.
+ Unavailable means no schema-valid business result could be produced; inspect error for technical failures. Not
+ applicable is used for cancellation and technical execution failure.
+ transcript (list[CallTranscriptTurn]): Recorded conversation turns in order, independent of the business result.
+ Always present; empty before terminal execution or when no transcript is available. Never generated from
+ result_schema.
+ task (str):
+ phone (str):
+ region (str):
+ locale (str):
+ result (AgenticCallResultType0 | None): Result validated against result_schema and durably persisted. Null while
+ pending, unavailable, not applicable, or on a technical error. Explicit schema-valid task fallbacks are
+ preserved.
+ error (GoalRunError | None): Technical execution or result-processing error, or null. No-answer, busy, declined,
+ cancellation and insufficient business evidence do not populate error.
+ metadata (AgenticCallMetadata):
+ created_at (datetime.datetime):
+ completed_at (datetime.datetime | None): UTC telephone-execution completion time, or `null` while execution is
+ non-terminal.
+ """
+
+ id: str
+ call_id: None | str
+ object_: Literal["call"]
+ status: GoalRunStatus
+ call_outcome: (
+ CallOutcomeType1 | CallOutcomeType2Type1 | CallOutcomeType3Type1 | None
+ )
+ result_status: BusinessResultStatus
+ transcript: list[CallTranscriptTurn]
+ task: str
+ phone: str
+ region: str
+ locale: str
+ result: AgenticCallResultType0 | None
+ error: GoalRunError | None
+ metadata: AgenticCallMetadata
+ created_at: datetime.datetime
+ completed_at: datetime.datetime | None
+
+ def to_dict(self) -> dict[str, Any]:
+ from ..models.agentic_call_result_type_0 import AgenticCallResultType0
+ from ..models.goal_run_error import GoalRunError
+
+ id = self.id
+
+ call_id: None | str
+ call_id = self.call_id
+
+ object_ = self.object_
+
+ status: str = self.status
+
+ call_outcome: None | str
+ if isinstance(self.call_outcome, str):
+ call_outcome = self.call_outcome
+ elif isinstance(self.call_outcome, str):
+ call_outcome = self.call_outcome
+ elif isinstance(self.call_outcome, str):
+ call_outcome = self.call_outcome
+ else:
+ call_outcome = self.call_outcome
+
+ result_status: str = self.result_status
+
+ transcript = []
+ for transcript_item_data in self.transcript:
+ transcript_item = transcript_item_data.to_dict()
+ transcript.append(transcript_item)
+
+ task = self.task
+
+ phone = self.phone
+
+ region = self.region
+
+ locale = self.locale
+
+ result: dict[str, Any] | None
+ if isinstance(self.result, AgenticCallResultType0):
+ result = self.result.to_dict()
+ else:
+ result = self.result
+
+ error: dict[str, Any] | None
+ if isinstance(self.error, GoalRunError):
+ error = self.error.to_dict()
+ else:
+ error = self.error
+
+ metadata = self.metadata.to_dict()
+
+ created_at = self.created_at.isoformat()
+
+ completed_at: None | str
+ if isinstance(self.completed_at, datetime.datetime):
+ completed_at = self.completed_at.isoformat()
+ else:
+ completed_at = self.completed_at
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update(
+ {
+ "id": id,
+ "call_id": call_id,
+ "object": object_,
+ "status": status,
+ "call_outcome": call_outcome,
+ "result_status": result_status,
+ "transcript": transcript,
+ "task": task,
+ "phone": phone,
+ "region": region,
+ "locale": locale,
+ "result": result,
+ "error": error,
+ "metadata": metadata,
+ "created_at": created_at,
+ "completed_at": completed_at,
+ }
+ )
+
+ return field_dict
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.agentic_call_metadata import AgenticCallMetadata
+ from ..models.agentic_call_result_type_0 import AgenticCallResultType0
+ from ..models.call_transcript_turn import CallTranscriptTurn
+ from ..models.goal_run_error import GoalRunError
+
+ d = dict(src_dict)
+ id = d.pop("id")
+
+ def _parse_call_id(data: object) -> None | str:
+ if data is None:
+ return data
+ return cast(None | str, data)
+
+ call_id = _parse_call_id(d.pop("call_id"))
+
+ object_ = cast(Literal["call"], d.pop("object"))
+ if object_ != "call":
+ raise ValueError(f"object must match const 'call', got '{object_}'")
+
+ status = check_goal_run_status(d.pop("status"))
+
+ def _parse_call_outcome(
+ data: object,
+ ) -> CallOutcomeType1 | CallOutcomeType2Type1 | CallOutcomeType3Type1 | None:
+ if data is None:
+ return data
+ try:
+ if not isinstance(data, str):
+ raise TypeError()
+ componentsschemas_call_outcome_type_1 = check_call_outcome_type_1(data)
+
+ return componentsschemas_call_outcome_type_1
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ try:
+ if not isinstance(data, str):
+ raise TypeError()
+ componentsschemas_call_outcome_type_2_type_1 = (
+ check_call_outcome_type_2_type_1(data)
+ )
+
+ return componentsschemas_call_outcome_type_2_type_1
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ try:
+ if not isinstance(data, str):
+ raise TypeError()
+ componentsschemas_call_outcome_type_3_type_1 = (
+ check_call_outcome_type_3_type_1(data)
+ )
+
+ return componentsschemas_call_outcome_type_3_type_1
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ return cast(
+ CallOutcomeType1 | CallOutcomeType2Type1 | CallOutcomeType3Type1 | None,
+ data,
+ )
+
+ call_outcome = _parse_call_outcome(d.pop("call_outcome"))
+
+ result_status = check_business_result_status(d.pop("result_status"))
+
+ transcript = []
+ _transcript = d.pop("transcript")
+ for transcript_item_data in _transcript:
+ transcript_item = CallTranscriptTurn.from_dict(transcript_item_data)
+
+ transcript.append(transcript_item)
+
+ task = d.pop("task")
+
+ phone = d.pop("phone")
+
+ region = d.pop("region")
+
+ locale = d.pop("locale")
+
+ def _parse_result(data: object) -> AgenticCallResultType0 | None:
+ if data is None:
+ return data
+ try:
+ if not isinstance(data, dict):
+ raise TypeError()
+ result_type_0 = AgenticCallResultType0.from_dict(data)
+
+ return result_type_0
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ return cast(AgenticCallResultType0 | None, data)
+
+ result = _parse_result(d.pop("result"))
+
+ def _parse_error(data: object) -> GoalRunError | None:
+ if data is None:
+ return data
+ try:
+ if not isinstance(data, dict):
+ raise TypeError()
+ error_type_0 = GoalRunError.from_dict(data)
+
+ return error_type_0
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ return cast(GoalRunError | None, data)
+
+ error = _parse_error(d.pop("error"))
+
+ metadata = AgenticCallMetadata.from_dict(d.pop("metadata"))
+
+ created_at = datetime.datetime.fromisoformat(d.pop("created_at"))
+
+ def _parse_completed_at(data: object) -> datetime.datetime | None:
+ if data is None:
+ return data
+ try:
+ if not isinstance(data, str):
+ raise TypeError()
+ completed_at_type_0 = datetime.datetime.fromisoformat(data)
+
+ return completed_at_type_0
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ return cast(datetime.datetime | None, data)
+
+ completed_at = _parse_completed_at(d.pop("completed_at"))
+
+ agentic_call = cls(
+ id=id,
+ call_id=call_id,
+ object_=object_,
+ status=status,
+ call_outcome=call_outcome,
+ result_status=result_status,
+ transcript=transcript,
+ task=task,
+ phone=phone,
+ region=region,
+ locale=locale,
+ result=result,
+ error=error,
+ metadata=metadata,
+ created_at=created_at,
+ completed_at=completed_at,
+ )
+
+ return agentic_call
diff --git a/src/calle/generated/models/agentic_call_metadata.py b/src/calle/generated/models/agentic_call_metadata.py
new file mode 100644
index 0000000..c47b425
--- /dev/null
+++ b/src/calle/generated/models/agentic_call_metadata.py
@@ -0,0 +1,48 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+
+T = TypeVar("T", bound="AgenticCallMetadata")
+
+
+@_attrs_define
+class AgenticCallMetadata:
+ """ """
+
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+ def to_dict(self) -> dict[str, Any]:
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+
+ return field_dict
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ agentic_call_metadata = cls()
+
+ agentic_call_metadata.additional_properties = d
+ return agentic_call_metadata
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/calle/generated/models/agentic_call_result_type_0.py b/src/calle/generated/models/agentic_call_result_type_0.py
new file mode 100644
index 0000000..3597d79
--- /dev/null
+++ b/src/calle/generated/models/agentic_call_result_type_0.py
@@ -0,0 +1,67 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+
+from typing import cast
+
+
+T = TypeVar("T", bound="AgenticCallResultType0")
+
+
+@_attrs_define
+class AgenticCallResultType0:
+ """Result validated against result_schema and durably persisted. Null while pending, unavailable, not applicable, or on
+ a technical error. Explicit schema-valid task fallbacks are preserved.
+
+ """
+
+ additional_properties: dict[str, bool | float | str] = _attrs_field(
+ init=False, factory=dict
+ )
+
+ def to_dict(self) -> dict[str, Any]:
+
+ field_dict: dict[str, Any] = {}
+ for prop_name, prop in self.additional_properties.items():
+ field_dict[prop_name] = prop
+
+ return field_dict
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ agentic_call_result_type_0 = cls()
+
+ additional_properties = {}
+ for prop_name, prop_dict in d.items():
+
+ def _parse_additional_property(data: object) -> bool | float | str:
+ return cast(bool | float | str, data)
+
+ additional_property = _parse_additional_property(prop_dict)
+
+ additional_properties[prop_name] = additional_property
+
+ agentic_call_result_type_0.additional_properties = additional_properties
+ return agentic_call_result_type_0
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> bool | float | str:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: bool | float | str) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/calle/generated/models/agentic_webhook_event.py b/src/calle/generated/models/agentic_webhook_event.py
new file mode 100644
index 0000000..00b54c9
--- /dev/null
+++ b/src/calle/generated/models/agentic_webhook_event.py
@@ -0,0 +1,81 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, TYPE_CHECKING
+
+from attrs import define as _attrs_define
+
+
+from ..models.agentic_webhook_event_type import AgenticWebhookEventType
+from ..models.agentic_webhook_event_type import check_agentic_webhook_event_type
+import datetime
+
+if TYPE_CHECKING:
+ from ..models.agentic_call import AgenticCall
+
+
+T = TypeVar("T", bound="AgenticWebhookEvent")
+
+
+@_attrs_define
+class AgenticWebhookEvent:
+ """v2 terminal webhook. data is identical to the terminal GET response. A valid call completion can carry a failed
+ custom result. Retries reuse the same event id.
+
+ Attributes:
+ id (str):
+ type_ (AgenticWebhookEventType):
+ created_at (datetime.datetime):
+ data (AgenticCall): Persisted one-shot snapshot sharing execution, call_outcome and result_status with Goal Run.
+ Poll only while result_status is pending. Completed execution does not imply business success. Terminal webhooks
+ are sent when result_status is no longer pending, including unavailable results.
+ """
+
+ id: str
+ type_: AgenticWebhookEventType
+ created_at: datetime.datetime
+ data: AgenticCall
+
+ def to_dict(self) -> dict[str, Any]:
+ id = self.id
+
+ type_: str = self.type_
+
+ created_at = self.created_at.isoformat()
+
+ data = self.data.to_dict()
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update(
+ {
+ "id": id,
+ "type": type_,
+ "created_at": created_at,
+ "data": data,
+ }
+ )
+
+ return field_dict
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.agentic_call import AgenticCall
+
+ d = dict(src_dict)
+ id = d.pop("id")
+
+ type_ = check_agentic_webhook_event_type(d.pop("type"))
+
+ created_at = datetime.datetime.fromisoformat(d.pop("created_at"))
+
+ data = AgenticCall.from_dict(d.pop("data"))
+
+ agentic_webhook_event = cls(
+ id=id,
+ type_=type_,
+ created_at=created_at,
+ data=data,
+ )
+
+ return agentic_webhook_event
diff --git a/src/calle/generated/models/agentic_webhook_event_type.py b/src/calle/generated/models/agentic_webhook_event_type.py
new file mode 100644
index 0000000..8abfd61
--- /dev/null
+++ b/src/calle/generated/models/agentic_webhook_event_type.py
@@ -0,0 +1,17 @@
+from typing import Literal
+
+AgenticWebhookEventType = Literal["call.canceled", "call.completed", "call.failed"]
+
+AGENTIC_WEBHOOK_EVENT_TYPE_VALUES: set[AgenticWebhookEventType] = {
+ "call.canceled",
+ "call.completed",
+ "call.failed",
+}
+
+
+def check_agentic_webhook_event_type(value: str) -> AgenticWebhookEventType:
+ if value in AGENTIC_WEBHOOK_EVENT_TYPE_VALUES:
+ return value
+ raise TypeError(
+ f"Unexpected value {value!r}. Expected one of {AGENTIC_WEBHOOK_EVENT_TYPE_VALUES!r}"
+ )
diff --git a/src/calle/generated/models/api_error_code.py b/src/calle/generated/models/api_error_code.py
index 820655a..eca3d07 100644
--- a/src/calle/generated/models/api_error_code.py
+++ b/src/calle/generated/models/api_error_code.py
@@ -1,17 +1,23 @@
from typing import Literal
APIErrorCode = Literal[
+ "account_concurrency_exceeded",
+ "account_concurrency_unavailable",
+ "call_cannot_cancel",
"call_not_ready",
"forbidden",
"goal_not_executable",
"goal_not_published",
"goal_not_ready",
"idempotency_conflict",
+ "input_incomplete",
"insufficient_balance",
"internal_error",
"invalid_phone",
"invalid_recipient",
"invalid_request",
+ "llm_token_budget_exceeded",
+ "llm_token_budget_unavailable",
"no_recipients",
"not_found",
"policy_violation",
@@ -28,17 +34,23 @@
]
API_ERROR_CODE_VALUES: set[APIErrorCode] = {
+ "account_concurrency_exceeded",
+ "account_concurrency_unavailable",
+ "call_cannot_cancel",
"call_not_ready",
"forbidden",
"goal_not_executable",
"goal_not_published",
"goal_not_ready",
"idempotency_conflict",
+ "input_incomplete",
"insufficient_balance",
"internal_error",
"invalid_phone",
"invalid_recipient",
"invalid_request",
+ "llm_token_budget_exceeded",
+ "llm_token_budget_unavailable",
"no_recipients",
"not_found",
"policy_violation",
diff --git a/src/calle/generated/models/business_result_status.py b/src/calle/generated/models/business_result_status.py
new file mode 100644
index 0000000..f1dd592
--- /dev/null
+++ b/src/calle/generated/models/business_result_status.py
@@ -0,0 +1,18 @@
+from typing import Literal
+
+BusinessResultStatus = Literal["available", "not_applicable", "pending", "unavailable"]
+
+BUSINESS_RESULT_STATUS_VALUES: set[BusinessResultStatus] = {
+ "available",
+ "not_applicable",
+ "pending",
+ "unavailable",
+}
+
+
+def check_business_result_status(value: str) -> BusinessResultStatus:
+ if value in BUSINESS_RESULT_STATUS_VALUES:
+ return value
+ raise TypeError(
+ f"Unexpected value {value!r}. Expected one of {BUSINESS_RESULT_STATUS_VALUES!r}"
+ )
diff --git a/src/calle/generated/models/call_outcome_type_1.py b/src/calle/generated/models/call_outcome_type_1.py
new file mode 100644
index 0000000..9819439
--- /dev/null
+++ b/src/calle/generated/models/call_outcome_type_1.py
@@ -0,0 +1,18 @@
+from typing import Literal
+
+CallOutcomeType1 = Literal["busy", "completed", "declined", "no_answer"]
+
+CALL_OUTCOME_TYPE_1_VALUES: set[CallOutcomeType1] = {
+ "busy",
+ "completed",
+ "declined",
+ "no_answer",
+}
+
+
+def check_call_outcome_type_1(value: str) -> CallOutcomeType1:
+ if value in CALL_OUTCOME_TYPE_1_VALUES:
+ return value
+ raise TypeError(
+ f"Unexpected value {value!r}. Expected one of {CALL_OUTCOME_TYPE_1_VALUES!r}"
+ )
diff --git a/src/calle/generated/models/call_outcome_type_2_type_1.py b/src/calle/generated/models/call_outcome_type_2_type_1.py
new file mode 100644
index 0000000..2157864
--- /dev/null
+++ b/src/calle/generated/models/call_outcome_type_2_type_1.py
@@ -0,0 +1,18 @@
+from typing import Literal
+
+CallOutcomeType2Type1 = Literal["busy", "completed", "declined", "no_answer"]
+
+CALL_OUTCOME_TYPE_2_TYPE_1_VALUES: set[CallOutcomeType2Type1] = {
+ "busy",
+ "completed",
+ "declined",
+ "no_answer",
+}
+
+
+def check_call_outcome_type_2_type_1(value: str) -> CallOutcomeType2Type1:
+ if value in CALL_OUTCOME_TYPE_2_TYPE_1_VALUES:
+ return value
+ raise TypeError(
+ f"Unexpected value {value!r}. Expected one of {CALL_OUTCOME_TYPE_2_TYPE_1_VALUES!r}"
+ )
diff --git a/src/calle/generated/models/call_outcome_type_3_type_1.py b/src/calle/generated/models/call_outcome_type_3_type_1.py
new file mode 100644
index 0000000..dfc2f69
--- /dev/null
+++ b/src/calle/generated/models/call_outcome_type_3_type_1.py
@@ -0,0 +1,18 @@
+from typing import Literal
+
+CallOutcomeType3Type1 = Literal["busy", "completed", "declined", "no_answer"]
+
+CALL_OUTCOME_TYPE_3_TYPE_1_VALUES: set[CallOutcomeType3Type1] = {
+ "busy",
+ "completed",
+ "declined",
+ "no_answer",
+}
+
+
+def check_call_outcome_type_3_type_1(value: str) -> CallOutcomeType3Type1:
+ if value in CALL_OUTCOME_TYPE_3_TYPE_1_VALUES:
+ return value
+ raise TypeError(
+ f"Unexpected value {value!r}. Expected one of {CALL_OUTCOME_TYPE_3_TYPE_1_VALUES!r}"
+ )
diff --git a/src/calle/generated/models/call_task.py b/src/calle/generated/models/call_task.py
new file mode 100644
index 0000000..9a8fc6b
--- /dev/null
+++ b/src/calle/generated/models/call_task.py
@@ -0,0 +1,281 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, TYPE_CHECKING
+
+from attrs import define as _attrs_define
+
+
+from ..models.call_status import CallStatus
+from ..models.call_status import check_call_status
+from ..models.call_task_object import CallTaskObject
+from ..models.call_task_object import check_call_task_object
+from typing import cast
+import datetime
+
+if TYPE_CHECKING:
+ from ..models.call_task_metadata import CallTaskMetadata
+ from ..models.call_task_recipient import CallTaskRecipient
+ from ..models.call_task_structured_result_type_0 import (
+ CallTaskStructuredResultType0,
+ )
+ from ..models.completion_confidence import CompletionConfidence
+
+
+T = TypeVar("T", bound="CallTask")
+
+
+@_attrs_define
+class CallTask:
+ """
+ Attributes:
+ id (str): Public CALL-E call task identifier. Store this id to fetch state, list events, and correlate webhooks.
+ object_ (CallTaskObject): Always `call_task` for call task responses.
+ status (CallStatus): Current lifecycle state of a CALL-E call. `in_progress` includes post-call result
+ finalization; terminal states are published only after the post-call outcome is available.
+ task (str): Original task instruction submitted when the call task was created.
+ recipients (list[CallTaskRecipient]): Recipient states for this call task.
+ structured_result (CallTaskStructuredResultType0 | None): Schema-valid structured result object extracted for
+ the whole call task using `result_schema`.
+
+ `null` means CALL-E could not produce a schema-valid task-level result from the terminal call evidence, or no
+ `result_schema` was provided. Check recipient-level `structured_result` when you use `recipient_result_schema`
+ for batch calls.
+ summary (None | str): Short human-readable summary of the call task outcome. `null` while the call task is still
+ running or when no useful summary is available.
+ task_completed (bool | None): Post-summary judgment for whether the task reached a clear end state for the user.
+ `null` until CALL-E has a terminal post-summary outcome.
+ completion_confidence (CompletionConfidence | None): Confidence for `task_completed`. `null` until CALL-E has a
+ terminal post-summary outcome.
+ evidence (list[str]): Short evidence items supporting the post-summary task outcome. Empty until CALL-E has
+ terminal evidence.
+ metadata (CallTaskMetadata): Caller-owned metadata submitted on the create call request.
+ failure_code (None | str): Machine-readable failure reason when `status` is `failed`; otherwise `null`.
+ failure_message (None | str): Human-readable failure explanation when `status` is `failed`; otherwise `null`.
+ created_at (datetime.datetime): ISO 8601 timestamp when CALL-E accepted the call request.
+ completed_at (datetime.datetime | None): ISO 8601 timestamp when the complete terminal call result was
+ published. `null` while queued or in progress.
+ """
+
+ id: str
+ object_: CallTaskObject
+ status: CallStatus
+ task: str
+ recipients: list[CallTaskRecipient]
+ structured_result: CallTaskStructuredResultType0 | None
+ summary: None | str
+ task_completed: bool | None
+ completion_confidence: CompletionConfidence | None
+ evidence: list[str]
+ metadata: CallTaskMetadata
+ failure_code: None | str
+ failure_message: None | str
+ created_at: datetime.datetime
+ completed_at: datetime.datetime | None
+
+ def to_dict(self) -> dict[str, Any]:
+ from ..models.call_task_structured_result_type_0 import (
+ CallTaskStructuredResultType0,
+ )
+ from ..models.completion_confidence import CompletionConfidence
+
+ id = self.id
+
+ object_: str = self.object_
+
+ status: str = self.status
+
+ task = self.task
+
+ recipients = []
+ for recipients_item_data in self.recipients:
+ recipients_item = recipients_item_data.to_dict()
+ recipients.append(recipients_item)
+
+ structured_result: dict[str, Any] | None
+ if isinstance(self.structured_result, CallTaskStructuredResultType0):
+ structured_result = self.structured_result.to_dict()
+ else:
+ structured_result = self.structured_result
+
+ summary: None | str
+ summary = self.summary
+
+ task_completed: bool | None
+ task_completed = self.task_completed
+
+ completion_confidence: dict[str, Any] | None
+ if isinstance(self.completion_confidence, CompletionConfidence):
+ completion_confidence = self.completion_confidence.to_dict()
+ else:
+ completion_confidence = self.completion_confidence
+
+ evidence = self.evidence
+
+ metadata = self.metadata.to_dict()
+
+ failure_code: None | str
+ failure_code = self.failure_code
+
+ failure_message: None | str
+ failure_message = self.failure_message
+
+ created_at = self.created_at.isoformat()
+
+ completed_at: None | str
+ if isinstance(self.completed_at, datetime.datetime):
+ completed_at = self.completed_at.isoformat()
+ else:
+ completed_at = self.completed_at
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update(
+ {
+ "id": id,
+ "object": object_,
+ "status": status,
+ "task": task,
+ "recipients": recipients,
+ "structured_result": structured_result,
+ "summary": summary,
+ "task_completed": task_completed,
+ "completion_confidence": completion_confidence,
+ "evidence": evidence,
+ "metadata": metadata,
+ "failure_code": failure_code,
+ "failure_message": failure_message,
+ "created_at": created_at,
+ "completed_at": completed_at,
+ }
+ )
+
+ return field_dict
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.call_task_metadata import CallTaskMetadata
+ from ..models.call_task_recipient import CallTaskRecipient
+ from ..models.call_task_structured_result_type_0 import (
+ CallTaskStructuredResultType0,
+ )
+ from ..models.completion_confidence import CompletionConfidence
+
+ d = dict(src_dict)
+ id = d.pop("id")
+
+ object_ = check_call_task_object(d.pop("object"))
+
+ status = check_call_status(d.pop("status"))
+
+ task = d.pop("task")
+
+ recipients = []
+ _recipients = d.pop("recipients")
+ for recipients_item_data in _recipients:
+ recipients_item = CallTaskRecipient.from_dict(recipients_item_data)
+
+ recipients.append(recipients_item)
+
+ def _parse_structured_result(
+ data: object,
+ ) -> CallTaskStructuredResultType0 | None:
+ if data is None:
+ return data
+ try:
+ if not isinstance(data, dict):
+ raise TypeError()
+ structured_result_type_0 = CallTaskStructuredResultType0.from_dict(data)
+
+ return structured_result_type_0
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ return cast(CallTaskStructuredResultType0 | None, data)
+
+ structured_result = _parse_structured_result(d.pop("structured_result"))
+
+ def _parse_summary(data: object) -> None | str:
+ if data is None:
+ return data
+ return cast(None | str, data)
+
+ summary = _parse_summary(d.pop("summary"))
+
+ def _parse_task_completed(data: object) -> bool | None:
+ if data is None:
+ return data
+ return cast(bool | None, data)
+
+ task_completed = _parse_task_completed(d.pop("task_completed"))
+
+ def _parse_completion_confidence(data: object) -> CompletionConfidence | None:
+ if data is None:
+ return data
+ try:
+ if not isinstance(data, dict):
+ raise TypeError()
+ completion_confidence_type_0 = CompletionConfidence.from_dict(data)
+
+ return completion_confidence_type_0
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ return cast(CompletionConfidence | None, data)
+
+ completion_confidence = _parse_completion_confidence(
+ d.pop("completion_confidence")
+ )
+
+ evidence = cast(list[str], d.pop("evidence"))
+
+ metadata = CallTaskMetadata.from_dict(d.pop("metadata"))
+
+ def _parse_failure_code(data: object) -> None | str:
+ if data is None:
+ return data
+ return cast(None | str, data)
+
+ failure_code = _parse_failure_code(d.pop("failure_code"))
+
+ def _parse_failure_message(data: object) -> None | str:
+ if data is None:
+ return data
+ return cast(None | str, data)
+
+ failure_message = _parse_failure_message(d.pop("failure_message"))
+
+ created_at = datetime.datetime.fromisoformat(d.pop("created_at"))
+
+ def _parse_completed_at(data: object) -> datetime.datetime | None:
+ if data is None:
+ return data
+ try:
+ if not isinstance(data, str):
+ raise TypeError()
+ completed_at_type_0 = datetime.datetime.fromisoformat(data)
+
+ return completed_at_type_0
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ return cast(datetime.datetime | None, data)
+
+ completed_at = _parse_completed_at(d.pop("completed_at"))
+
+ call_task = cls(
+ id=id,
+ object_=object_,
+ status=status,
+ task=task,
+ recipients=recipients,
+ structured_result=structured_result,
+ summary=summary,
+ task_completed=task_completed,
+ completion_confidence=completion_confidence,
+ evidence=evidence,
+ metadata=metadata,
+ failure_code=failure_code,
+ failure_message=failure_message,
+ created_at=created_at,
+ completed_at=completed_at,
+ )
+
+ return call_task
diff --git a/src/calle/generated/models/call_task_metadata.py b/src/calle/generated/models/call_task_metadata.py
new file mode 100644
index 0000000..38582ce
--- /dev/null
+++ b/src/calle/generated/models/call_task_metadata.py
@@ -0,0 +1,48 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+
+T = TypeVar("T", bound="CallTaskMetadata")
+
+
+@_attrs_define
+class CallTaskMetadata:
+ """Caller-owned metadata submitted on the create call request."""
+
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+ def to_dict(self) -> dict[str, Any]:
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+
+ return field_dict
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ call_task_metadata = cls()
+
+ call_task_metadata.additional_properties = d
+ return call_task_metadata
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/calle/generated/models/create_agentic_call_request.py b/src/calle/generated/models/create_agentic_call_request.py
new file mode 100644
index 0000000..e04f234
--- /dev/null
+++ b/src/calle/generated/models/create_agentic_call_request.py
@@ -0,0 +1,161 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, TYPE_CHECKING
+
+from attrs import define as _attrs_define
+
+from ..types import UNSET, Unset
+
+from typing import cast
+
+if TYPE_CHECKING:
+ from ..models.create_agentic_call_request_metadata import (
+ CreateAgenticCallRequestMetadata,
+ )
+ from ..models.create_agentic_call_request_result_schema import (
+ CreateAgenticCallRequestResultSchema,
+ )
+
+
+T = TypeVar("T", bound="CreateAgenticCallRequest")
+
+
+@_attrs_define
+class CreateAgenticCallRequest:
+ """One phone, optional region and spoken locale, and a required Goal-compatible result schema.
+
+ Attributes:
+ task (str):
+ phone (str):
+ result_schema (CreateAgenticCallRequestResultSchema): Required closed flat JSON Schema using the same
+ calle.result.scalar-object.v1 profile as Goal. At most 32 string, boolean, integer or number properties;
+ additionalProperties must be false. Nested objects, arrays, null values and schema combinators are unsupported.
+ region (None | str | Unset): Optional destination region. Inferred from the phone when omitted. An explicit
+ region conflicting with the phone requires corrected input.
+ locale (None | str | Unset): Optional spoken BCP-47 locale. Inferred from task intent and available regional
+ languages when omitted.
+ metadata (CreateAgenticCallRequestMetadata | Unset):
+ webhook_url (None | str | Unset):
+ """
+
+ task: str
+ phone: str
+ result_schema: CreateAgenticCallRequestResultSchema
+ region: None | str | Unset = UNSET
+ locale: None | str | Unset = UNSET
+ metadata: CreateAgenticCallRequestMetadata | Unset = UNSET
+ webhook_url: None | str | Unset = UNSET
+
+ def to_dict(self) -> dict[str, Any]:
+ task = self.task
+
+ phone = self.phone
+
+ result_schema = self.result_schema.to_dict()
+
+ region: None | str | Unset
+ if isinstance(self.region, Unset):
+ region = UNSET
+ else:
+ region = self.region
+
+ locale: None | str | Unset
+ if isinstance(self.locale, Unset):
+ locale = UNSET
+ else:
+ locale = self.locale
+
+ metadata: dict[str, Any] | Unset = UNSET
+ if not isinstance(self.metadata, Unset):
+ metadata = self.metadata.to_dict()
+
+ webhook_url: None | str | Unset
+ if isinstance(self.webhook_url, Unset):
+ webhook_url = UNSET
+ else:
+ webhook_url = self.webhook_url
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update(
+ {
+ "task": task,
+ "phone": phone,
+ "result_schema": result_schema,
+ }
+ )
+ if region is not UNSET:
+ field_dict["region"] = region
+ if locale is not UNSET:
+ field_dict["locale"] = locale
+ if metadata is not UNSET:
+ field_dict["metadata"] = metadata
+ if webhook_url is not UNSET:
+ field_dict["webhook_url"] = webhook_url
+
+ return field_dict
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.create_agentic_call_request_metadata import (
+ CreateAgenticCallRequestMetadata,
+ )
+ from ..models.create_agentic_call_request_result_schema import (
+ CreateAgenticCallRequestResultSchema,
+ )
+
+ d = dict(src_dict)
+ task = d.pop("task")
+
+ phone = d.pop("phone")
+
+ result_schema = CreateAgenticCallRequestResultSchema.from_dict(
+ d.pop("result_schema")
+ )
+
+ def _parse_region(data: object) -> None | str | Unset:
+ if data is None:
+ return data
+ if isinstance(data, Unset):
+ return data
+ return cast(None | str | Unset, data)
+
+ region = _parse_region(d.pop("region", UNSET))
+
+ def _parse_locale(data: object) -> None | str | Unset:
+ if data is None:
+ return data
+ if isinstance(data, Unset):
+ return data
+ return cast(None | str | Unset, data)
+
+ locale = _parse_locale(d.pop("locale", UNSET))
+
+ _metadata = d.pop("metadata", UNSET)
+ metadata: CreateAgenticCallRequestMetadata | Unset
+ if isinstance(_metadata, Unset):
+ metadata = UNSET
+ else:
+ metadata = CreateAgenticCallRequestMetadata.from_dict(_metadata)
+
+ def _parse_webhook_url(data: object) -> None | str | Unset:
+ if data is None:
+ return data
+ if isinstance(data, Unset):
+ return data
+ return cast(None | str | Unset, data)
+
+ webhook_url = _parse_webhook_url(d.pop("webhook_url", UNSET))
+
+ create_agentic_call_request = cls(
+ task=task,
+ phone=phone,
+ result_schema=result_schema,
+ region=region,
+ locale=locale,
+ metadata=metadata,
+ webhook_url=webhook_url,
+ )
+
+ return create_agentic_call_request
diff --git a/src/calle/generated/models/create_agentic_call_request_metadata.py b/src/calle/generated/models/create_agentic_call_request_metadata.py
new file mode 100644
index 0000000..b10e42d
--- /dev/null
+++ b/src/calle/generated/models/create_agentic_call_request_metadata.py
@@ -0,0 +1,48 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+
+T = TypeVar("T", bound="CreateAgenticCallRequestMetadata")
+
+
+@_attrs_define
+class CreateAgenticCallRequestMetadata:
+ """ """
+
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+ def to_dict(self) -> dict[str, Any]:
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+
+ return field_dict
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ create_agentic_call_request_metadata = cls()
+
+ create_agentic_call_request_metadata.additional_properties = d
+ return create_agentic_call_request_metadata
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/calle/generated/models/create_agentic_call_request_result_schema.py b/src/calle/generated/models/create_agentic_call_request_result_schema.py
new file mode 100644
index 0000000..ccfd56d
--- /dev/null
+++ b/src/calle/generated/models/create_agentic_call_request_result_schema.py
@@ -0,0 +1,52 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+
+T = TypeVar("T", bound="CreateAgenticCallRequestResultSchema")
+
+
+@_attrs_define
+class CreateAgenticCallRequestResultSchema:
+ """Required closed flat JSON Schema using the same calle.result.scalar-object.v1 profile as Goal. At most 32 string,
+ boolean, integer or number properties; additionalProperties must be false. Nested objects, arrays, null values and
+ schema combinators are unsupported.
+
+ """
+
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+ def to_dict(self) -> dict[str, Any]:
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+
+ return field_dict
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ create_agentic_call_request_result_schema = cls()
+
+ create_agentic_call_request_result_schema.additional_properties = d
+ return create_agentic_call_request_result_schema
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/calle/generated/models/create_call_request.py b/src/calle/generated/models/create_call_request.py
index ebcfedf..afd3618 100644
--- a/src/calle/generated/models/create_call_request.py
+++ b/src/calle/generated/models/create_call_request.py
@@ -30,7 +30,9 @@ class CreateCallRequest:
task (str): Natural-language instruction for the call task. Include the goal, relevant details the voice agent
should know, and the exact information you want collected.
recipients (list[CallTaskRecipientRequest] | None | Unset): Optional explicit recipients for this call task.
- Omit it when the task text already contains the phone targets CALL-E should use.
+ Omit it when the task text already contains the phone targets CALL-E should use. The default outbound line
+ permits one phone number in total across all recipients. Multiple targets require an eligible purchased outbound
+ number; this also applies to targets inferred from task text.
result_schema (CreateCallRequestResultSchemaType0 | None | Unset): Optional JSON Schema object that defines the
structured result CALL-E should extract for the whole call task.
diff --git a/src/calle/generated/models/goal_run.py b/src/calle/generated/models/goal_run.py
index 6f2c592..fecad4b 100644
--- a/src/calle/generated/models/goal_run.py
+++ b/src/calle/generated/models/goal_run.py
@@ -6,6 +6,14 @@
from attrs import define as _attrs_define
+from ..models.business_result_status import BusinessResultStatus
+from ..models.business_result_status import check_business_result_status
+from ..models.call_outcome_type_1 import CallOutcomeType1
+from ..models.call_outcome_type_1 import check_call_outcome_type_1
+from ..models.call_outcome_type_2_type_1 import CallOutcomeType2Type1
+from ..models.call_outcome_type_2_type_1 import check_call_outcome_type_2_type_1
+from ..models.call_outcome_type_3_type_1 import CallOutcomeType3Type1
+from ..models.call_outcome_type_3_type_1 import check_call_outcome_type_3_type_1
from ..models.goal_run_object import check_goal_run_object
from ..models.goal_run_object import GoalRunObject
from ..models.goal_run_status import check_goal_run_status
@@ -14,6 +22,7 @@
import datetime
if TYPE_CHECKING:
+ from ..models.call_transcript_turn import CallTranscriptTurn
from ..models.goal_run_error import GoalRunError
from ..models.goal_run_result_type_0 import GoalRunResultType0
from ..models.goal_run_spec_snapshot import GoalRunSpecSnapshot
@@ -24,9 +33,8 @@
@_attrs_define
class GoalRun:
- """Public projection of one phone-specific execution of a published Goal. A non-null `result`
- is a successfully parsed and persisted object. A non-null `error` means the Run will not
- produce a result. When both are null, continue polling.
+ """Public projection of one phone-specific execution of a published Goal. Execution, telephone
+ outcome and business result readiness are independent. Poll only while result_status is pending.
Attributes:
object_ (GoalRunObject):
@@ -38,16 +46,23 @@ class GoalRun:
is different from the Goal Run `id` and nested `run_id`; it does not expose other provider
diagnostics and must not be treated as an independent answered-call boolean.
run_spec (GoalRunSpecSnapshot): Exact immutable RunSpec identity and version pinned by a Goal Run.
- status (GoalRunStatus): Stable telephone execution state. `queued` and `in_progress` are non-terminal;
- `completed`,
- `failed`, and `canceled` are terminal. A completed call can still have `result: null` and
- `error: null` briefly while CALL-E parses and saves the result.
+ status (GoalRunStatus): Stable execution state. No-answer, busy and declined calls complete execution normally.
+ Technical execution failures are failed; explicit cancellation is canceled. Completed execution
+ may still have result_status=pending while its business result is being processed.
+ call_outcome (CallOutcomeType1 | CallOutcomeType2Type1 | CallOutcomeType3Type1 | None): Reported telephone
+ outcome, separate from business success and billing connection evidence. Null before a telephone outcome is
+ known or when execution is canceled or fails technically.
+ result_status (BusinessResultStatus): Pending means keep polling. Available includes an empty result object.
+ Unavailable means no schema-valid business result could be produced; inspect error for technical failures. Not
+ applicable is used for cancellation and technical execution failure.
+ transcript (list[CallTranscriptTurn]): Recorded conversation turns in order, independent of business result
+ readiness or errors. Always present; empty before terminal execution or when no transcript is available.
result (GoalRunResultType0 | None): Parsed result validated against the published result schema and durably
persisted, or
- `null` while processing or when the Run has an error. Its keys vary by Goal.
- error (GoalRunError | None): Unified execution or result-processing error, or `null`. Branch on `code`; keep
- `message`
- for logs and operators. A non-null error is final and is mutually exclusive with `result`.
+ `null` while pending, unavailable, not applicable, or on a technical error. Its keys vary by Goal.
+ error (GoalRunError | None): Technical execution or result-processing error, or null. Ordinary telephone
+ outcomes,
+ cancellation and insufficient business evidence are represented by call_outcome/result_status.
created_at (datetime.datetime): UTC time at which CALL-E durably accepted this Goal Run.
completed_at (datetime.datetime | None): UTC telephone-execution completion time, or `null` while execution is
non-terminal.
@@ -60,6 +75,11 @@ class GoalRun:
call_id: None | str
run_spec: GoalRunSpecSnapshot
status: GoalRunStatus
+ call_outcome: (
+ CallOutcomeType1 | CallOutcomeType2Type1 | CallOutcomeType3Type1 | None
+ )
+ result_status: BusinessResultStatus
+ transcript: list[CallTranscriptTurn]
result: GoalRunResultType0 | None
error: GoalRunError | None
created_at: datetime.datetime
@@ -84,6 +104,23 @@ def to_dict(self) -> dict[str, Any]:
status: str = self.status
+ call_outcome: None | str
+ if isinstance(self.call_outcome, str):
+ call_outcome = self.call_outcome
+ elif isinstance(self.call_outcome, str):
+ call_outcome = self.call_outcome
+ elif isinstance(self.call_outcome, str):
+ call_outcome = self.call_outcome
+ else:
+ call_outcome = self.call_outcome
+
+ result_status: str = self.result_status
+
+ transcript = []
+ for transcript_item_data in self.transcript:
+ transcript_item = transcript_item_data.to_dict()
+ transcript.append(transcript_item)
+
result: dict[str, Any] | None
if isinstance(self.result, GoalRunResultType0):
result = self.result.to_dict()
@@ -115,6 +152,9 @@ def to_dict(self) -> dict[str, Any]:
"call_id": call_id,
"run_spec": run_spec,
"status": status,
+ "call_outcome": call_outcome,
+ "result_status": result_status,
+ "transcript": transcript,
"result": result,
"error": error,
"created_at": created_at,
@@ -126,6 +166,7 @@ def to_dict(self) -> dict[str, Any]:
@classmethod
def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.call_transcript_turn import CallTranscriptTurn
from ..models.goal_run_error import GoalRunError
from ..models.goal_run_result_type_0 import GoalRunResultType0
from ..models.goal_run_spec_snapshot import GoalRunSpecSnapshot
@@ -150,6 +191,55 @@ def _parse_call_id(data: object) -> None | str:
status = check_goal_run_status(d.pop("status"))
+ def _parse_call_outcome(
+ data: object,
+ ) -> CallOutcomeType1 | CallOutcomeType2Type1 | CallOutcomeType3Type1 | None:
+ if data is None:
+ return data
+ try:
+ if not isinstance(data, str):
+ raise TypeError()
+ componentsschemas_call_outcome_type_1 = check_call_outcome_type_1(data)
+
+ return componentsschemas_call_outcome_type_1
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ try:
+ if not isinstance(data, str):
+ raise TypeError()
+ componentsschemas_call_outcome_type_2_type_1 = (
+ check_call_outcome_type_2_type_1(data)
+ )
+
+ return componentsschemas_call_outcome_type_2_type_1
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ try:
+ if not isinstance(data, str):
+ raise TypeError()
+ componentsschemas_call_outcome_type_3_type_1 = (
+ check_call_outcome_type_3_type_1(data)
+ )
+
+ return componentsschemas_call_outcome_type_3_type_1
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ return cast(
+ CallOutcomeType1 | CallOutcomeType2Type1 | CallOutcomeType3Type1 | None,
+ data,
+ )
+
+ call_outcome = _parse_call_outcome(d.pop("call_outcome"))
+
+ result_status = check_business_result_status(d.pop("result_status"))
+
+ transcript = []
+ _transcript = d.pop("transcript")
+ for transcript_item_data in _transcript:
+ transcript_item = CallTranscriptTurn.from_dict(transcript_item_data)
+
+ transcript.append(transcript_item)
+
def _parse_result(data: object) -> GoalRunResultType0 | None:
if data is None:
return data
@@ -205,6 +295,9 @@ def _parse_completed_at(data: object) -> datetime.datetime | None:
call_id=call_id,
run_spec=run_spec,
status=status,
+ call_outcome=call_outcome,
+ result_status=result_status,
+ transcript=transcript,
result=result,
error=error,
created_at=created_at,
diff --git a/src/calle/generated/models/goal_run_error.py b/src/calle/generated/models/goal_run_error.py
index ae09263..7a138ca 100644
--- a/src/calle/generated/models/goal_run_error.py
+++ b/src/calle/generated/models/goal_run_error.py
@@ -16,7 +16,7 @@
@_attrs_define
class GoalRunError:
- """Unified safe error returned when a Goal Run cannot produce a usable result.
+ """Technical execution or result-processing error. Ordinary call outcomes are not errors.
Attributes:
code (GoalRunErrorCode):
diff --git a/src/calle/generated/models/goal_run_error_code.py b/src/calle/generated/models/goal_run_error_code.py
index edf42be..7ab9156 100644
--- a/src/calle/generated/models/goal_run_error_code.py
+++ b/src/calle/generated/models/goal_run_error_code.py
@@ -1,24 +1,13 @@
from typing import Literal
GoalRunErrorCode = Literal[
- "call_failed",
- "canceled",
- "declined",
- "no_answer",
- "result_failed",
- "result_invalid",
- "result_unavailable",
- "timed_out",
+ "call_failed", "result_failed", "result_invalid", "timed_out"
]
GOAL_RUN_ERROR_CODE_VALUES: set[GoalRunErrorCode] = {
"call_failed",
- "canceled",
- "declined",
- "no_answer",
"result_failed",
"result_invalid",
- "result_unavailable",
"timed_out",
}
diff --git a/src/calle/generated/models/goal_run_result_type_0.py b/src/calle/generated/models/goal_run_result_type_0.py
index 3dddc86..a0a5550 100644
--- a/src/calle/generated/models/goal_run_result_type_0.py
+++ b/src/calle/generated/models/goal_run_result_type_0.py
@@ -16,7 +16,7 @@
@_attrs_define
class GoalRunResultType0:
"""Parsed result validated against the published result schema and durably persisted, or
- `null` while processing or when the Run has an error. Its keys vary by Goal.
+ `null` while pending, unavailable, not applicable, or on a technical error. Its keys vary by Goal.
"""
diff --git a/src/calle/generated/models/webhook_call_data.py b/src/calle/generated/models/webhook_call_data.py
new file mode 100644
index 0000000..663919f
--- /dev/null
+++ b/src/calle/generated/models/webhook_call_data.py
@@ -0,0 +1,283 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, TYPE_CHECKING
+
+from attrs import define as _attrs_define
+
+
+from ..models.call_status import CallStatus
+from ..models.call_status import check_call_status
+from ..models.webhook_call_data_object import check_webhook_call_data_object
+from ..models.webhook_call_data_object import WebhookCallDataObject
+from typing import cast
+import datetime
+
+if TYPE_CHECKING:
+ from ..models.call_task_recipient import CallTaskRecipient
+ from ..models.completion_confidence import CompletionConfidence
+ from ..models.webhook_call_data_metadata import WebhookCallDataMetadata
+ from ..models.webhook_call_data_structured_result_type_0 import (
+ WebhookCallDataStructuredResultType0,
+ )
+
+
+T = TypeVar("T", bound="WebhookCallData")
+
+
+@_attrs_define
+class WebhookCallData:
+ """
+ Attributes:
+ id (str): Public CALL-E call task identifier. Store this id to fetch state, list events, and correlate webhooks.
+ object_ (WebhookCallDataObject): Always `call_task` for call task responses.
+ status (CallStatus): Current lifecycle state of a CALL-E call. `in_progress` includes post-call result
+ finalization; terminal states are published only after the post-call outcome is available.
+ task (str): Original task instruction submitted when the call task was created.
+ recipients (list[CallTaskRecipient]): Recipient states for this call task.
+ structured_result (None | WebhookCallDataStructuredResultType0): Schema-valid structured result object extracted
+ for the whole call task using `result_schema`.
+
+ `null` means CALL-E could not produce a schema-valid task-level result from the terminal call evidence, or no
+ `result_schema` was provided. Check recipient-level `structured_result` when you use `recipient_result_schema`
+ for batch calls.
+ summary (None | str): Short human-readable summary of the call task outcome. `null` while the call task is still
+ running or when no useful summary is available.
+ task_completed (bool | None): Post-summary judgment for whether the task reached a clear end state for the user.
+ `null` until CALL-E has a terminal post-summary outcome.
+ completion_confidence (CompletionConfidence | None): Confidence for `task_completed`. `null` until CALL-E has a
+ terminal post-summary outcome.
+ evidence (list[str]): Short evidence items supporting the post-summary task outcome. Empty until CALL-E has
+ terminal evidence.
+ metadata (WebhookCallDataMetadata): Caller-owned metadata submitted on the create call request.
+ failure_code (None | str): Machine-readable failure reason when `status` is `failed`; otherwise `null`.
+ failure_message (None | str): Human-readable failure explanation when `status` is `failed`; otherwise `null`.
+ created_at (datetime.datetime): ISO 8601 timestamp when CALL-E accepted the call request.
+ completed_at (datetime.datetime | None): ISO 8601 timestamp when the complete terminal call result was
+ published. `null` while queued or in progress.
+ """
+
+ id: str
+ object_: WebhookCallDataObject
+ status: CallStatus
+ task: str
+ recipients: list[CallTaskRecipient]
+ structured_result: None | WebhookCallDataStructuredResultType0
+ summary: None | str
+ task_completed: bool | None
+ completion_confidence: CompletionConfidence | None
+ evidence: list[str]
+ metadata: WebhookCallDataMetadata
+ failure_code: None | str
+ failure_message: None | str
+ created_at: datetime.datetime
+ completed_at: datetime.datetime | None
+
+ def to_dict(self) -> dict[str, Any]:
+ from ..models.completion_confidence import CompletionConfidence
+ from ..models.webhook_call_data_structured_result_type_0 import (
+ WebhookCallDataStructuredResultType0,
+ )
+
+ id = self.id
+
+ object_: str = self.object_
+
+ status: str = self.status
+
+ task = self.task
+
+ recipients = []
+ for recipients_item_data in self.recipients:
+ recipients_item = recipients_item_data.to_dict()
+ recipients.append(recipients_item)
+
+ structured_result: dict[str, Any] | None
+ if isinstance(self.structured_result, WebhookCallDataStructuredResultType0):
+ structured_result = self.structured_result.to_dict()
+ else:
+ structured_result = self.structured_result
+
+ summary: None | str
+ summary = self.summary
+
+ task_completed: bool | None
+ task_completed = self.task_completed
+
+ completion_confidence: dict[str, Any] | None
+ if isinstance(self.completion_confidence, CompletionConfidence):
+ completion_confidence = self.completion_confidence.to_dict()
+ else:
+ completion_confidence = self.completion_confidence
+
+ evidence = self.evidence
+
+ metadata = self.metadata.to_dict()
+
+ failure_code: None | str
+ failure_code = self.failure_code
+
+ failure_message: None | str
+ failure_message = self.failure_message
+
+ created_at = self.created_at.isoformat()
+
+ completed_at: None | str
+ if isinstance(self.completed_at, datetime.datetime):
+ completed_at = self.completed_at.isoformat()
+ else:
+ completed_at = self.completed_at
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update(
+ {
+ "id": id,
+ "object": object_,
+ "status": status,
+ "task": task,
+ "recipients": recipients,
+ "structured_result": structured_result,
+ "summary": summary,
+ "task_completed": task_completed,
+ "completion_confidence": completion_confidence,
+ "evidence": evidence,
+ "metadata": metadata,
+ "failure_code": failure_code,
+ "failure_message": failure_message,
+ "created_at": created_at,
+ "completed_at": completed_at,
+ }
+ )
+
+ return field_dict
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.call_task_recipient import CallTaskRecipient
+ from ..models.completion_confidence import CompletionConfidence
+ from ..models.webhook_call_data_metadata import WebhookCallDataMetadata
+ from ..models.webhook_call_data_structured_result_type_0 import (
+ WebhookCallDataStructuredResultType0,
+ )
+
+ d = dict(src_dict)
+ id = d.pop("id")
+
+ object_ = check_webhook_call_data_object(d.pop("object"))
+
+ status = check_call_status(d.pop("status"))
+
+ task = d.pop("task")
+
+ recipients = []
+ _recipients = d.pop("recipients")
+ for recipients_item_data in _recipients:
+ recipients_item = CallTaskRecipient.from_dict(recipients_item_data)
+
+ recipients.append(recipients_item)
+
+ def _parse_structured_result(
+ data: object,
+ ) -> None | WebhookCallDataStructuredResultType0:
+ if data is None:
+ return data
+ try:
+ if not isinstance(data, dict):
+ raise TypeError()
+ structured_result_type_0 = (
+ WebhookCallDataStructuredResultType0.from_dict(data)
+ )
+
+ return structured_result_type_0
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ return cast(None | WebhookCallDataStructuredResultType0, data)
+
+ structured_result = _parse_structured_result(d.pop("structured_result"))
+
+ def _parse_summary(data: object) -> None | str:
+ if data is None:
+ return data
+ return cast(None | str, data)
+
+ summary = _parse_summary(d.pop("summary"))
+
+ def _parse_task_completed(data: object) -> bool | None:
+ if data is None:
+ return data
+ return cast(bool | None, data)
+
+ task_completed = _parse_task_completed(d.pop("task_completed"))
+
+ def _parse_completion_confidence(data: object) -> CompletionConfidence | None:
+ if data is None:
+ return data
+ try:
+ if not isinstance(data, dict):
+ raise TypeError()
+ completion_confidence_type_0 = CompletionConfidence.from_dict(data)
+
+ return completion_confidence_type_0
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ return cast(CompletionConfidence | None, data)
+
+ completion_confidence = _parse_completion_confidence(
+ d.pop("completion_confidence")
+ )
+
+ evidence = cast(list[str], d.pop("evidence"))
+
+ metadata = WebhookCallDataMetadata.from_dict(d.pop("metadata"))
+
+ def _parse_failure_code(data: object) -> None | str:
+ if data is None:
+ return data
+ return cast(None | str, data)
+
+ failure_code = _parse_failure_code(d.pop("failure_code"))
+
+ def _parse_failure_message(data: object) -> None | str:
+ if data is None:
+ return data
+ return cast(None | str, data)
+
+ failure_message = _parse_failure_message(d.pop("failure_message"))
+
+ created_at = datetime.datetime.fromisoformat(d.pop("created_at"))
+
+ def _parse_completed_at(data: object) -> datetime.datetime | None:
+ if data is None:
+ return data
+ try:
+ if not isinstance(data, str):
+ raise TypeError()
+ completed_at_type_0 = datetime.datetime.fromisoformat(data)
+
+ return completed_at_type_0
+ except (TypeError, ValueError, AttributeError, KeyError):
+ pass
+ return cast(datetime.datetime | None, data)
+
+ completed_at = _parse_completed_at(d.pop("completed_at"))
+
+ webhook_call_data = cls(
+ id=id,
+ object_=object_,
+ status=status,
+ task=task,
+ recipients=recipients,
+ structured_result=structured_result,
+ summary=summary,
+ task_completed=task_completed,
+ completion_confidence=completion_confidence,
+ evidence=evidence,
+ metadata=metadata,
+ failure_code=failure_code,
+ failure_message=failure_message,
+ created_at=created_at,
+ completed_at=completed_at,
+ )
+
+ return webhook_call_data
diff --git a/src/calle/generated/models/webhook_call_data_metadata.py b/src/calle/generated/models/webhook_call_data_metadata.py
new file mode 100644
index 0000000..447c05c
--- /dev/null
+++ b/src/calle/generated/models/webhook_call_data_metadata.py
@@ -0,0 +1,48 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+
+T = TypeVar("T", bound="WebhookCallDataMetadata")
+
+
+@_attrs_define
+class WebhookCallDataMetadata:
+ """Caller-owned metadata submitted on the create call request."""
+
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+ def to_dict(self) -> dict[str, Any]:
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+
+ return field_dict
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ webhook_call_data_metadata = cls()
+
+ webhook_call_data_metadata.additional_properties = d
+ return webhook_call_data_metadata
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/calle/generated/models/webhook_call_data_object.py b/src/calle/generated/models/webhook_call_data_object.py
new file mode 100644
index 0000000..922c400
--- /dev/null
+++ b/src/calle/generated/models/webhook_call_data_object.py
@@ -0,0 +1,15 @@
+from typing import Literal
+
+WebhookCallDataObject = Literal["call_task"]
+
+WEBHOOK_CALL_DATA_OBJECT_VALUES: set[WebhookCallDataObject] = {
+ "call_task",
+}
+
+
+def check_webhook_call_data_object(value: str) -> WebhookCallDataObject:
+ if value in WEBHOOK_CALL_DATA_OBJECT_VALUES:
+ return value
+ raise TypeError(
+ f"Unexpected value {value!r}. Expected one of {WEBHOOK_CALL_DATA_OBJECT_VALUES!r}"
+ )
diff --git a/src/calle/generated/models/webhook_call_data_structured_result_type_0.py b/src/calle/generated/models/webhook_call_data_structured_result_type_0.py
new file mode 100644
index 0000000..935af8c
--- /dev/null
+++ b/src/calle/generated/models/webhook_call_data_structured_result_type_0.py
@@ -0,0 +1,54 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar
+
+from attrs import define as _attrs_define
+from attrs import field as _attrs_field
+
+
+T = TypeVar("T", bound="WebhookCallDataStructuredResultType0")
+
+
+@_attrs_define
+class WebhookCallDataStructuredResultType0:
+ """Schema-valid structured result object extracted for the whole call task using `result_schema`.
+
+ `null` means CALL-E could not produce a schema-valid task-level result from the terminal call evidence, or no
+ `result_schema` was provided. Check recipient-level `structured_result` when you use `recipient_result_schema` for
+ batch calls.
+
+ """
+
+ additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict)
+
+ def to_dict(self) -> dict[str, Any]:
+
+ field_dict: dict[str, Any] = {}
+ field_dict.update(self.additional_properties)
+
+ return field_dict
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ d = dict(src_dict)
+ webhook_call_data_structured_result_type_0 = cls()
+
+ webhook_call_data_structured_result_type_0.additional_properties = d
+ return webhook_call_data_structured_result_type_0
+
+ @property
+ def additional_keys(self) -> list[str]:
+ return list(self.additional_properties.keys())
+
+ def __getitem__(self, key: str) -> Any:
+ return self.additional_properties[key]
+
+ def __setitem__(self, key: str, value: Any) -> None:
+ self.additional_properties[key] = value
+
+ def __delitem__(self, key: str) -> None:
+ del self.additional_properties[key]
+
+ def __contains__(self, key: str) -> bool:
+ return key in self.additional_properties
diff --git a/src/calle/generated/models/webhook_event.py b/src/calle/generated/models/webhook_event.py
new file mode 100644
index 0000000..97c0a90
--- /dev/null
+++ b/src/calle/generated/models/webhook_event.py
@@ -0,0 +1,78 @@
+from __future__ import annotations
+
+from collections.abc import Mapping
+from typing import Any, TypeVar, TYPE_CHECKING
+
+from attrs import define as _attrs_define
+
+
+from ..models.webhook_event_type import check_webhook_event_type
+from ..models.webhook_event_type import WebhookEventType
+import datetime
+
+if TYPE_CHECKING:
+ from ..models.webhook_call_data import WebhookCallData
+
+
+T = TypeVar("T", bound="WebhookEvent")
+
+
+@_attrs_define
+class WebhookEvent:
+ """Event payload CALL-E sends to your webhook receiver.
+
+ Attributes:
+ id (str): Unique webhook event id. Use it as an idempotency key when processing webhook side effects.
+ type_ (WebhookEventType): Terminal webhook event type.
+ created_at (datetime.datetime): ISO 8601 timestamp when CALL-E created this webhook event.
+ data (WebhookCallData):
+ """
+
+ id: str
+ type_: WebhookEventType
+ created_at: datetime.datetime
+ data: WebhookCallData
+
+ def to_dict(self) -> dict[str, Any]:
+ id = self.id
+
+ type_: str = self.type_
+
+ created_at = self.created_at.isoformat()
+
+ data = self.data.to_dict()
+
+ field_dict: dict[str, Any] = {}
+
+ field_dict.update(
+ {
+ "id": id,
+ "type": type_,
+ "created_at": created_at,
+ "data": data,
+ }
+ )
+
+ return field_dict
+
+ @classmethod
+ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T:
+ from ..models.webhook_call_data import WebhookCallData
+
+ d = dict(src_dict)
+ id = d.pop("id")
+
+ type_ = check_webhook_event_type(d.pop("type"))
+
+ created_at = datetime.datetime.fromisoformat(d.pop("created_at"))
+
+ data = WebhookCallData.from_dict(d.pop("data"))
+
+ webhook_event = cls(
+ id=id,
+ type_=type_,
+ created_at=created_at,
+ data=data,
+ )
+
+ return webhook_event
diff --git a/src/calle/goals.py b/src/calle/goals.py
index 17592c5..6d3854d 100644
--- a/src/calle/goals.py
+++ b/src/calle/goals.py
@@ -87,7 +87,7 @@ def wait_for_result(
goal_run_id,
timeout_seconds=remaining_seconds,
)
- if run.get("result") is not None or run.get("error") is not None:
+ if run["result_status"] != "pending":
return run
remaining_seconds = deadline - time.monotonic()
if remaining_seconds <= 0:
@@ -113,7 +113,7 @@ def run_and_wait(
variables=variables,
idempotency_key=idempotency_key,
)
- if run.get("result") is not None or run.get("error") is not None:
+ if run["result_status"] != "pending":
return run
return self.wait_for_result(
goal_id,
diff --git a/tests/test_calls.py b/tests/test_calls.py
index b0b9796..3c22e48 100644
--- a/tests/test_calls.py
+++ b/tests/test_calls.py
@@ -10,105 +10,121 @@
COMPLETED_CALL = {
"id": "call_123",
- "object": "call_task",
+ "call_id": "billing-call-123",
+ "object": "call",
"status": "completed",
+ "call_outcome": "completed", "result_status": "available",
+ "transcript": [],
"task": "Call.",
- "recipients": [
- {
- "id": "rcp_123",
- "phones": ["+14155550100"],
- "region": "US",
- "locale": "en-US",
- "status": "completed",
- "structured_result": {"can_attend": "yes"},
- "summary": "Recipient can attend.",
- "attempts": [
- {
- "id": "att_123",
- "phone": "+14155550100",
- "status": "completed",
- "started_at": "2026-05-31T00:00:10Z",
- "completed_at": "2026-05-31T00:01:00Z",
- "summary": "Recipient can attend.",
- "transcript_turns": [{"offset_seconds": 2, "speaker": "user", "text": "Yes."}],
- "provider_call_id": "provider_123",
- "failure_code": None,
- "failure_message": None,
- }
- ],
- }
- ],
- "structured_result": {"completed_count": 1},
- "summary": "Done.",
- "task_completed": True,
- "completion_confidence": {"score": 0.92, "label": "high"},
- "evidence": ["The recipient said yes."],
+ "phone": "+14155550100", "region": "US", "locale": "en-US",
+ "result": {"completed_count": 1}, "error": None,
"metadata": {"workflow_run_id": "wf_123"},
- "failure_code": None,
- "failure_message": None,
"created_at": "2026-05-31T00:00:00Z",
"completed_at": "2026-05-31T00:01:00Z",
}
+@respx.mock
+@pytest.mark.parametrize("call_id", [None, "billing-call-123"])
+def test_billing_call_id_is_separate_from_api_id(call_id: str | None) -> None:
+ from calle.generated.models import AgenticCall
+
+ body = {**COMPLETED_CALL, "call_id": call_id}
+ respx.get("https://api.heycall-e.com/v2/calls/call_123").mock(return_value=httpx.Response(200, json=body))
+ with CalleClient(api_key="test") as client:
+ call = client.calls.get("call_123")
+ assert call["id"] == "call_123"
+ assert call["call_id"] == call_id
+ generated = AgenticCall.from_dict(call)
+ assert generated.call_id == call_id
+ assert generated.to_dict()["call_id"] == call_id
+
+
@respx.mock
def test_create_call_uses_default_production_base_url() -> None:
- route = respx.post("https://api.heycall-e.com/v1/calls").mock(return_value=httpx.Response(200, json=COMPLETED_CALL))
+ route = respx.post("https://api.heycall-e.com/v2/calls").mock(return_value=httpx.Response(200, json=COMPLETED_CALL))
client = CalleClient(api_key="key_test")
client.calls.create(
task="Call.",
- recipient={"phone": "+14155550100", "region": "US", "locale": "en-US"},
- result_schema={"type": "object", "properties": {}},
+ phone="+14155550100", region="US", locale="en-US", idempotency_key="wf_123",
+ result_schema={"type": "object", "additionalProperties": False, "properties": {}},
)
assert route.called
+@respx.mock
+def test_create_omits_target_hints_and_returns_resolved_values() -> None:
+ route = respx.post("https://api.heycall-e.com/v2/calls").mock(return_value=httpx.Response(202, json=COMPLETED_CALL))
+ with CalleClient(api_key="test") as client:
+ call = client.calls.create(task="Ask in English.", phone="+14155550100", idempotency_key="infer-target",
+ result_schema={"type": "object", "properties": {}, "additionalProperties": False})
+ body = json.loads(route.calls.last.request.content)
+ assert "region" not in body and "locale" not in body
+ assert (call["region"], call["locale"]) == ("US", "en-US")
+
+
+@respx.mock
+def test_transcript_survives_unavailable_business_result() -> None:
+ from calle.generated.models import AgenticCall
+
+ turns = [{"speaker": "bot", "offset_seconds": 0, "text": "Hello."},
+ {"speaker": "unknown", "offset_seconds": None, "text": "Unattributed words."}]
+ body = {**COMPLETED_CALL, "result_status": "unavailable", "result": None, "transcript": turns}
+ respx.get("https://api.heycall-e.com/v2/calls/call_123").mock(return_value=httpx.Response(200, json=body))
+ with CalleClient(api_key="test") as client:
+ result = client.calls.wait_for_result("call_123", interval_seconds=0.001, timeout_seconds=0.1)
+ assert result["transcript"] == turns
+ assert AgenticCall.from_dict(result).to_dict()["transcript"] == turns
+
+
@respx.mock
def test_create_call_sends_auth_and_idempotency_headers() -> None:
- route = respx.post("https://api.heycall-e.com/v1/calls").mock(return_value=httpx.Response(200, json=COMPLETED_CALL))
+ route = respx.post("https://api.heycall-e.com/v2/calls").mock(return_value=httpx.Response(200, json=COMPLETED_CALL))
client = CalleClient(api_key="key_test", base_url="https://api.heycall-e.com")
call = client.calls.create(
task="Call.",
- recipient={"phone": "+14155550100", "region": "US", "locale": "en-US"},
- result_schema={"type": "object", "properties": {}},
+ phone="+14155550100", region="US", locale="en-US", idempotency_key="wf_123",
+ result_schema={"type": "object", "additionalProperties": False, "properties": {}},
webhook_url="https://example.com/webhook",
- idempotency_key="wf_123",
)
request = route.calls.last.request
assert request.headers["authorization"] == "Bearer key_test"
assert request.headers["idempotency-key"] == "wf_123"
payload = json.loads(request.content)
- assert payload["recipients"] == [{"phones": ["+14155550100"], "region": "US", "locale": "en-US"}]
- assert payload["result_schema"] == {"type": "object", "properties": {}}
+ assert payload["phone"] == "+14155550100"
+ assert "recipients" not in payload
+ assert payload["result_schema"] == {"type": "object", "additionalProperties": False, "properties": {}}
assert payload["webhook_url"] == "https://example.com/webhook"
assert call["id"] == "call_123"
- assert call["structured_result"] == {"completed_count": 1}
- assert call["task_completed"] is True
- assert call["completion_confidence"] == {"score": 0.92, "label": "high"}
- assert call["evidence"] == ["The recipient said yes."]
- assert call["recipients"][0]["structured_result"] == {"can_attend": "yes"}
- assert "result_validation" not in call
+ assert call["result"] == {"completed_count": 1}
+ assert call["error"] is None
+ assert "structured_result" not in call
@respx.mock
-def test_create_call_can_send_task_only_request() -> None:
- route = respx.post("https://api.heycall-e.com/v1/calls").mock(return_value=httpx.Response(200, json=COMPLETED_CALL))
- client = CalleClient(api_key="key_test", base_url="https://api.heycall-e.com")
+def test_create_requires_a_stable_idempotency_key() -> None:
+ with CalleClient(api_key="test") as client:
+ with pytest.raises(ValueError, match="idempotency_key"):
+ client.calls.create(task="Call.", phone="+14155550100", region="US", locale="en-US", idempotency_key=" ", result_schema={"type": "object", "additionalProperties": False, "properties": {}})
- call = client.calls.create(task="Call +14155550100.")
- payload = json.loads(route.calls.last.request.content)
- assert payload == {"task": "Call +14155550100."}
- assert call["status"] == "completed"
+@respx.mock
+def test_cancel_returns_the_persisted_result_error() -> None:
+ canceled = {**COMPLETED_CALL, "status": "canceled", "call_outcome": None,
+ "result_status": "not_applicable", "result": None, "error": None}
+ route = respx.post("https://api.heycall-e.com/v2/calls/call_123/cancel").mock(return_value=httpx.Response(200, json=canceled))
+ with CalleClient(api_key="test") as client:
+ assert client.calls.cancel("call_123") == canceled
+ assert route.called
@respx.mock
def test_create_call_maps_api_errors() -> None:
- respx.post("https://api.heycall-e.com/v1/calls").mock(
+ respx.post("https://api.heycall-e.com/v2/calls").mock(
return_value=httpx.Response(
409,
json={"error": {"code": "idempotency_conflict", "message": "Conflict.", "details": {"key": "wf_123"}}},
@@ -119,8 +135,8 @@ def test_create_call_maps_api_errors() -> None:
with pytest.raises(CalleAPIError) as exc_info:
client.calls.create(
task="Call.",
- recipient={"phone": "+14155550100", "region": "US", "locale": "en-US"},
- result_schema={"type": "object", "properties": {}},
+ phone="+14155550100", region="US", locale="en-US", idempotency_key="wf_123",
+ result_schema={"type": "object", "additionalProperties": False, "properties": {}},
)
assert exc_info.value.code == "idempotency_conflict"
@@ -130,9 +146,9 @@ def test_create_call_maps_api_errors() -> None:
@respx.mock
def test_wait_for_result_returns_completed_call() -> None:
- queued = {**COMPLETED_CALL, "status": "queued", "structured_result": None, "completed_at": None}
- respx.get("https://api.heycall-e.com/v1/calls/call_123").mock(
- side_effect=[httpx.Response(200, json=queued), httpx.Response(200, json=COMPLETED_CALL)]
+ queued = {**COMPLETED_CALL, "status": "queued", "call_outcome": None, "result_status": "pending", "result": None, "completed_at": None}
+ respx.get("https://api.heycall-e.com/v2/calls/call_123").mock(
+ side_effect=[httpx.Response(200, json=queued), httpx.Response(200, json={**COMPLETED_CALL, "result_status": "pending", "result": None}), httpx.Response(200, json=COMPLETED_CALL)]
)
client = CalleClient(api_key="key_test", base_url="https://api.heycall-e.com")
@@ -141,28 +157,45 @@ def test_wait_for_result_returns_completed_call() -> None:
assert call["status"] == "completed"
+@pytest.mark.parametrize("outcome", ["no_answer", "busy", "declined"])
@respx.mock
-def test_wait_for_result_returns_failed_call() -> None:
- failed = {**COMPLETED_CALL, "status": "failed", "failure_code": "no_answer"}
- respx.get("https://api.heycall-e.com/v1/calls/call_123").mock(return_value=httpx.Response(200, json=failed))
+def test_wait_for_result_returns_ordinary_outcome_without_result_or_error(outcome) -> None:
+ failed = {**COMPLETED_CALL, "call_outcome": outcome, "result_status": "unavailable", "result": None, "error": None}
+ respx.get("https://api.heycall-e.com/v2/calls/call_123").mock(return_value=httpx.Response(200, json=failed))
client = CalleClient(api_key="key_test", base_url="https://api.heycall-e.com")
call = client.calls.wait_for_result("call_123", interval_seconds=0.001, timeout_seconds=0.5)
- assert call["status"] == "failed"
- assert call["failure_code"] == "no_answer"
+ assert call["status"] == "completed"
+ assert call["call_outcome"] == outcome
+ assert call["error"] is None
@respx.mock
def test_wait_for_result_raises_timeout() -> None:
- queued = {**COMPLETED_CALL, "status": "queued", "structured_result": None, "completed_at": None}
- respx.get("https://api.heycall-e.com/v1/calls/call_123").mock(return_value=httpx.Response(200, json=queued))
+ queued = {**COMPLETED_CALL, "status": "queued", "call_outcome": None, "result_status": "pending", "result": None, "completed_at": None}
+ respx.get("https://api.heycall-e.com/v2/calls/call_123").mock(return_value=httpx.Response(200, json=queued))
client = CalleClient(api_key="key_test", base_url="https://api.heycall-e.com")
with pytest.raises(CalleTimeoutError):
client.calls.wait_for_result("call_123", interval_seconds=0.001, timeout_seconds=0.002)
+def test_generated_call_round_trips_goal_compatible_result() -> None:
+ from calle.generated.models.agentic_call import AgenticCall
+ generated = AgenticCall.from_dict(COMPLETED_CALL)
+ assert generated.result.to_dict() == {"completed_count": 1}
+ assert generated.error is None
+ assert generated.to_dict()["result"] == COMPLETED_CALL["result"]
+
+
+@respx.mock
+def test_wait_accepts_empty_result() -> None:
+ respx.get("https://api.heycall-e.com/v2/calls/call_123").mock(return_value=httpx.Response(200, json={**COMPLETED_CALL, "result_status": "available", "result": {}}))
+ with CalleClient(api_key="test") as client:
+ assert client.calls.wait_for_result("call_123", interval_seconds=0.001, timeout_seconds=0.5)["result"] == {}
+
+
def test_call_id_stays_in_one_url_path_segment() -> None:
requests: list[httpx.Request] = []
@@ -180,5 +213,5 @@ def handle_request(request: httpx.Request) -> httpx.Response:
client.calls.list_events(call_id)
encoded_id = "%2E%2E%2Fgoals%3Fadmin%3D1%23fragment%252F"
- assert requests[0].url.raw_path == f"/v1/calls/{encoded_id}".encode()
- assert requests[1].url.raw_path == f"/v1/calls/{encoded_id}/events".encode()
+ assert requests[0].url.raw_path == f"/v2/calls/{encoded_id}".encode()
+ assert requests[1].url.raw_path == f"/v2/calls/{encoded_id}/events".encode()
diff --git a/tests/test_generated_client.py b/tests/test_generated_client.py
index 57fd691..61f0162 100644
--- a/tests/test_generated_client.py
+++ b/tests/test_generated_client.py
@@ -2,8 +2,8 @@
from attrs import define
from calle.generated import AuthenticatedClient, Client
-from calle.generated.api.calls.get_call import _get_kwargs as get_call_kwargs
-from calle.generated.api.calls.list_call_events import (
+from calle.generated.api.legacy_calls.get_call import _get_kwargs as get_call_kwargs
+from calle.generated.api.legacy_calls.list_call_events import (
_get_kwargs as list_call_events_kwargs,
)
@@ -69,3 +69,19 @@ def test_generated_call_id_stays_in_one_url_path_segment() -> None:
)
finally:
client.close()
+
+
+def test_generated_single_target_call_paths_encode_opaque_ids() -> None:
+ from calle.generated.api.calls.cancel_agentic_call import _get_kwargs as cancel_kwargs
+ from calle.generated.api.calls.get_agentic_call import _get_kwargs as get_kwargs
+ from calle.generated.api.calls.list_agentic_call_events import _get_kwargs as events_kwargs
+
+ with httpx.Client(base_url="https://api.heycall-e.com") as client:
+ for method, kwargs, suffix in [
+ ("GET", get_kwargs(".."), ""),
+ ("GET", events_kwargs(".."), "/events"),
+ ("POST", cancel_kwargs(".."), "/cancel"),
+ ]:
+ assert client.build_request(method, kwargs["url"]).url.raw_path == (
+ f"/v2/calls/%2E%2E{suffix}".encode()
+ )
diff --git a/tests/test_goals.py b/tests/test_goals.py
index b9b68ad..b5b4b69 100644
--- a/tests/test_goals.py
+++ b/tests/test_goals.py
@@ -40,6 +40,8 @@
"call_id": None,
"run_spec": {"id": "rspec_delivery_v4", "version": 4},
"status": "queued",
+ "call_outcome": None, "result_status": "pending",
+ "transcript": [],
"result": None,
"error": None,
"created_at": "2026-07-22T10:00:00Z",
@@ -56,6 +58,35 @@ def test_generated_goal_models_parse_public_api_examples() -> None:
assert run.id == "rgrp_delivery_8472"
assert run.call_id is None
assert run.run_spec.id == "rspec_delivery_v4"
+ assert run.transcript == []
+
+
+@respx.mock
+def test_goal_transcript_survives_result_processing_failure():
+ turns = [{"speaker": "user", "offset_seconds": 2, "text": "Goodbye."}]
+ body = {**QUEUED_RUN, "status": "completed", "result_status": "unavailable", "transcript": turns,
+ "completed_at": "2026-07-22T10:01:00Z",
+ "error": {"code": "result_failed", "message": "Result processing failed.", "detail_code": None}}
+ respx.get("https://api.heycall-e.com/v1/goals/goal_delivery/runs/rgrp_delivery_8472").mock(
+ return_value=httpx.Response(200, json=body),
+ )
+ with CalleClient(api_key="test") as client:
+ result = client.goals.wait_for_result("goal_delivery", "rgrp_delivery_8472", interval_seconds=0.001, timeout_seconds=0.1)
+ assert result["transcript"] == turns
+ assert GoalRun.from_dict(result).to_dict()["transcript"] == turns
+
+
+@pytest.mark.parametrize("outcome", ["no_answer", "busy", "declined"])
+@respx.mock
+def test_wait_finishes_with_unavailable_business_result_and_no_error(outcome):
+ body = {**QUEUED_RUN, "status": "completed", "call_outcome": outcome,
+ "result_status": "unavailable", "completed_at": "2026-07-22T10:01:00Z"}
+ route = respx.get("https://api.heycall-e.com/v1/goals/goal_delivery/runs/rgrp_delivery_8472").mock(
+ return_value=httpx.Response(200, json=body),
+ )
+ with CalleClient(api_key="test") as client:
+ assert client.goals.wait_for_result("goal_delivery", "rgrp_delivery_8472", interval_seconds=0.001, timeout_seconds=0.1) == body
+ assert route.call_count == 1
@respx.mock
@@ -153,7 +184,7 @@ def test_wait_for_result_ignores_completed_status_until_result_exists() -> None:
succeeded = {
**materializing,
"call_id": "calling_call_delivery_8472",
- "result": {"delivery_outcome": "confirmed"},
+ "result_status": "available", "call_outcome": "completed", "result": {"delivery_outcome": "confirmed"},
}
route = respx.get("https://api.heycall-e.com/v1/goals/goal_delivery/runs/rgrp_delivery_8472").mock(
side_effect=[
@@ -179,9 +210,9 @@ def test_wait_for_result_ignores_completed_status_until_result_exists() -> None:
def test_wait_for_result_returns_domain_error_as_data() -> None:
failed = {
**QUEUED_RUN,
- "status": "failed",
+ "status": "failed", "result_status": "not_applicable",
"error": {
- "code": "no_answer",
+ "code": "call_failed",
"message": "No human answered the call.",
"detail_code": "provider_no_answer",
},
@@ -200,7 +231,7 @@ def test_wait_for_result_returns_domain_error_as_data() -> None:
)
assert run["error"] == {
- "code": "no_answer",
+ "code": "call_failed",
"message": "No human answered the call.",
"detail_code": "provider_no_answer",
}
@@ -217,7 +248,7 @@ def test_run_and_wait_polls_returned_goal_run_identity() -> None:
json={
**QUEUED_RUN,
"status": "completed",
- "result": {"delivery_outcome": "confirmed"},
+ "result_status": "available", "call_outcome": "completed", "result": {"delivery_outcome": "confirmed"},
"completed_at": "2026-07-22T10:01:00Z",
},
)
@@ -242,7 +273,7 @@ def test_run_and_wait_returns_terminal_idempotent_replay_without_polling() -> No
succeeded = {
**QUEUED_RUN,
"status": "completed",
- "result": {"delivery_outcome": "confirmed"},
+ "result_status": "available", "call_outcome": "completed", "result": {"delivery_outcome": "confirmed"},
"completed_at": "2026-07-22T10:01:00Z",
}
create_route = respx.post("https://api.heycall-e.com/v1/goals/goal_delivery/runs").mock(
diff --git a/uv.lock b/uv.lock
index eeacc83..3223065 100644
--- a/uv.lock
+++ b/uv.lock
@@ -97,7 +97,7 @@ wheels = [
[[package]]
name = "calle-ai"
-version = "0.7.1"
+version = "1.0.0"
source = { editable = "." }
dependencies = [
{ name = "attrs" },
@@ -154,12 +154,16 @@ sdist = { url = "https://files.pythonhosted.org/packages/9e/ef/008a1939e372c0632
wheels = [
{ url = "https://files.pythonhosted.org/packages/ad/5a/4707a0dc1f203f5dde5a907b0d4e3c25d71120241048bd5bc6f1bb9d4e71/cffi-2.1.1-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:154852545011f779917b11c78db2358d095da62a9a172b78ad0a583ee5adc0d0", size = 211805, upload-time = "2026-08-03T21:19:31.867Z" },
{ url = "https://files.pythonhosted.org/packages/ad/66/c19feabb28485b6e0bbaaafa90837a1ef5d302e90f2178bd33f17a49879b/cffi-2.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3311ed60d36f83378794e1009ac6258bafbf81f7888b4caa7b35a521e3f95813", size = 218716, upload-time = "2026-08-03T21:19:32.896Z" },
+ { url = "https://files.pythonhosted.org/packages/a7/92/500760486c8baab49a7a8a58ba7fc3355ec3974b454b8a09e528efde9e1d/cffi-2.1.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6e192623c49c94421616a5778fba35cf0d5a8d000650c1967ef4448ee5cdd990", size = 205569, upload-time = "2026-08-03T21:19:34.142Z" },
+ { url = "https://files.pythonhosted.org/packages/a5/a7/a67c733254d6e7373f7822f8082d8d6beade791e0cf12a7611f376fa61c7/cffi-2.1.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a6e721d4b0e45d5b65e87534470e67b18dcd092c83f68fba09f152b9cbc061af", size = 204907, upload-time = "2026-08-03T21:19:35.174Z" },
{ url = "https://files.pythonhosted.org/packages/f7/a4/4399daaf8f7dfee9d7c3327fdb0426ee041cc63edc358b93911ceb2bfc7a/cffi-2.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:34e261f78cb6ceaaa36f42f2613f4380d94d9c759a9c73c769ee6e0247364632", size = 217807, upload-time = "2026-08-03T21:19:36.286Z" },
{ url = "https://files.pythonhosted.org/packages/28/f7/dabe6da2466ecbd82dc62e7342dc6b1065dad990c06f00f0ede9ebf2a0ed/cffi-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7225e4514edb64eb6740324353e0da0711954fd8d7da4576755b1c6e09b697cd", size = 221252, upload-time = "2026-08-03T21:19:37.416Z" },
{ url = "https://files.pythonhosted.org/packages/ce/87/616202d8e51342c07d2534c510111c4cc37201775ce8f60802c9335d1edd/cffi-2.1.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df913725b79db7bcf03448f36b7bf8815363417d5b58deecf9305e3e30f0f21a", size = 214214, upload-time = "2026-08-03T21:19:38.507Z" },
{ url = "https://files.pythonhosted.org/packages/b4/c6/ab025d75d2c26c19b087c0124e75ee31cb65032f4fe345d356d8c507ab97/cffi-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f5cfbc5fe74540d335175b656c725d74d90e3730c626d92575eea35029d9afaa", size = 219408, upload-time = "2026-08-03T21:19:39.809Z" },
{ url = "https://files.pythonhosted.org/packages/56/e6/8941622732edec876dd17d0453dce07317ae96db34f2ec1436c9d3785986/cffi-2.1.1-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:811bd1e21d32de12efca32393a0ab3f5133b54fce9bd44b8bd77ab07da14bf6a", size = 214799, upload-time = "2026-08-03T21:19:47.218Z" },
{ url = "https://files.pythonhosted.org/packages/44/de/f98430906df1545ffde0d543dd124a7a439bc2cd32b36b9c53f805df7333/cffi-2.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:68e62fe11f30d5ca8289242866f0a5291402d8529ca2178ab8afc5c9694ae890", size = 222389, upload-time = "2026-08-03T21:19:48.331Z" },
+ { url = "https://files.pythonhosted.org/packages/6a/5b/717f1526b9957b34456313c31645c5b82b8fb5c3fe9e4752999be7128bfc/cffi-2.1.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:4a7c934f7360e8cd64fe9efadcbd10c7c6364f531e432b9a4bf5ccbc9e0e8b50", size = 210249, upload-time = "2026-08-03T21:19:49.543Z" },
+ { url = "https://files.pythonhosted.org/packages/64/b3/f8aa4f3e34986c7e4ec45072d1b1b9dd295b6b18007b45518d79726dd725/cffi-2.1.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:3143d81e29e1e20a9ce10901ec369012947876596f75a222235965f2b7ae832e", size = 208775, upload-time = "2026-08-03T21:19:50.918Z" },
{ url = "https://files.pythonhosted.org/packages/b1/db/dceb9dd5b231e1da801793f8acc9f3c52a7e1afe40bb1aae37e02b0faad5/cffi-2.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf", size = 221822, upload-time = "2026-08-03T21:19:52.054Z" },
{ url = "https://files.pythonhosted.org/packages/a0/d2/6cd24ae3be000a634109c247d1475d62e5616d0dc78c82770942ec384248/cffi-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:208f941bb9d18e768138677f0a6d2ce01f590df56043dda1df1535ac57c88517", size = 225232, upload-time = "2026-08-03T21:19:53.109Z" },
{ url = "https://files.pythonhosted.org/packages/cb/52/3fa190537004dd7f0ab860a6dc7c0175b8667f68d1e618a46f5498d30250/cffi-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:210019b6c7cf07f081b4c54635c8cf744377001350e29cc0f81c4377b4797735", size = 223597, upload-time = "2026-08-03T21:19:54.515Z" },
@@ -167,26 +171,36 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/76/af/2aeb4dbb5fc41a04161ae9ff1518de7cec08e164f44a8ce6a4cf7fd2cd1d/cffi-2.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:31348097ff5bbe827ccc41795d4dd099d9f0625e7def00ee653c137a490c2a6c", size = 196908, upload-time = "2026-08-03T21:20:00.746Z" },
{ url = "https://files.pythonhosted.org/packages/43/1f/1c3d90d91811c8f86ced9ed637956c54bfe5b79ca98fe976d7f8c8979f6b/cffi-2.1.1-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6a8dddef476fab96d066d578fc88526767b836ab5ab21754e1d5bf3879c31c7c", size = 214722, upload-time = "2026-08-03T21:20:04.377Z" },
{ url = "https://files.pythonhosted.org/packages/37/6f/3b5ce4c3b2192d250f04908f2bfd91ef34552ec8f7716a5d4abdb8d67bb2/cffi-2.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f16c709686a78c727bbbf059f92b0bf41c6fc60deec706d2dc19f529175a6125", size = 222369, upload-time = "2026-08-03T21:20:05.544Z" },
+ { url = "https://files.pythonhosted.org/packages/02/10/4b3c75dde3d9663c9e02ba05c2668b954f671d4bbe346413ca8c696b295a/cffi-2.1.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:fcd22650c908d7b7da162bbfaab594a1227a15d1643a98c68b122ac642fa2264", size = 210175, upload-time = "2026-08-03T21:20:06.75Z" },
+ { url = "https://files.pythonhosted.org/packages/df/62/14f74b9543e605d17701dc797b815958b8bb70b7624ce1b832ddad48ed6c/cffi-2.1.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:aa9511c62d14da7aacc9b4bf51f3f697a621e83b2d6919008243c3aad168eea3", size = 208670, upload-time = "2026-08-03T21:20:08.04Z" },
{ url = "https://files.pythonhosted.org/packages/95/95/86342356ff5953b3fb06f7ef7c5bee212d45e770abc7218d451b9148313c/cffi-2.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a931079504ecc49efed7744c476a5c343a92fabf66dec2db95edb1b2fdc770e2", size = 221824, upload-time = "2026-08-03T21:20:09.274Z" },
{ url = "https://files.pythonhosted.org/packages/eb/ff/7b3429ff53aafe931ed8a5fc69f481bbef7ba6de87ddcbb63d08f483f613/cffi-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a2d7755bef5a12ed488f4ef1f1b69ee9191d7396083b755a5d2295f6edb4768b", size = 225148, upload-time = "2026-08-03T21:20:10.7Z" },
{ url = "https://files.pythonhosted.org/packages/34/34/a95870b9221e09cf4f2ce3178b1a210abdfe63a1bd357da940418d7b8d15/cffi-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e0bcb7e0f677f543555d2adff3bf19c05f66cdb4796e5ff602442ab2fe3c4ef7", size = 223564, upload-time = "2026-08-03T21:20:12.165Z" },
{ url = "https://files.pythonhosted.org/packages/d3/7b/d6bbf82b8b96e7391438898c42f5bd96dd02030fd5b64937d248220003e2/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7dbb61fe3a7699468030f71bbe5f8a0e326a151daa91beb11a6fc1f980c55e1c", size = 194064, upload-time = "2026-08-03T21:20:17.148Z" },
{ url = "https://files.pythonhosted.org/packages/94/e6/bcc91b283be94735e268487a054004f0aa19947b6348fa367db53230abc8/cffi-2.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:f24fb43132a4c6b4cb4eb029492919b2db645be6808d738f244fd146c03c32cb", size = 196720, upload-time = "2026-08-03T21:20:18.268Z" },
{ url = "https://files.pythonhosted.org/packages/67/b8/b42132ca113dc567d37684437b46ca1dafc885902b02a110a02d5b511857/cffi-2.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58acb8ab8e295e6c5ea12f888cbb13cf21511ef2a3303a23f4325c29d17fe5c1", size = 222328, upload-time = "2026-08-03T21:20:22.118Z" },
+ { url = "https://files.pythonhosted.org/packages/80/10/c5c0cbf0a657aecf59ef511409734230bf556f05a0d6c9eed7aa5c0a0166/cffi-2.1.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:456a61fa52d579ebf9df2e9552ead5129855dbaff6c1e5a9b1bc408809bdc062", size = 209985, upload-time = "2026-08-03T21:20:23.401Z" },
+ { url = "https://files.pythonhosted.org/packages/d5/6c/bfa0b87b03b9238148beca990292843c9396ba069b54496596594173de7b/cffi-2.1.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a4f00aa42f75d6e4595e8866e748cc1705adc0cddfeb2ca86d0d03993d63ba03", size = 208530, upload-time = "2026-08-03T21:20:24.628Z" },
{ url = "https://files.pythonhosted.org/packages/e9/02/4e7d553a7ac4b4238b38b3c1b80d486e9d4436f8d2acbf87a0997fe3f402/cffi-2.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b0431303acaea1089ad4b3e9ce4e6518193def1118d4073ca848635ee4ea2e96", size = 221525, upload-time = "2026-08-03T21:20:25.758Z" },
{ url = "https://files.pythonhosted.org/packages/82/1d/a4aaf9babd75acb4d5f223bff71533bee748dd770a382619a798960ee9ba/cffi-2.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:64faea20f4e2613363a1a9b9c7dd73058f3ecd00133a511e72ad7c511658f527", size = 225053, upload-time = "2026-08-03T21:20:26.985Z" },
{ url = "https://files.pythonhosted.org/packages/81/10/5dc0e7bdd18e22107054288283380fc97a06ae3f1656a106908d666a3c88/cffi-2.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5c58fe613dc5e5336357eff555824a314d8e43282600435c8d1cb6a7a2fedd13", size = 223213, upload-time = "2026-08-03T21:20:28.277Z" },
{ url = "https://files.pythonhosted.org/packages/2a/9c/92934c3bea9f785b23eba304538c0b4d37a2a96d2431eb3a1bc87a11aa19/cffi-2.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49cbc70e6542d4ccccb936558d1064a8012541e78f821f955cff24e357776c94", size = 223864, upload-time = "2026-08-03T21:20:32.571Z" },
+ { url = "https://files.pythonhosted.org/packages/4d/45/ba4c93527bc38616a8bd36488acb69a2212d60486794f0c1f318949bbb76/cffi-2.1.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e2d65b31f36619cda3999b78b2aa9632e76b78448e7a56fc4240824200e7c4fc", size = 211538, upload-time = "2026-08-03T21:20:33.808Z" },
+ { url = "https://files.pythonhosted.org/packages/80/e9/b6ef565e452acb932fb0cb5443f44a78efbd1233e566f02b5a83855e9115/cffi-2.1.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:28907ab9bfb6aa13184cfc17c6b8e1023c5ab6fd7076d8c20a35e59fe04f8f29", size = 210688, upload-time = "2026-08-03T21:20:34.974Z" },
{ url = "https://files.pythonhosted.org/packages/9a/95/eff5f0cee78d2eabc7eebffec40d3fc1876b5f3c95582e018bb4b99601f2/cffi-2.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51b31d1c98274844cfd7838ce00bfc27c7423a4dc00fc0772fc3331c2cc90676", size = 223803, upload-time = "2026-08-03T21:20:36.564Z" },
{ url = "https://files.pythonhosted.org/packages/fa/01/579d39fb8bef00a335a23d83757b44feb24cd6345a2c451b64cb67b9c362/cffi-2.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e7cecbaadb83884793e05828cee59b210b24583b9c7425d0ba6a754fe22eb4e", size = 226763, upload-time = "2026-08-03T21:20:37.816Z" },
{ url = "https://files.pythonhosted.org/packages/8d/b0/0b44f47c60b01b57b6e2bbd92343f13a85a1d93bc46ccf6e47e244acd99c/cffi-2.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:25792eac27877609e7bb06d42ff88278a6624fff2ba9bbb523c09616b117e80f", size = 225688, upload-time = "2026-08-03T21:20:38.959Z" },
{ url = "https://files.pythonhosted.org/packages/c1/8f/9ebe220eab48a093d1a5a5e339ab0dc7316eef3bb04d63c42f0251b61f50/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:dddad92b554513a31f272570678ba307fb9f618f05e3d4a5eacafff9eae03e1d", size = 194043, upload-time = "2026-08-03T21:20:48.179Z" },
{ url = "https://files.pythonhosted.org/packages/ff/69/844bad3ece306c4782c2ecb93597035b6690d48704b803914c199da1e8b3/cffi-2.1.1-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:da0e573f9f97159390c89d9f1a9e41908b66d408cc5b58d08cf3847d844c531b", size = 196737, upload-time = "2026-08-03T21:20:49.457Z" },
{ url = "https://files.pythonhosted.org/packages/e2/31/9e1313b0a6e30e91b3b3d3fff51ae99c857c07738e3afcce1f7334e1b7ab/cffi-2.1.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:507a24c282e0f42f8ed737cf048572cbf580468da5555764a8331735e9c736b6", size = 222271, upload-time = "2026-08-03T21:20:53.462Z" },
+ { url = "https://files.pythonhosted.org/packages/50/e3/f6234a833e6e08c7007003074723c406559eecf9b48dfc97471e5a8eb7a0/cffi-2.1.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:246fa40ce8645a614ff682e0b70f37134e460eaf93a775e0cbe3cca585a67a80", size = 209919, upload-time = "2026-08-03T21:20:54.783Z" },
+ { url = "https://files.pythonhosted.org/packages/0d/fc/5f74e293fced6edb51af3a46c4ccf6c23c9943774ecb375ddbd522c76add/cffi-2.1.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:471cee653ae88de62096552e6d24ccb4a5adb8c8c9f10b5054d0122c15bf2779", size = 208529, upload-time = "2026-08-03T21:20:56.066Z" },
{ url = "https://files.pythonhosted.org/packages/44/16/29e6d01b388bef055ecd6ca8244b3f4d336bd09e92d5d892187b9601084e/cffi-2.1.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeae0e330c9f6acd681f647d46cefd30c29f93e3392882e792e82080c9691399", size = 221630, upload-time = "2026-08-03T21:20:57.336Z" },
{ url = "https://files.pythonhosted.org/packages/a4/18/fa7f1f6857d5eb88a4ca99ffcbfb7c387a287ccc154c64a73e86314745d7/cffi-2.1.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:42a494cee34437f05546455144f2b5d9ac09b1face62bcfce597d2e521066688", size = 225134, upload-time = "2026-08-03T21:20:58.675Z" },
{ url = "https://files.pythonhosted.org/packages/e0/9f/e8e3dfa04a1b4c241f8c91faacad872b4d4efd051d49764ad4e2fd4b9fea/cffi-2.1.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:cc572dace3f60ef98d7b12ff411d20f5362feb31a0439eab0085bbfd349982d7", size = 223197, upload-time = "2026-08-03T21:20:59.968Z" },
{ url = "https://files.pythonhosted.org/packages/53/b2/6187f46f2912276a3ae284076109cc5c8680482f11f766ccf26db4a86427/cffi-2.1.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:937c0052c05a31ca1daf18de3158eed4dbfcb9cc107adbea227728d647be701e", size = 223779, upload-time = "2026-08-03T21:21:03.553Z" },
+ { url = "https://files.pythonhosted.org/packages/8a/f6/c3ad28bd19f77047a03084424fbd4cbe997303267c14423737324be0385d/cffi-2.1.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:df423d40ee8654634421812bc3b196da3f9bd7d32929da813f8394c4348a5358", size = 211520, upload-time = "2026-08-03T21:21:04.863Z" },
+ { url = "https://files.pythonhosted.org/packages/a0/cd/ccac9013a5bd9fd764de118674ab9c805b5ca10c19270d90ee273f8b2240/cffi-2.1.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:a730a083190634c65cca36ba5f489531576ebd79bcd5c8e172130f6453127231", size = 210673, upload-time = "2026-08-03T21:21:06.223Z" },
{ url = "https://files.pythonhosted.org/packages/52/86/2976131c639aead931c5bee5aba67e4b09fbeb8018b6f282f70803f923a7/cffi-2.1.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:363e05fa78e15116c3c32c210ee36884fd6b9afa6d440e47112c3bd511d64cb6", size = 223835, upload-time = "2026-08-03T21:21:07.539Z" },
{ url = "https://files.pythonhosted.org/packages/ac/0c/33a7aeab2f9c76918c52e084beb39c570db3588133412929e8ec06fab90b/cffi-2.1.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:770de9db11e84213beec501cfcaa013b019820ca881e03344dea5844f7876d94", size = 226705, upload-time = "2026-08-03T21:21:08.774Z" },
{ url = "https://files.pythonhosted.org/packages/e3/26/2cde30fdde421130bfc18f70395731a6e6b2053c6a1978a5258ff04e72fa/cffi-2.1.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:7da0c5eff80f0197f3b3d1232ec5a682a9325f4ae9016a78f5f5ca35f9ced1f5", size = 225539, upload-time = "2026-08-03T21:21:09.911Z" },
@@ -360,27 +374,33 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/90/34/9ce9a62ed9dc82ca9fd6a34445b6904af56e5f38b3eae2ed32e49c36053d/cryptography-50.0.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:53e279950892dc102c6b4e52af03ae5ea92fac572a1ddab78ca73a997f62b69f", size = 4723133, upload-time = "2026-08-25T19:44:05.461Z" },
{ url = "https://files.pythonhosted.org/packages/57/26/e6d4fc8512a51a5f9ee7bfdbfb853bce1197087df40c9ad993ad370b846f/cryptography-50.0.1-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ff838d62ec1bfce4f9ba7fa16f4a7b554cd8d0c299e6be37502161a660c84eef", size = 4712478, upload-time = "2026-08-25T19:44:07.375Z" },
{ url = "https://files.pythonhosted.org/packages/e6/de/d3cdc2815697aae84126cbd6a030ca7b6b452e28a88b501b836bd3aa7a86/cryptography-50.0.1-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e74591e283fe6eb956416c929eb58262a719fe0311fd9054c62c3350ed8760d8", size = 4730726, upload-time = "2026-08-25T19:44:09.294Z" },
+ { url = "https://files.pythonhosted.org/packages/55/32/38c0d344b98c06d34b5df8946565a9c0d6dbf32c8e0730a7f05f0a3c6cab/cryptography-50.0.1-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5fe002589592ed749ce77fe0695fcbd3500dd61d7d6db5858a7544c612fa8e45", size = 5353524, upload-time = "2026-08-25T19:44:11.96Z" },
{ url = "https://files.pythonhosted.org/packages/e1/1b/82f0f0d8858d4432be1af790477edf62aef90324041aa07c57e57bef1af7/cryptography-50.0.1-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:51593d180cf6d179bde5c5d065bed81386b1f381656ae7d042b7ffc87a9895ad", size = 4746720, upload-time = "2026-08-25T19:44:14.051Z" },
{ url = "https://files.pythonhosted.org/packages/29/ba/042ca458b8c64348c768284b5d23e69b92ed53d057ab779fee628564676d/cryptography-50.0.1-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:359e62deae718bce96170e223fdcb6357e4fbd3bb7a3a75f4430763532560e49", size = 4361866, upload-time = "2026-08-25T19:44:16.167Z" },
{ url = "https://files.pythonhosted.org/packages/39/3b/e96c1ef71edef71057c7e3c3d982ce8fda554e0c52d0cc19c18845cde3eb/cryptography-50.0.1-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e2ca8fd1b6b4b82a1c4cb02841d0837e3c12336c2e24b520ab8ab3b969733d8f", size = 4730028, upload-time = "2026-08-25T19:44:18.085Z" },
+ { url = "https://files.pythonhosted.org/packages/e3/38/45abd72ef63f2e7d0754a6cacf97bd8b69512ace7f6130d24c39ece65da2/cryptography-50.0.1-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:76de83fbd91ac49c0feaaa983d0748fd7a53176afac5fb3bf7478d244f0eb527", size = 5308405, upload-time = "2026-08-25T19:44:20.197Z" },
{ url = "https://files.pythonhosted.org/packages/85/66/6ccca4722987ddedaa7fc9c3f4708af7431f5535666c174350830888c6b7/cryptography-50.0.1-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a", size = 4746230, upload-time = "2026-08-25T19:44:22.376Z" },
{ url = "https://files.pythonhosted.org/packages/13/0e/b1f92e013228111413f2e6743948b80bc24dfd3c1b87ba98ceea16f5df89/cryptography-50.0.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:be224a65493ec5b74a158ff22a5522ce4a5ca1e543c647a3a4730d4a09e5f959", size = 4862596, upload-time = "2026-08-25T19:44:24.472Z" },
{ url = "https://files.pythonhosted.org/packages/7e/22/c3654cccc856e9d682817b04ac3ee79731cb09ca6f95996a95c904de2883/cryptography-50.0.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9ebcdd5519be9b652a46f507817a74591774fc3d6923ac364e4dfa64e36b291b", size = 5014082, upload-time = "2026-08-25T19:44:26.709Z" },
{ url = "https://files.pythonhosted.org/packages/4d/72/3a2711d967977ab5fc80b782837c7e8d1ac7445e764c20c381a265c57ef3/cryptography-50.0.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a0b1a59e3a089064a0ec309e9428c8e3ae4e161419d20ac33600767e83fc658a", size = 4708817, upload-time = "2026-08-25T19:44:32.773Z" },
{ url = "https://files.pythonhosted.org/packages/b4/f2/bb1f56e10815b789df0b409a69fa4992ff3d3fef9c72747f4a6b26fed38e/cryptography-50.0.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8921d58f426793c5f1b47f0b59575780de9a095214958d0eb37d909593db8367", size = 4697300, upload-time = "2026-08-25T19:44:35.144Z" },
{ url = "https://files.pythonhosted.org/packages/08/bd/ed5396be499ffcf8807a585bfe38b71a1fbdd1c342b4f9b6d0ef5162a946/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:a8f40ea47330e71b594a7e246898f93177c259490c63183dbaf9e571d71ed9a5", size = 4716039, upload-time = "2026-08-25T19:44:37.192Z" },
+ { url = "https://files.pythonhosted.org/packages/f6/6e/1cf405c5c8e8df7545378048e954792f00b7f2367af8863ce8b8f3e10607/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:a255449073358275b64b67d3f595f268bbef70e72b6edb65e0c70c735bf739c9", size = 5332388, upload-time = "2026-08-25T19:44:39.16Z" },
{ url = "https://files.pythonhosted.org/packages/47/92/b4317e8c32c4f47b062f5398bd79106b220a124546f42be83bf32b761e2a/cryptography-50.0.1-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:8df2de9102026855887e4587084f6eabd80ed0f345b8ad8a7ac27ab9bf4723e0", size = 4730293, upload-time = "2026-08-25T19:44:41.298Z" },
{ url = "https://files.pythonhosted.org/packages/39/0d/a1e7633e2c744d0f2983320a27e924ef2264c79c56e1a58d5fb0a1cfd413/cryptography-50.0.1-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:ac02b07824d4d1001bd4367599f839c19cb171924c796e52c23508ac14c2c0cc", size = 4346031, upload-time = "2026-08-25T19:44:43.245Z" },
{ url = "https://files.pythonhosted.org/packages/88/dd/b215616f9bab3fc18510c78a4e5c9f362d77838503c363dc747c7d4f5c6f/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:cbf74a81765ee67413503ca6e26dcc4f6f5a519822436cc0a1b97aab6c1b8a17", size = 4715344, upload-time = "2026-08-25T19:44:45.291Z" },
+ { url = "https://files.pythonhosted.org/packages/b1/1b/ec3ebd31741d0e963612c4fe43caa39341b9b1e031e469820e42e4c83918/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:16c5ecd954b3330ebfb6605eca4fd952da8bef376551d5cc264534e3770a9ee6", size = 5287201, upload-time = "2026-08-25T19:44:47.297Z" },
{ url = "https://files.pythonhosted.org/packages/1a/01/0127d11a762b31a9ee0221894f540318761783f3fdc4bc5d057698caebd5/cryptography-50.0.1-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:79bf008d1f9af6071c797ad133e39915dfee7614f18f18f4db9072eb715064a3", size = 4730023, upload-time = "2026-08-25T19:44:49.435Z" },
{ url = "https://files.pythonhosted.org/packages/9e/b9/e7425ebfb599241a0c1d7000f1b466c3062da66c19d9525031315dff7213/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:330fbb252391c596f1ae42c5754449dc924e6ad012dca8efe0d703f9f2d12ec6", size = 4847362, upload-time = "2026-08-25T19:44:51.94Z" },
{ url = "https://files.pythonhosted.org/packages/2d/fd/60d0ddf4defa12e482c9d5e0f554384d6e8ab25341fd15f060028fd92e6a/cryptography-50.0.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:42be3bb70596b3abe4ac097b75be223e8b3ab614a0e5de068e3dcc54d71d6149", size = 4999247, upload-time = "2026-08-25T19:44:53.876Z" },
{ url = "https://files.pythonhosted.org/packages/5e/a5/9ec7e81e8526c0d7a387d73386b2daed3f39e10d81a85930bd1b6bfba65c/cryptography-50.0.1-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:05ba322c4da95b262a212c345af888ef2c37c88c0509756ea00a0e6d68850f23", size = 4751900, upload-time = "2026-08-25T19:45:00.401Z" },
{ url = "https://files.pythonhosted.org/packages/7e/3c/0e77bd5ffcf078e9dd27d3074aad6c030d9b10d0bf69329d573c927a188c/cryptography-50.0.1-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e22dfed744bd4002e909464cb23d2f0b05c6f3113a79ef2e9864a53db737c733", size = 4738357, upload-time = "2026-08-25T19:45:02.786Z" },
{ url = "https://files.pythonhosted.org/packages/27/3a/3c5f80daa4dcd47323c7af8a2fcb90de27a33564d4fcac69846c0972691a/cryptography-50.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:4c4188f7c0cf655be5c06342b817ed0f9595b69ffa2b12026e5353eed29dea88", size = 4758474, upload-time = "2026-08-25T19:45:04.889Z" },
+ { url = "https://files.pythonhosted.org/packages/6e/2b/214cf0cf93db9628c3c20c896b229f327f6fb1b20e4b3743d8ad3f00af8b/cryptography-50.0.1-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2ebbfb0f1fed745e91796e3e1080a1440423fdae8ece1b995a1d80883a409054", size = 5375862, upload-time = "2026-08-25T19:45:07.163Z" },
{ url = "https://files.pythonhosted.org/packages/d6/51/3f9701867a46b6c1740c9b52fc4d3bed6cbdcfedcc9b6e64305c07f39cff/cryptography-50.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:407fe2b6db00939c05c0e945e9914238f2f0a430974839429dafc82b1ee6bee5", size = 4772942, upload-time = "2026-08-25T19:45:09.396Z" },
{ url = "https://files.pythonhosted.org/packages/0d/5c/13ea642e08e2544d0f5396122055f4820cfacb3203562197b5967125ea97/cryptography-50.0.1-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:2b34d76a652ea2b6faf777c35df230c5637842cd904e04f16230c3f9f03e4361", size = 4383347, upload-time = "2026-08-25T19:45:11.659Z" },
{ url = "https://files.pythonhosted.org/packages/84/d5/7d1fe1cb93f91c428093ff234e128c89ba8ea61a6f26aab406081f9b996e/cryptography-50.0.1-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:01f41478cf33fc605a6a089cd56d28b45c6c0b45a1928b61797f2621a04bac71", size = 4758050, upload-time = "2026-08-25T19:45:13.745Z" },
+ { url = "https://files.pythonhosted.org/packages/dd/04/557fc5ead96a829e0bc812a3b9dc4a52a2f27e4f7f5950da7ff27653a805/cryptography-50.0.1-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:fc3ed7ebd2a8c96f5b166de0ab9b624996bef3b07bbeb19364dfb78222c22c80", size = 5332955, upload-time = "2026-08-25T19:45:16.193Z" },
{ url = "https://files.pythonhosted.org/packages/8c/eb/5d7124083e8d8cda8f5b348f544b71ad6f707ad63193758ef4d8e569da02/cryptography-50.0.1-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:9dde0a357190eb3b1da1bb9ab750e9c85cba82ca5977aa0836cbb94e92611239", size = 4772694, upload-time = "2026-08-25T19:45:18.315Z" },
{ url = "https://files.pythonhosted.org/packages/63/8e/f1f955e0921dd2b6d22eae7e8d24a4c4b638d10735ffbf6a71f99eb0fcb8/cryptography-50.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd3718b960d0b5dd213cdf03f3bcb7000e69dda0de8b956061947ff6bcff5558", size = 4888413, upload-time = "2026-08-25T19:45:20.4Z" },
{ url = "https://files.pythonhosted.org/packages/1f/ab/89e2b798d2c3925f82e2bb72d5979f3d2f6da2dd22ef4a8cd8b70d920039/cryptography-50.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2a93d05e34d5f67fba6f891fe85d929999baa7195e853923ea6d7576c9e68c5e", size = 5044355, upload-time = "2026-08-25T19:45:22.353Z" },
@@ -478,7 +498,7 @@ name = "importlib-metadata"
version = "9.0.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
- { name = "zipp" },
+ { name = "zipp", marker = "python_full_version < '3.15'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6f/7e/1e7e8dc30634b93ebb3d58a3dea569ad146e656218d3960ab04f62047b29/importlib_metadata-9.0.1.tar.gz", hash = "sha256:ab830580bc0ef3db61ce8fae716389e5462b67e033018bab6d8f80ef17172f99", size = 59124, upload-time = "2026-08-28T15:30:34.646Z" }
wheels = [