AgentPhone Documentation Audit
The docs are well-structured for a young product (Mintlify, llms.txt, OpenAPI in JSON and YAML), but the API surface has casing drift across three dimensions, prose-only enums missing from the spec, a Create Agent table that omits the fields you actually need to configure the agent, and a Quick Start that 404s on copy-paste.
1. Three casing conventions collide on the same agent identifier (critical)
Location: /api-reference/messages/send-message-v-1-messages-post, /documentation/guides/agents, /welcome
Problem: The same conceptual field is spelled three different ways depending on where it appears:
- URL path params use snake_case:
/v1/agents/{agent_id}/numbers,/v1/agents/{agent_id}/conversations,/v1/agents/{agent_id}/calls. - Request bodies on those same endpoints use camelCase: the Welcome quick start posts
{"numberId": "NUMBER_ID"}to/v1/agents/{agent_id}/numbers, and elsewhere the body field isagentId(e.g.,POST /v1/calls/web, the Create numberagentIdfield). - The messaging endpoint goes back to snake_case for the body:
SendMessageRequestis fully snake_case —agent_id,to_number,media_url,media_urls,number_id— andSendMessageResponsereturnsfrom_number,to_number,media_urls.SendReactionResponsethen mixes both worlds — request field isreactionbut the response returnsreaction_typeandmessage_id.
Consequence: A developer or AI coding agent who internalizes the camelCase convention from the Welcome quick start will send agentId/toNumber/mediaUrl to POST /v1/messages and get validation errors with no obvious cause. SDK code generators that map agent_id (path) and agentId (body) as different concepts will produce broken types. Reaction handlers that read reaction off the response object will get undefined.
The fix: Pick one casing per the API style guide (almost certainly camelCase, given that the rest of v1 uses it) and migrate. At minimum, normalize path params to camelCase (/v1/agents/{agentId}/...), migrate /v1/messages request and response bodies to camelCase with deprecation aliases, and align SendReactionResponse to return reaction and messageId. Add a prominent style note to the OpenAPI description until the migration is complete.
2. Reaction enum documented in prose but missing from the OpenAPI schema (critical)
Location: /documentation/guides/conversations vs /api-reference/messages/send-reaction-v-1-messages-message-id-reactions-post
Problem: The Conversations guide states reaction must be "One of: love, like, dislike, laugh, emphasize, question". The OpenAPI schema for SendReactionRequest defines reaction as a bare { type: string } with no enum, no examples, and no description.
Consequence: AI agents and SDK code generators that drive off the OpenAPI spec — which is the entire point of shipping one — won't know the allowed values. They'll happily generate code that sends "thumbs_up" or "❤️" and only discover the constraint at runtime. Anyone reading the API reference page (rather than the prose guide) gets zero guidance on valid input.
The fix: Add enum: [love, like, dislike, laugh, emphasize, question] to the reaction property in the OpenAPI schema, plus a description explaining the iMessage-only constraint. Do the same audit pass on every other "one of" enumeration in the prose guides — voiceMode, modelTier, sttMode, denoisingMode, action — to make sure each is enumerated in the spec, not just the guide.
3. Create Agent guide table omits fields the docs themselves rely on (significant)
Location: /documentation/guides/agents (Create agent table) vs CreateAgentRequest schema and surrounding prose
Problem: The Create Agent field table lists name, voiceMode, modelTier, sttMode, denoisingMode, transferNumber. The OpenAPI excerpt of CreateAgentRequest visible from llms-full.txt additionally shows ambientSound and maxSilenceMs — neither appears in the guide's table. The Voice modes section explicitly says hosted mode "Uses a built-in LLM with the agent's systemPrompt," and the next section instructs readers to "Use the returned voice identifiers (e.g., Polly.Amy, Polly.Joanna) in the voice field when creating agents" — but neither systemPrompt nor voice appears in the guide's table, and neither appears in the portion of CreateAgentRequest visible from llms-full.txt (the schema excerpt is truncated, so the live OpenAPI may still expose them; that should be confirmed before publishing the fix).
Consequence: A developer following the guide cannot configure an agent's persona (no systemPrompt), pick a voice (no voice), or tune call behavior (no ambientSound, no maxSilenceMs) without bouncing between the prose, the OpenAPI JSON, and trial-and-error POSTs. For hosted-mode agents specifically — the mode the docs pitch as "no webhook or server needed" — the most important configuration knob is invisible in the guide.
The fix: Add voice, systemPrompt, ambientSound, and maxSilenceMs rows to the Create agent field table with type, default, and a one-line description each, after confirming each one against the live OpenAPI. Cross-check the guide tables for every other resource (numbers, webhooks, conversations, calls) against the OpenAPI to find the same drift.
4. The Quick Start's third step contains an unsubstituted URL placeholder (significant)
Location: /welcome — Quick Start step 3
Problem: Step 3 is literally:
curl -X POST "https://api.agentphone.to/v1/agents/{agent_id}/numbers" \
-H "Authorization: Bearer YOUR_API_KEY" \
...
-d '{"numberId": "NUMBER_ID"}'
There is no instruction in the Steps block to replace {agent_id} with the ID returned from step 1. The placeholder is also not styled or marked as a placeholder — it's the literal URL on the page. The body uses NUMBER_ID for the same purpose but at least that one is in caps and reads like a placeholder; {agent_id} looks like a real path-template syntax a tool might handle.
Consequence: A developer who copy-pastes the snippet verbatim sends a POST to a URL containing the literal characters {agent_id} and gets a 404 (or, worse, a routing match they didn't expect). AI coding agents that resolve YOUR_API_KEY and NUMBER_ID from environment variables but don't recognize curly-brace path templates as placeholders will run the request unmodified.
The fix: Either inline a step-1 → step-3 capture (AGENT_ID=$(curl ... | jq -r .id) then "...$AGENT_ID/numbers"), or replace {agent_id} with a clearly-flagged placeholder like AGENT_ID and add an explicit "replace with the id from step 1" line. Do the same scan across every code example in the docs for stray path-template syntax.
5. Two ways to end a call, three names for DTMF, no precedence rules (significant)
Location: /documentation/guides/calls — Webhook response format
Problem: The webhook response schema documents both hangup: boolean ("Set to true to end the call after speaking") and action: "hangup" (""hangup" to end it"). The DTMF field is named digits but the docs note "Aliases: press_digit, dtmf" — three accepted names for the same field.
Consequence: When hangup: true and action: "transfer" are sent in the same response (a real scenario for agents wrapping up a call), the docs don't say which wins. Developers will write conflicting handlers based on their guess. Three DTMF aliases mean three different code paths in the wild — and AI coding agents pulling examples from different pages will pick different names, fragmenting integrations.
The fix: Pick one canonical field for call termination (action: "hangup" is more consistent with action: "transfer") and deprecate hangup: boolean with a clear timeline. Do the same with DTMF: declare digits canonical, mark press_digit and dtmf as legacy, and remove them from the public docs (keep them in the server for back-compat). Add an explicit precedence table for what happens when multiple control fields are returned in one response.
6. AgentResponse requires a description field that has no visible input (significant)
Location: /api-reference/agents/create-agent-v-1-agents-post
Problem: The OpenAPI excerpt from llms-full.txt declares AgentResponse as required: [id, name, description, voiceMode, voice, createdAt]. But description does not appear in the CreateAgentRequest properties shown in the same excerpt, nor in the Create Agent field table in the Agents guide. Nothing in the prose says whether description is auto-generated, defaulted, set on a separate endpoint, or simply missing from the visible schema.
Consequence: Code generators producing typed clients will see a required-on-read field that has no obvious write path and either omit it (breaking the response model) or guess. Developers reading the response shape will not know whether they can set the description or whether it's derived from name or systemPrompt. AI agents trying to round-trip an agent (read → modify → write) will fail because they have a required field they cannot set.
The fix: Either add description to the CreateAgentRequest (and the guide table) with type and behavior documented, or — if it really is auto-generated — make it not-required on the response and document its origin. At minimum, document the field's source in one place a reader can find from either endpoint.
7. Missing Detach Number operation despite Attach being prominent (significant)
Location: /documentation/guides/agents and /documentation/guides/phone-numbers
Problem: The Agents guide documents POST /v1/agents/{agent_id}/numbers ("Attach number to agent") with a full curl example in the Welcome quick start. Neither the Agents guide nor the Phone numbers guide documents a corresponding detach operation. The Phone numbers guide describes Delete (release), which destroys the number entirely; there is no documented intermediate state for "keep the number, just unbind it from this agent."
Consequence: Developers who want to move a number to a different agent or temporarily park it have no documented path. They will either guess at an undocumented endpoint, release-and-reprovision (losing the number), or open a support ticket. AI agents reasoning about lifecycle operations will not be able to plan a detach step.
The fix: If a detach endpoint exists, document it on both guides with a curl example. If it doesn't, ship one — moving a number between agents is a core ops requirement — and document it.
8. The route /agents returns 404 despite being the obvious shorthand (minor)
Location: https://docs.agentphone.to/agents
Problem: A HEAD request to https://docs.agentphone.to/agents returns HTTP/2 404. The canonical Agents guide lives at /documentation/guides/agents, and the llms.txt sitemap correctly points there. The short path is the URL a human would guess, type, or share, and it isn't wired up.
Consequence: Anyone typing or sharing the obvious shorthand lands on a 404. External links to the short path break. AI agents discovering the 404 may cache it or skip indexing.
The fix: Add a Mintlify redirect from /agents (and other plausible short paths like /calls, /conversations, /webhooks) to the corresponding /documentation/guides/... URL. Run a link checker on the rendered site to find the rest of these.
9. Web call access token TTL equals the voice webhook timeout (minor)
Location: /documentation/guides/calls — Web calls and Webhook timeout
Problem: The web-call access token returned by POST /v1/calls/web is "valid for 30 seconds." The voice webhook default timeout is also "30-second default timeout (configurable from 5–120 seconds per webhook)." There's no documentation of what happens to a hosted-mode flow where a slow webhook turn and a freshly-minted token are racing the same 30-second budget, no refresh endpoint described, no error code surfaced for an expired token, and no recommended pattern (mint on user click vs. mint on page load).
Consequence: Real-world UIs mint tokens on page load and run into expiration on slow user actions. Developers who configure a webhook timeout near 30s and then mint a token at the start of a turn can have both expire together with no clear signal which failed. Without an error code or refresh path documented, retries will be silent or opaque.
The fix: Document the exact error returned when the token is expired or already consumed, recommend "mint on user click" as the canonical pattern with a code snippet, and either expose a refresh endpoint or state explicitly that the only path is to mint a fresh token. Add a note to the Webhook timeout warning that the 30s default also matches the access-token TTL so developers don't conflate the two budgets.
10. Authentication section appears to end without a path to obtaining a key (minor)
Location: /welcome — Authentication
Problem: Every Quick Start curl uses Authorization: Bearer YOUR_API_KEY. In the scraped MDX excerpt, the Authentication section consists of one line — "Include your API key as a Bearer token in every request:" — and the excerpt cuts off there. The visible content has no signup link, no dashboard pointer, no env var convention, and no token format/expiration/rotation/scope information. The scraped excerpt is truncated, so the rendered page may contain more; this should be confirmed on the live page.
Consequence: If the rendered Authentication section really does end where the scrape ends, a developer running through the Quick Start verbatim hits the first request, gets a 401, and has to leave the docs to figure out account creation. Agents that resolve placeholders by pattern-matching YOUR_API_KEY against environment variables will not know which env var to look for.
The fix: Verify the live page first. If the section truly stops there, add a 3-line "Get your API key" subsection: where to sign up, where in the dashboard to find/create the key, the recommended env var (AGENTPHONE_API_KEY), and rotation/expiration behavior. If a key prefix exists (ap_live_…), document it so agents can validate before sending.
What they do well
- llms.txt is present and well-structured; OpenAPI is published in both JSON and YAML, so agents have a real chance at code generation.
- The Calls guide has explicit, useful operational specifics — 30-second voice webhook timeout (configurable 5–120s), 30-second web token TTL, the silence/hangup default — that are exactly the kind of numbers most voice APIs hide.
- The 10DLC compliance constraint for outbound SMS is called out up front in the Phone numbers guide, so developers don't discover it in production.
Top 3 recommendations
- Pick one casing convention and migrate, end-to-end. The three-dimensional drift — snake_case path params, camelCase bodies, snake_case
/v1/messages— is the single most agent-hostile thing in the surface. It will silently break copy-paste integrations and SDK generators. - Get every prose enum into the OpenAPI spec. Start with
reaction, thenvoiceMode,modelTier,sttMode,denoisingMode,action. The spec is what agents and SDK generators read. - Reconcile the Create Agent guide and schema with reality. Add
voice,systemPrompt,ambientSound,maxSilenceMsto the guide table, resolve thedescriptionrequired-but-unsettable mystery onAgentResponse, and fix the literal{agent_id}placeholder in the Quick Start so the very first integration attempt actually works.