lydoh
Start free trial

Specification

ATEP 1.0, draft 3

Agentic Telephone Exchange Protocol. Editor: Lydoh. License: Apache 2.0. A draft for public comment: the discovery path, the hello sentences, and the DTMF frames are frozen from this draft onward and will not change in any 1.x release.

Version 1.0, draft 3. Editor: Lydoh (hello@lydoh.com). License: Apache 2.0.

Abstract

ATEP lets two AI agents that meet over an ordinary telephone call, one calling on behalf of a person and one answering on behalf of a business, recognize each other within the first seconds of the call, bind their web endpoints to that exact call, prove whom they act for, and complete a bounded task over a structured HTTPS channel, with the voice call kept as the fallback. It needs one JSON document at a fixed well-known path, one HTTPS endpoint, one spoken sentence, and a few DTMF digits. It works when only one side implements it. Every action that binds a person or a business ends in a receipt both sides sign.

Status

This is a draft for public comment. The discovery path (Section 4.1), the hello sentences (Section 5.1), and the DTMF frames (Section 5.2) are frozen from this draft onward: they will not change in any 1.x release. Message fields may be added in 1.x; none will be removed or change meaning.

1. Terms

The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY are to be interpreted as in RFC 2119 and RFC 8174.

2. Design

ATEP has five layers. Each is usable without the ones above it.

L4  Intents      availability, book, reschedule, cancel, quote, message, handoff
L3  Session      delegation, mandates, receipts, state machine, attestations
L2  Exchange     HTTPS + JSON, JWS-signed, JCS-canonicalized
L1  Handshake    spoken hello, DTMF frames, call binding
L0  Discovery    /.well-known/atep.json, DNS, the registry

Principles the design follows:

  1. The telephone number is the address. Discovery starts from an E.164 number because that is all a caller has.
  2. It works one-sided. A card helps a caller even when the answerer is human. A hello costs a caller nothing when the answerer is human.
  3. Voice is the transport of last resort, never the transport of record.
  4. Nothing binds a principal without a receipt.
  5. Every session has a handoff path to a human, in both directions.
  6. Disclosure is protocol, not prompt.
  7. Authority is declared, scoped, signed, and revocable, and is never verified by the agent itself.
  8. The vocabulary is small and closed. Extensions must be declared in the card.
  9. Conformance is a public test anyone can run against a number.
  10. The discovery path never moves.

3. Data conventions

4. Discovery (L0)

4.1 The card

A party that answers or places ATEP calls MUST publish a card at:

https://<domain>/.well-known/atep.json

served with Content-Type: application/json, over TLS, with a cache lifetime of at least one hour. The path is frozen.

A platform that hosts agents for many businesses publishes its own card at the well-known path and MAY host one card per business at any HTTPS URL on the same domain (for example https://lydoh.com/atep/cards/cedar-grove.json). Such a hosted card is a full card in its own right: it names the business as party, lists the business's numbers, and is signed with the platform's key. The registry and the DNS record (Section 4.2) point to hosted cards by URL. A business with its own domain MAY instead publish the card itself; a number MUST resolve to exactly one card.

{
  "atep": "1.0",
  "party": {
    "id": "urn:atep:party:9f2c0e4a-6e6d-4a2e-9b1a-3f4b0b9a1c77",
    "name": "Cedar Grove Family Medicine",
    "kind": "business",
    "website": "https://cedargrovefm.example"
  },
  "numbers": ["+14632513583"],
  "endpoint": "https://api.example/atep/v1/sessions",
  "keys": [
    { "kid": "2026-09", "kty": "OKP", "crv": "Ed25519", "x": "…", "use": "sig" }
  ],
  "intents": ["availability", "book", "reschedule", "cancel", "message", "handoff"],
  "services": [
    { "id": "new-patient", "name": "New patient visit", "duration_minutes": 30 },
    { "id": "follow-up", "name": "Follow-up", "duration_minutes": 15 }
  ],
  "hours": {
    "timezone": "America/New_York",
    "weekly": { "mon": [["09:00", "17:00"]], "tue": [["09:00", "17:00"]] }
  },
  "languages": ["en"],
  "policies": {
    "records_calls": true,
    "no_record_mode": true,
    "human_available": "business_hours",
    "accepts_agents": "yes",
    "no_sales": true,
    "retention_days": 365,
    "do_not_call_days": 30
  },
  "disclosure": "This is Cedar Grove Family Medicine's AI assistant.",
  "healthcare": true,
  "a2a": "https://cedargrovefm.example/.well-known/agent-card.json",
  "contact": { "technical": "it@cedargrovefm.example", "abuse": "abuse@cedargrovefm.example" },
  "issued": "2026-09-23T00:00:00Z",
  "expires": "2027-09-23T00:00:00Z",
  "sig": "…"
}

Field rules:

4.2 Resolution from a number

Given an E.164 number, a caller resolves a card in this order and stops at the first success:

  1. A cached card whose numbers includes the number and which has not expired.
  2. The registry (Section 4.4): GET /v1/numbers/{e164}.
  3. A DNS TXT record at _atep.<domain> when the caller already knows the party's domain: "v=atep1; card=https://<domain>/.well-known/atep.json".
  4. The DTMF pointer received during the handshake (Section 5.2), which restarts this list with the pointer's platform or number.

If nothing resolves, ATEP is not available for that call and the caller proceeds in plain voice. Resolution MUST NOT delay dialing; a caller MAY resolve after the answerer's hello.

4.3 Verifying a card belongs to a number

A card's numbers field is a claim. It becomes verified in one of two ways:

A caller MUST NOT send a delegation to an endpoint that is neither registry-verified nor live-bound.

4.4 The registry

A registry is an HTTPS service with this interface. Anyone may run one; a caller is configured with one or more registry base URLs. The reference registry is at https://atep.io.

Method and pathPurpose
GET /v1/numbers/{e164}Returns { "number", "card": "<url>", "party", "verified_at", "platform": "<code>" } or 404.
GET /v1/platforms/{code}Returns { "code", "card": "<url>", "party", "verified_at" } or 404. code is 2 to 6 digits, issued by the registry.
POST /v1/numbersBody { "number", "card" }. Starts verification: the registry fetches the card, checks numbers includes the number, calls the number, and expects the handshake and binding. Returns { "verification_id", "status": "pending" }.
GET /v1/verifications/{id}Returns { "status": "pending" | "verified" | "failed", "reason" }. reason is present only when status is failed.
GET /v1/cards?party={id}Returns { "party", "cards": [{ "card", "numbers", "verified_at", "platform" }] }.

A registry MUST re-verify a number at least every 90 days and MUST remove a number whose card expired or whose verification fails twice. Every registry response body carries a sig member, signed per Section 3 with a key published in the registry's own card at /.well-known/atep.json.

5. Handshake (L1)

The handshake happens on the voice call. It has three parts: the hello sentence, the DTMF frame, and binding.

5.1 The hello

An ATEP agent's first utterance on a call MUST be its hello. The answerer speaks it on answer, within 10 seconds of the call connecting. The caller speaks it after the answerer's hello frame when it hears one, and otherwise after the answerer's first sound, in either case within 10 seconds of that moment. The templates are frozen. Braces mark substitutions.

Caller:

This is an AI assistant calling for {principal}. Callback {number}. ATEP.

Answerer:

{disclosure} ATEP.

where {disclosure} is the card's disclosure sentence.

If the speaker records the call, it MUST insert "This call is recorded." immediately before "ATEP." The announcement is then inside the recording.

{principal} is the principal's display name as it appears in the delegation. {number} is a callback number for the responsible entity, read as digits, at which a human can reach the caller's principal or platform. It MUST NOT be a dialer-only number.

An agent MUST speak the hello in the call's language. The English templates are normative; a translation MUST preserve every element (AI status, principal or party name, callback number for a caller, recording notice, and the word "ATEP").

An agent MUST re-assert that it is an AI whenever asked, in any phrasing, on any leg, at any time.

5.2 The DTMF frame

Immediately after speaking "ATEP." the agent MUST send a DTMF frame:

Caller:    # 1 #
Answerer:  # 1 <pointer> #

1 is the major version. <pointer> is OPTIONAL and is one of:

Frames are sent as RFC 4733 telephone events where available and as in-band tones otherwise. A sender SHOULD use 100 ms tones with 100 ms gaps and MUST NOT exceed 500 ms per digit including its gap; a receiver MUST accept any spacing up to that. A frame MUST start within 2 seconds of the end of "ATEP." and MUST NOT be interrupted by speech. An answerer on a registered platform SHOULD send the platform-code pointer rather than its E.164 digits: it is a third of the length, and every 100 ms on the handshake is paid on every call.

A receiver MUST split on # and MUST tolerate several frames arriving in one buffer, since telephony stacks commonly batch keypad input.

Detection: an agent that hears a well-formed frame within 8 seconds of the end of the other side's hello knows it is talking to an ATEP agent. If it hears no frame in that window, it MUST assume the other side is a human or a phone tree, continue in plain voice, and MUST NOT send further frames. It MAY log peer: human.

A # followed by an unexpected digit sequence is not a frame. Phone trees commonly ignore # sequences that match no menu option; an agent MUST tolerate a frame being swallowed and fall back to voice.

5.3 Binding

Binding ties the HTTPS session to this exact call. Both sides prove control of their leg.

  1. On detecting the answerer's frame, the caller resolves the answerer's card (Section 4.2) and, within 3 seconds, POSTs a hello message (Section 6.2) to the card's endpoint. The hello carries a call_ref (both numbers, the connect time, a nonce) and a four-digit challenge chosen by the caller.
  2. The answerer's endpoint replies with hello_ack, which carries the session id and a four-digit challenge chosen by the answerer.
  3. After it has heard the caller's hello frame, the answerer's leg sends DTMF # 0 <answerer challenge> # on the call. It MUST NOT send it while the caller is speaking.
  4. The caller's leg, on hearing it, compares it to the hello_ack. On match it sends DTMF # 0 <caller challenge> # on the call and POSTs a bind message carrying what it heard.
  5. The answerer compares the DTMF it heard with the challenge in the hello, and the bind message with the challenge it sent. On match it replies bound. The session is now bound.

Binding MUST complete within 15 seconds of the end of the caller's hello frame, or either side sends error with binding_failed; nobody hangs up, and both continue in plain voice. Implementations SHOULD emit binding frames from the telephony layer rather than through a language model; measured on one production stack, a model-triggered frame starts 2.7 seconds after the cue and a telephony-layer frame starts within 100 ms. A challenge MUST be freshly random per call and MUST NOT be reused.

What binding proves: the party controlling the endpoint also controls the leg that sent the matching digits on this call. An attacker who obtained a card URL cannot complete a session for a call they are not on. Replay is prevented by call_ref.nonce and a 10-minute validity on hello.

5.4 Silence and release

After sending its frame, an agent MUST NOT speak on the call while the session is active, except to complete a handoff (Section 8.7) or after a fallback (Section 5.5). Hold music, announcements, and silence on the line are not reasons to speak.

Once bound, either side MAY send release. When both sides have acknowledged, both hang up and the session continues over HTTPS alone. A released session MUST reach receipted, declined, or error within 15 minutes. A handoff intent on a released session requires a new call: the caller redials, speaks its hello with the session id appended ("ATEP session {id}."), and the answerer rebinds using Section 5.3 with the existing session.

Release is where ATEP saves cost: a bound session typically needs the call for less than 20 seconds.

5.5 Fallback to voice

At any point before receipted, either side MAY send fallback with a reason. Both sides then continue the task in plain voice, starting from the last confirmed state. A caller that loses HTTPS connectivity MUST say so on the call: "ATEP unavailable, continuing by voice." The session's partial log remains valid for the receipt if the task later completes by voice; a voice-completed task produces a receipt with outcome_channel: "voice" signed by whichever side can, and the other side SHOULD countersign when it can.

6. Exchange and session (L2, L3)

6.1 Transport

One session is one HTTPS resource. The caller POSTs messages to the answerer; each POST returns the answerer's reply in the response body. The answerer never pushes messages to the caller in v1; the only requests it makes toward the caller's platform are card fetches and the revocation check (Section 7.1). Every message is a JSON object with this envelope:

{
  "atep": "1.0",
  "id": "m_01J8…",
  "session": "s_01J8…",
  "ts": "2026-09-23T14:30:02-04:00",
  "from": "urn:atep:party:…",
  "type": "…",
  "human_audible": false,
  "body": { },
  "sig": "…"
}

session is absent on hello and REQUIRED on every other message, including hello_ack, whose envelope carries the session id it assigns. A message whose envelope session disagrees with a session in its body is rejected with malformed. sig is the JWS per Section 3 over the envelope without sig, signed with a key from the sender's card. A receiver MUST reject a message whose signature fails, whose ts is more than 5 minutes from its clock, or whose id it has seen before.

Latency: the answerer MUST respond to every POST within 2 seconds. If the work takes longer it MUST reply working with retry_after_ms, and the caller polls with GET {endpoint}/{session}/messages?after={id}. Two seconds is the human conversational clock; on the phone, silence past it reads as a dead call.

6.2 Messages

TypeSenderBody
hellocallercall_ref { from, to, connected_at, nonce }, caller_card (URL), challenge (4 digits), records: bool, language, intents (what the caller wants to do)
hello_ackanswerersession, challenge (4 digits), records: bool, accepts: [intents], healthcare: bool
bindcallerheard (the 4 digits received on the call)
boundanswererbound_at
delegationcallerSection 7.1
delegation_ackanswereraccepted: bool, reason
mandatecallerSection 7.2
intentcallerintent, request (Section 8)
resultanswererintent, response (Section 8)
confirmeitherintent, summary (read-back text), accept: bool
receipteitherSection 7.5
handoffeitherSection 8.7
release / release_ackeitherempty
fallbackeitherreason
workinganswererretry_after_ms
erroreithercode, detail (Section 6.5)

Every field a table row or JSON block in this document lists is REQUIRED unless the prose marks it optional or gives a default. Exceptions stated once here: note in any intent request is OPTIONAL; each member of caps is OPTIONAL; reference, details, and mandate in a receipt are OPTIONAL. Receivers MUST ignore fields they do not recognize; senders MUST NOT rename or repurpose a defined field.

6.3 State machine

hello → bound → delegated → negotiating → confirmed → receipted

From any state, a side MAY move to handoff, declined (via error with declined), released (parallel to the states above; it changes transport, not progress), or error. A session that has not advanced for 5 minutes on a live call, or 15 minutes released, is expired and MUST NOT produce a receipt.

6.4 Confirmation

A book, reschedule, cancel, or accepted quote MUST be confirmed before a receipt. The answerer sends confirm with a summary: a single sentence in the call's language stating the outcome exactly as a human would read it back ("Booked: new patient visit, Tuesday October 6 at 9:00 AM, for Sohel S., reference 4471."). The caller replies confirm with the same summary and accept: true, or accept: false with a reason. Mismatched summaries are not a confirmation.

If any leg after binding was human-audible, the confirming side MUST also speak the summary on the call before sending confirm.

6.5 Errors

detail is an object. It MAY carry text; some codes require more, listed below.

CodeMeaning
malformedThe message fails the schema, or its envelope and body disagree.
unsupported_versionMajor version not supported.
bad_signatureSignature or key check failed.
binding_failedChallenge mismatch or timeout.
declinedThe party will not proceed; detail says why.
delegation_insufficientScope, caps, or expiry do not cover the request.
mandate_requiredThe action needs a per-transaction mandate (Section 7.2). detail.terms carries the exact terms.
out_of_scopeIntent not served by this party.
not_availableNo slot, item, or person matches.
needs_humanThe party requires a human on its side; a handoff follows.
neither_confirm_nor_denyHealthcare profile; Section 9.
rate_limitedToo many sessions from this platform. detail.retry_after is an RFC 3339 time.
do_not_callThe party asks not to be called. detail.until is an RFC 3339 time, policies.do_not_call_days from now.
expiredSession timed out.

6.6 Human-audible attestation

Every message carries human_audible. A sender sets it true if a human on its side may have heard the content of that message spoken, or may hear the call at that moment. After binding, an agent that has muted itself and whose principal is not on the line sets it false. A session is machine-only when every message after bound from both sides carries false. The receipt records this (Section 7.5). A machine-only session's exchange is not human-directed content; the hello and any handoff always are.

Neither side may treat machine-only as an exception to disclosure or recording rules. The hello is spoken regardless, because neither side can know at connect time who is listening.

7. Authority, records, and receipts (L3)

7.1 Delegation

The caller's first message after bound MUST be a delegation:

{
  "principal": {
    "id": "urn:atep:principal:c1b6…",
    "name": "Sohel S.",
    "contact": "+17755551234",
    "platform": "https://lydoh.com/.well-known/atep.json"
  },
  "scope": ["availability", "book", "message", "handoff"],
  "caps": {
    "max_spend": { "amount": 200, "currency": "USD" },
    "date_window": { "start": "2026-10-01", "end": "2026-10-31" },
    "max_bookings": 1
  },
  "identity_verification": "none",
  "issued": "2026-09-23T14:29:00-04:00",
  "expires": "2026-09-23T15:29:00-04:00",
  "revocation_url": "https://lydoh.com/atep/v1/delegations/d_01J8…",
  "credential": "<JWS>"
}

The answerer replies delegation_ack. accepted: false ends the session with declined.

7.2 Mandates

Some actions bind the principal beyond a standing delegation: cancel, a reschedule that carries a fee, and accepting a quote. For these the answerer MUST reply mandate_required with the exact terms, and the caller MUST obtain a mandate from its principal before proceeding:

{
  "for": "cancel",
  "reference": "4471",
  "terms": { "fee": { "amount": 50, "currency": "USD" }, "text": "Cancellation within 24 hours carries a $50 fee." },
  "issued": "…",
  "expires": "…",
  "credential": "<JWS>"
}

for is one of cancel, reschedule, quote, or the name of an extension intent that declares in its definition that it requires a mandate.

How the platform obtains the principal's consent (a push notification, a text, a spoken yes on another line) is the platform's business, but the mandate's credential MUST be issued after the terms were shown to the principal, and the platform MUST keep evidence of that. A mandate expires in at most one hour and covers exactly one action with exactly those terms.

7.3 Read-back

Every book, reschedule, cancel, and accepted quote MUST pass confirmation (Section 6.4). The summary in the receipt is the text both sides accepted.

7.4 Retention and revocation

A party MUST keep session logs and receipts for at least policies.retention_days and MUST NOT keep them indefinitely. A principal's platform MUST be able to revoke a delegation at any time by making revocation_url return revoked: true. A business that receives a do_not_call request from its principal MUST answer subsequent sessions from that caller platform with do_not_call for the declared period.

7.5 Receipt

A receipt is produced for every state-changing outcome and for every message and handoff:

{
  "session": "s_01J8…",
  "intent": "book",
  "outcome": "booked",
  "outcome_channel": "atep",
  "reference": "4471",
  "summary": "Booked: new patient visit, Tuesday October 6 at 9:00 AM, for Sohel S., reference 4471.",
  "details": { "service": "new-patient", "start": "2026-10-06T09:00:00-04:00", "duration_minutes": 30 },
  "principal": { "id": "urn:atep:principal:c1b6…", "name": "Sohel S.", "platform": "…" },
  "party": { "id": "urn:atep:party:9f2c…", "name": "Cedar Grove Family Medicine", "card": "…" },
  "delegation": "sha256:…",
  "mandate": null,
  "disclosures": {
    "caller": "This is an AI assistant calling for Sohel S. Callback 7 7 5 5 5 5 1 2 3 4. ATEP.",
    "answerer": "This is Cedar Grove Family Medicine's AI assistant. This call is recorded. ATEP."
  },
  "recorded": { "caller": false, "answerer": true },
  "machine_only": true,
  "log_hash": "sha256:…",
  "issued": "2026-09-23T14:30:41-04:00",
  "signatures": { "caller": "<JWS>", "answerer": "<JWS>" }
}

outcome is one of booked, rescheduled, cancelled, not_found, quoted, quote_accepted, message_taken, handed_off, declined, not_available. Response outcome values in Section 8 use the same vocabulary, plus needs_human, which produces no receipt because a handoff follows. log_hash is SHA-256 over the JCS form of the array of all session messages in order. disclosures are the hello sentences as spoken. When one side never spoke an ATEP hello (a human answerer, or a task finished by voice under Section 5.5), that side's member of disclosures, recorded, and signatures is null. Each side signs the receipt without the other's signature. Both sides MUST store it and MUST make it available to their principal in a human-readable form.

8. Intents (L4)

Every intent has a request and a response. Field names are fixed. Free text appears only in the fields Section 3 enumerates, each at most 500 characters.

8.1 availability

Request: service (id from the card), window { start, end }, preferences { times_of_day: ["morning"|"afternoon"|"evening"], days: ["mon"…], provider } (optional), count (max slots, default 5).

Response: slots: [{ start, duration_minutes, provider, id }], note.

Standing delegation suffices. No receipt.

8.2 book

Request: service, slot (an id from availability) or start, for { name, contact } (defaults to the delegation principal), reason (an id from the service's reasons, only if the card lists them), note.

Response: outcome: "booked" | "not_available" | "needs_human", reference, start, duration_minutes, provider, instructions (what to bring, where to park), cancellation_terms { text, fee, cutoff_hours }.

Requires confirmation. Receipt. The date_window and max_bookings caps apply.

8.3 reschedule

Request: reference, to { slot | start }, note.

Response: as book with outcome: "rescheduled" | "not_available" | "needs_human", plus fee if one applies.

If fee is non-zero, the answerer MUST first reply mandate_required. Requires confirmation. Receipt.

8.4 cancel

Request: reference, reason (optional).

Response: outcome: "cancelled" | "not_found" | "needs_human", fee, text.

Always requires a mandate (a cancellation is never reversible by the agent). Requires confirmation. Receipt.

8.5 quote

Request: description (structured: service, text, quantity, location), budget (optional, from caps.max_spend).

Response: outcome: "quoted" | "needs_human" | "declined", price { amount, currency }, includes: [text], excludes: [text], valid_until, terms, reference.

Requesting a quote runs on the standing delegation. Accepting one is a separate intent with intent: "quote", request { accept: true, reference }, which MUST carry a mandate and MUST NOT exceed caps.max_spend. Its response is outcome: "quote_accepted" | "declined", reference. Receipt on acceptance.

8.6 message

Request: to { name, role } (a person or role at the business, optional), text, callback { number, window { start, end } }, urgency: "routine".

Response: outcome: "message_taken", reference, expected_response (text).

urgency MUST be routine. ATEP MUST NOT be used for emergencies or anything where minutes matter; an agent that detects such a request MUST tell its principal to call directly. Receipt.

8.7 handoff

Either side may send handoff:

{ "kind": "join_principal" | "need_human", "reason": "…", "hold_budget_seconds": 900 }

Response (a result with intent: "handoff"): outcome: "handed_off" | "not_available", kind, connected (bool), waited_seconds, callback { number, window { start, end } } (on not_available), reason.

Receipt with outcome: "handed_off" and details { kind, connected: bool, waited_seconds }.

8.8 Extensions

An intent named x-<vendor>-<name> MAY be used only if it appears in both cards' intents. A receiver that does not recognize an extension replies out_of_scope. Extensions MUST NOT weaken any MUST in this document.

9. Healthcare profile

Applies to every session where the answerer's card has healthcare: true. It exists because a healthcare answerer must verify identity and authority before disclosing appointment details, and software cannot use the judgment a human receptionist can.

Caller obligations:

Answerer obligations:

Both:

10. Behavior rules

11. Conformance

LevelNameRequirements
0CardPublishes a valid, signed card at the well-known path. Any business, even human-answered.
1CallerSpeaks the hello, sends the frame, resolves and verifies cards, completes binding, sends a valid delegation, completes availability, book, and message against the reference answerer, honors mandate_required, produces and stores receipts, falls back to voice correctly.
2AnswererSpeaks the hello, sends the frame, serves hello, binding, all seven intents, confirmation, receipts, release, no-record mode, do_not_call, revocation checks, and the 2-second latency contract.
3HealthcareLevel 2 plus Section 9.

The reference tester dials a candidate number, or accepts a call from a candidate caller, runs the scenarios for the claimed level, and issues a signed conformance statement. A registry SHOULD list the level.

12. Security considerations

13. Privacy considerations

The minimum a session discloses is the principal's display name and a contact number. The healthcare profile forbids clinical detail. Receipts carry only what the person would read on a confirmation card. Cards are public by design and MUST NOT contain personal data. Registries store numbers, card URLs, and verification timestamps, nothing about calls.

14. IANA considerations

This document requests provisional registration of the well-known URI suffix atep.json (RFC 8615), change controller Lydoh (hello@lydoh.com), reference this specification, and reserves the DTMF frame prefixes #0 (binding frames) and #1 through #8 (hello frames, by major version) for ATEP use on ATEP calls.

Appendix A. Hello sentences by language

English (normative):

Other languages are added by pull request with a native-speaker review and MUST preserve every element.

Appendix B. Example session

  1. Caller dials +1 463 251 3583.
  2. Answerer: "This is Cedar Grove Family Medicine's AI assistant. This call is recorded. ATEP." then DTMF #114632513583#.
  3. Caller: "This is an AI assistant calling for Sohel S. Callback 7 7 5 5 5 5 1 2 3 4. ATEP." then DTMF #1#.
  4. Caller resolves the card from the registry, POSTs hello with challenge 7204. Answerer replies hello_ack with session s_01J8… and challenge 4831.
  5. Answerer leg sends #04831#. Caller hears it, sends #07204#, POSTs bind { heard: "4831" }. Answerer replies bound. Elapsed since the caller's frame: about 6 seconds on a telephony-layer implementation at 380 ms per digit.
  6. Both send release; both hang up. Elapsed on the call: 19 seconds.
  7. Caller POSTs delegation (scope availability, book; window October; one booking). Answerer checks the credential and the revocation URL, replies delegation_ack.
  8. Caller intent availability { service: "new-patient", window: October, preferences: mornings }. Answerer result { slots: [Oct 6 9:00, Oct 8 9:30, …] }.
  9. Caller intent book { slot: Oct 6 9:00 }. Answerer result { outcome: booked, reference: 4471, cancellation_terms }.
  10. Answerer confirm { summary: "Booked: new patient visit, Tuesday October 6 at 9:00 AM, for Sohel S., reference 4471." }. Caller confirm { same summary, accept: true }.
  11. Both sign the receipt. Sohel's phone shows the booking; the practice's front desk sees the receipt on the appointment.

Appendix C. Changes

Authored by Lydoh. Draft 2, September 2026. Comments to hello@lydoh.com.

Implement it in an afternoon.

Level 0 is one signed JSON file at a fixed path. Level 1 and Level 2 are the caller and the answerer, with the plain-voice fallback built in.