Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion content/guides/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Use an explicit base URL in each environment so staging jobs do not accidentally
export CALLE_BASE_URL="https://api.heycall-e.com"
```

The [complete Python call example](https://github.com/CALLE-AI/calle-docs/blob/main/examples/calls.py)
The [complete Python call example](https://github.com/CALLE-AI/calle-docs/blob/main/examples/v2/calls.py)
reads `CALLE_BASE_URL` for both `start` and `resume`. If it is unset or empty,
the example keeps the SDK's default URL. Use the same API key and environment
when resuming a saved call. Keep test and production API keys separate, and do
Expand Down
5 changes: 5 additions & 0 deletions content/guides/calls.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ Calls are available on the production API and in SDK 1.0. See the
Calls and Goal Runs include `call_outcome`, `result_status` and `transcript`.
Wait while `result_status` is `pending`, not until result or error becomes non-null.

Use the [complete Python and Ruby examples](/quickstart#run-a-complete-example)
to persist a request and resume it. The
[V2 result reader](https://github.com/CALLE-AI/calle-docs/blob/main/examples/v2/read_call_result.py)
reads their saved response, including unavailable results and the top-level transcript.

## Call ID and Billing

The response adds a fixed `call_id` field for matching a telephone call to the
Expand Down
4 changes: 2 additions & 2 deletions content/guides/goal-runs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Start outbound calls from a published Goal when your application repeats the sam
**Server-side** · **Asynchronous**

Examples target the [current Developer API contract](/openapi/calle.openapi.yaml)
and server SDKs `@call-e/calle@1.0.0` and `calle-ai==1.0.0`.
and server SDKs `@call-e/calle@1.0.1` and `calle-ai==1.0.1`.
See [API versions and migration](/changelog#api-versions-and-migration) for
request and response changes.

Expand Down Expand Up @@ -356,7 +356,7 @@ See [Errors](/errors) for the HTTP error catalog. HTTP errors mean the API reque

## SDK examples

Use TypeScript and Python SDK `1.0.0` for these examples. SDK 0.7.x wait helpers
Use TypeScript and Python SDK `1.0.1` for these examples. SDK 0.7.x wait helpers
wait for a non-null result or error and can time out on a final unavailable result.
Upgrade or poll the REST response using `result_status`; do not redial on timeout.

Expand Down
23 changes: 23 additions & 0 deletions content/guides/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,29 @@ The [SDK guide](/sdks) includes installation and complete TypeScript and Python
For tests, explicitly set `CALLE_BASE_URL=https://test-api.heycall-e.com` and use
a test-compatible key.

## Run a complete example

The runnable [Python SDK example](https://github.com/CALLE-AI/calle-docs/blob/main/examples/v2/calls.py)
and [Ruby HTTP example](https://github.com/CALLE-AI/calle-docs/blob/main/examples/v2/calls.rb)
save the original request, idempotency key and Call ID so the same operation
can resume after a timeout or lost response. Choose one runtime and an authorized
destination; each `start` creates a real call.

```bash
export CALLE_TEST_PHONE="<AUTHORIZED_E164_PHONE>"
python -m pip install calle-ai==1.0.1
python examples/v2/calls.py start ../calle-v2-run --phone "$CALLE_TEST_PHONE"
python examples/v2/calls.py resume ../calle-v2-run
```

For Ruby preview/start/resume commands, see the
[complete example instructions](https://github.com/CALLE-AI/calle-docs/tree/main/examples/v2).
Read either runtime's saved result without sending another API request:

```bash
python examples/v2/read_call_result.py ../calle-v2-run/result.json
```

## Retry without calling twice

If the create response is lost, retry the **same request with the same key**.
Expand Down
9 changes: 6 additions & 3 deletions content/guides/sdks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ description: Install SDK 1.0 for single-target Calls and published Goal Runs.
---

The official server SDKs support single-target Calls and published Goal Runs.
Use SDK **1.0.0** with the production API. Legacy call-task integrations must
Use SDK **1.0.1** with the production API. Legacy call-task integrations must
[migrate their inputs and result handling](/migration) before upgrading.

## Install

```bash
pnpm add @call-e/calle@1.0.0
pip install calle-ai==1.0.0
pnpm add @call-e/calle@1.0.1
pip install calle-ai==1.0.1
```

Python requires 3.11 or later. Keep API keys in trusted server environments.
Expand Down Expand Up @@ -100,6 +100,9 @@ both result and error null. A timeout does not cancel or replace the Call.
Python exposes the Billing identifier as `call_id`; TypeScript uses `callId`.
Use the separate resource `id` for API requests.

For a runnable Python or Ruby example that saves the request and resumes the
same Call, see [Run a complete example](/quickstart#run-a-complete-example).

## Retry safely

Persist one key and the original request before calling `create`. Retrying the
Expand Down
9 changes: 9 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# CALL-E documentation examples

Use [`v2/`](v2/) for new integrations with `/v2/calls` and SDK 1.0.1.
The files directly in this directory use the legacy `/v1/calls` contract and
SDK 0.7.0, including `recover_create.py` and the result/transcript readers.
Keep their request shapes, IDs and recovery rules separate from V2.

See the [migration guide](https://docs.heycall-e.com/migration) before upgrading
an existing workflow.
64 changes: 64 additions & 0 deletions examples/v2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Calls V2 examples

These examples create one authorized English greeting call using `/v2/calls`.
Python requires 3.11+ and `calle-ai==1.0.1`; Ruby uses its standard library.
Check [supported regions and languages](https://docs.heycall-e.com/regions)
before choosing a number you own or are authorized to call.

From the repository root, configure the same key and environment for start
and every resume. Use a new private directory for each intentionally new call.

```bash
export CALLE_BASE_URL="https://api.heycall-e.com"
export CALLE_API_KEY="<YOUR_CALLE_API_KEY>"
export CALLE_TEST_PHONE="<AUTHORIZED_E164_PHONE>"
```

Choose one runtime. Running both start commands places two calls.

```bash
python -m pip install calle-ai==1.0.1
python examples/v2/calls.py start ../calle-v2-run --phone "$CALLE_TEST_PHONE"
python examples/v2/calls.py resume ../calle-v2-run
```

```bash
# Preview without sending a request
ruby examples/v2/calls.rb start ../calle-v2-ruby-run
ruby examples/v2/calls.rb start ../calle-v2-ruby-run --execute --confirm-authorized-recipient
ruby examples/v2/calls.rb resume ../calle-v2-ruby-run
```

Both scripts save `request.json` with the original input and idempotency key
before submission, then `call-id.json` before waiting. `resume` reads a known
Call ID; when a create response was lost before saving the ID, it resubmits
the saved input and key unchanged. It never allocates a replacement key.
If a request is rejected, inspect the private error file and follow the
[V2 retry decisions](https://docs.heycall-e.com/calls#idempotency) before
correcting input. Keep any omitted region or locale omitted during recovery.

Waiting stops when `result_status` is `available`, `unavailable` or
`not_applicable`. Exit 0 means retrieval finished, including a final null
result. It does not establish business success.

Read either runtime's saved response without making another request:

```bash
python examples/v2/read_call_result.py ../calle-v2-run/result.json
```

For Ruby, use `../calle-v2-ruby-run/result.json` instead.

The reader preserves `result`, `error`, `call_outcome`, `result_status`,
the Billing `call_id` and the top-level transcript. Use the resource `id`
for API reads; a Billing ID can remain null.

Supplemental checks make no real requests:

```bash
python -m unittest discover -s examples/v2 -p test_examples.py
ruby examples/v2/test_calls.rb
```

Files in the parent `examples/` directory are legacy examples for SDK 0.7.0.
Use their original interface for historical call-task IDs.
104 changes: 104 additions & 0 deletions examples/v2/calls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"""Create or resume one Calls V2 operation. Python 3.11+, calle-ai==1.0.1."""

import argparse
import json
import os
from pathlib import Path
import re
import sys
import uuid

from calle import CalleClient
from calle.errors import CalleAPIError, CalleConnectionError, CalleTimeoutError


def save(path, value):
with path.open("w", encoding="utf-8") as stream:
path.chmod(0o600)
json.dump(value, stream, indent=2)
stream.write("\n")
stream.flush()
os.fsync(stream.fileno())


def run(client, directory, phone=None):
if phone is not None:
if not re.fullmatch(r"\+[1-9][0-9]{7,14}", phone):
raise ValueError("Use an authorized E.164 phone number.")
directory.mkdir(mode=0o700) # Refuse to overwrite an existing operation.
save(directory / "request.json", {
"idempotency_key": str(uuid.uuid4()),
"phone": phone,
"task": (
"Make one authorized developer integration test call in English. "
"Listen to the first complete greeting, then thank the other side "
"and end the call. Do not press menu keys or wait on hold. "
"Use unknown for heard_greeting and greeting_summary if no greeting is heard."
),
"result_schema": {
"type": "object",
"properties": {
"heard_greeting": {"type": "string", "enum": ["yes", "no", "unknown"]},
"greeting_summary": {
"type": "string",
"description": "Summarize only the greeting actually heard, or use unknown.",
},
},
"required": ["heard_greeting", "greeting_summary"],
"additionalProperties": False,
},
})
id_path = directory / "call-id.json"
if not id_path.is_file():
# Lost create responses replay the exact saved request and key.
operation = json.loads((directory / "request.json").read_text(encoding="utf-8"))
created = client.calls.create(**operation)
save(id_path, created["id"])
call_id = json.loads(id_path.read_text(encoding="utf-8"))
if not isinstance(call_id, str) or not re.fullmatch(r"[A-Za-z0-9_-]+", call_id):
raise ValueError("Invalid saved Call ID; inspect call-id.json privately.")
print(f"Call ID: {call_id}", flush=True)
call = client.calls.wait_for_result(call_id, timeout_seconds=300)
if call["id"] != call_id:
raise ValueError("Response Call ID differs from the saved ID; stop and inspect the original call.")
save(directory / "result.json", call)
print(json.dumps({key: call[key] for key in (
"status", "call_outcome", "result_status", "result", "error"
)}, indent=2))
print("Retrieval finished. Read result.json and its transcript before deciding business success.")
return 0


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("action", choices=["start", "resume"])
parser.add_argument("directory", type=Path, help="Private run directory; start requires a new path.")
parser.add_argument("--phone", help="Authorized E.164 test destination; start only.")
args = parser.parse_args()
if (args.action == "start") != bool(args.phone):
parser.error("start requires --phone; resume must omit it.")
if not os.environ.get("CALLE_API_KEY"):
parser.error("Set CALLE_API_KEY before running.")
options = {"api_key": os.environ["CALLE_API_KEY"]}
if base_url := os.environ.get("CALLE_BASE_URL"):
options["base_url"] = base_url
try:
with CalleClient(**options) as client:
return run(client, args.directory, args.phone)
except CalleAPIError as exc:
save(args.directory / "error.json", {
"status_code": exc.status_code, "code": exc.code,
"message": str(exc), "details": exc.details,
})
print(f"HTTP {exc.status_code}: {exc.code}. Inspect error.json privately.", file=sys.stderr)
except (CalleConnectionError, CalleTimeoutError, json.JSONDecodeError, KeyError, TypeError):
print("Request or response unavailable. Keep the saved request and key.", file=sys.stderr)
except (OSError, ValueError) as exc:
print(str(exc), file=sys.stderr)
return 1
print("Run resume with the same key and environment. No automatic retry was made.", file=sys.stderr)
return 1


if __name__ == "__main__":
sys.exit(main())
Loading
Loading