Back to the evidence page

calle_double

A CALL-E that dials nobody, written from the published API and checked against 11 recorded production responses. Two ways to mount it, and the three things about it that are not true.

A CALL-E that dials nobody, written from the published API and checked against recorded production responses.

It exists because the platform ships no sandbox, no dry-run flag and no test key. That was verified by searching the OpenAPI specification, both SDKs, every guide and this repository, and it is why the default run of apps/python/firstbell needs no account and costs nothing.

If you are building on CALL-E, this is the part of that entry you can take.

Two ways to mount it

Inside the process, on the SDK's own transport. The client under test is the shipped calle.CalleClient, not a stand-in for it, so nothing about your call path changes:

from calle_double import CalleDouble, build_transport
from calle_double.transport import BASE_URL

double = CalleDouble()
client = CalleClient(api_key="iams_test_anything", base_url=BASE_URL,
                     http_client=httpx.Client(transport=build_transport(double)))

As a real HTTP server, for anything that cannot be mounted in process: a client in another language, a curl by hand, or an integration test that runs your application as a subprocess.

python -m calle_double.server --port 8787
export CALLE_BASE_URL=http://127.0.0.1:8787
export CALLE_API_KEY=iams_test_anything

It listens on loopback and refuses any other interface unless you pass --i-know-this-is-open. That is not a key check. It authorises any non-empty bearer token, deliberately, because a double that demanded a real key would defeat the point of having one, so the interface it listens on is the only thing keeping it private.

What it answers

POST /v1/calls, GET /v1/calls/{id}, GET /v1/calls, GET /v1/calls/{id}/events and GET /v1/goals. Idempotency keys, cursor pagination, regional number validation, per-recipient attempt ledgers, structured-result schemas and the platform's own error codes and status codes.

There is no cancel route, because production has none. A POST to /v1/calls/{id}/cancel gets a 404 with not_found, which is what the real API gives you, and it matters: the engine can cancel a call internally and refusing to expose that over the wire is what stops a client being written against a brake that does not exist. Once a call is accepted it runs to completion, and a concurrency cap is the only control you have.

Outcomes are yours to set. Answered, no answer, voicemail, a schema-valid result whose every field says unknown, a platform failure whose accounts of itself disagree, a timeout, a replay of a call already placed. That last group is the reason this is worth having: those are the responses you cannot ask production for.

Why you should believe it

evidence/api-shape.json records every key path and JSON type returned by the real API across 11 recorded production responses, and the same for the double, and compares them. Paths and type names only: no transcript text, no phone number, no provider call id and no field value of any kind, so the record is publishable.

python tools/double_conformance.py --check

That reads the committed record and confirms the double still emits every path production returned. It exits 0 on a clean checkout with nothing installed beyond the requirements, because it compares against the record rather than against the recordings. Without --check it re-derives the record from the call recordings, which are deliberately not committed, and exits 3 saying it could not measure rather than reporting a pass.

missing_in_double is currently empty. It is a list rather than a boolean so that a future edit narrowing the double names what it dropped.

What is not true about it

Tests

tests/test_calle_double.py and tests/test_double_guards.py in the parent app, run by python -m pytest. The second one exists because a guard sweep found eleven guards inside this package that no test noticed being deleted.

This page is calle_double/README.md in the repository, rendered at build time. The file is the original and this is a copy of it, so if the two ever disagree the file is right.