Build an Auditable AI Phone Workflow with Telnyx Missions Telnyx published an open-source Python sample, missions-audit, that builds an auditable AI phone workflow on its AI Missions API. The sample creates a durable mission, starts a run, submits an ordered three-step plan, advances step statuses with typed events, optionally schedules an AI-assisted phone call, and renders a timestamped audit report from fresh server state. The design treats the audit trail as part of the workflow's API contract rather than something reconstructed later from process logs. An AI phone workflow can return a successful response and still leave a difficult question unanswered: what actually happened? If a clinic says an assistant called a patient, confirmed an appointment, and completed the outreach plan, a reviewer may need more than application logs. They need the objective, the approved plan, every observed state transition, the assistant associated with the run, and the final result in one durable record. The open-source missions-audit https://github.com/team-telnyx/telnyx-code-examples/tree/main/missions-audit sample shows one way to build that record with Python and Telnyx AI Missions. It creates a mission, starts a run, submits a plan, advances step statuses, logs typed events, optionally schedules a real AI-assisted phone call, and renders the resulting ledger as a timestamped audit report. The important idea is simple: the audit trail is part of the workflow's API contract, not a story reconstructed later from process logs. A mission is the durable objective. A run is one execution of that objective. Each run has an ordered plan, and each plan contains steps whose statuses change as work progresses. The sample combines five related resources: | Resource | Purpose | |---|---| | Mission | Stores the durable objective | | Run | Represents one execution and its final result | | Plan and steps | Describe the intended work and its progress | | Typed events | Record the chronological audit timeline | | Agent link | Identifies the Telnyx AI assistant associated with the run | This produces a queryable record of what was planned, what the worker observed, and how the run ended. The project separates the workflow into five small programs: create mission.py creates the durable mission. start run.py starts one execution of that mission. plan steps.py submits an ordered three-step plan. execute.py advances the run, steps, and event timeline. audit.py reads fresh server state and renders the report. The default plan is: provision assistant schedule outreach call verify outcome Those names describe the live workflow, but the project is safe by default. More on that distinction shortly. The sample uses raw HTTP requests against https://api.telnyx.com/v2 . Missions responses are wrapped in a data envelope and use identifiers such as mission id and run id . Creating the durable objective starts with POST /ai/missions : mission = post "/ai/missions", { "name": "Clinic outreach audit", "description": "Verify clinic contacts can be reached", "instructions": "Verify the contact is reachable, confirm the appointment " "slot briefly, and stay polite." , "execution mode": "external", } The worker is external, so the application controls execution while the Missions API keeps the durable record. One execution is created with: run = post f"/ai/missions/{mission id}/runs", {} The run begins in pending . The worker can advance it through running to succeeded or failed . The API also supports paused and cancelled states for operator intervention. The plan uses client-selected step id values, descriptions, and one-based sequence numbers: steps = { "step id": "provision assistant", "description": "Create or select the outreach assistant.", "sequence": 1, }, { "step id": "schedule outreach call", "description": "Create one scheduled phone-call event.", "sequence": 2, }, { "step id": "verify outcome", "description": "Confirm the event reached a terminal status.", "sequence": 3, }, post f"/ai/missions/{mission id}/runs/{run id}/plan", {"steps": steps} Steps move from pending to in progress , then to completed or failed . A skipped state is available for intentional no-ops. The most useful pattern in the sample is not an endpoint. It is a rule enforced by execute.py : Record every state change the worker observes, and never invent a platform outcome. When a step starts, the worker updates the step and writes a step started event. When it completes, the worker writes the corresponding status and step completed event. Failures become both a failed step and a step failed event. python def run step step id, work : update step step id, "in progress" log event "step started", f"Step {step id} started", step id try: result = work except Exception as exc: update step step id, "failed" log event "step failed", f"Step {step id} failed: {exc}", step id, raise update step step id, "completed" log event "step completed", f"Step {step id} completed", step id return result In live mode, the worker polls the scheduled-event API every five seconds, with a hard ten-minute cap. Each newly observed event status becomes a status change mission event. A request failure or timeout becomes an explicit error event and causes the verification step to fail. That means the ledger never reports that a call completed merely because the worker expected it to complete. DEMO MODE=true is the default. In demo mode, the sample exercises the mission, run, plan, step, and event APIs without creating an assistant, creating a scheduled event, placing a call, or linking an agent. It records a custom event with this meaning: { "demo mode": true, "note": "no assistant created, no call placed" } The run's result summary says the same thing. The simulated walk is useful for learning the ledger mechanics, but it cannot be mistaken for evidence of a real phone call. Set DEMO MODE=false to exercise the live path. Live mode requires placeholder-backed environment variables for a destination you control and a phone number attached to the assistant: TELNYX API KEY=YOUR TELNYX API KEY DEMO MODE=false DESTINATION NUMBER=