Skip to content

docs(telephony): trunks-and-caller-IDs rework (PRO-2785, atoms-platform#3516) - #413

Open
abhishekmishragithub wants to merge 6 commits into
docs/sip-wire-referencefrom
docs/telephony-rework-pro-2785
Open

docs(telephony): trunks-and-caller-IDs rework (PRO-2785, atoms-platform#3516)#413
abhishekmishragithub wants to merge 6 commits into
docs/sip-wire-referencefrom
docs/telephony-rework-pro-2785

Conversation

@abhishekmishragithub

@abhishekmishragithub abhishekmishragithub commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Public docs for the telephony rework in smallest-inc/atoms-platform#3516 (PRO-2785): SIP trunks become one resource per direction, an agent answers on a number/trunk, and every call names its own caller ID.

atoms-platform#3516 is live on prod (merged 2026-09-04 19:59 UTC); verified that no post-review commit touched the documented API surface (routes, validation, compat, resolution). Changelog entry dated to the release day. Stacked on #412: merge order is #411 -> #412 -> #413.

Written against the PR's implementation, not the Notion proposal. Where they diverge, the code wins:

  • No outbound bindings exist; outbound is per-call (fromNumber / fromNumbers / transfer source). The /agent/{id}/telephony routes from the proposal were never built; the real surface is /agent/{id}/answers + PUT /agent/{id}/transfer-source.
  • No removal date is promised anywhere: deprecated paths send Deprecation: true with no Sunset header, and the docs say "a removal date will be announced" rather than repeating the proposal's 45-day window (which exists only in the ticket).
  • fromNumber on /conversation/outbound is matched exactly (no E.164 normalization) — documented explicitly, since +1 415 555 2671 fails where +14155552671 works.
  • import-phone-number still works (201 + Deprecation header), documented as deprecated, not removed.

Changes

  • OpenAPI: /sip-trunk/{inbound,outbound} CRUD, /agent/{agentId}/answers (+ delete by sourceId), /agent/{agentId}/transfer-source; fromNumber + deprecated fromProductId on outbound calls; fromNumbers + deprecated phoneNumberIds on campaigns; import-phone-number marked deprecated; caller-ID resolution description rewritten (the silent platform-number fallback is gone from the product, so it is gone from the docs).
  • New page: Deprecations tab → "Migration Guide (trunks & caller IDs)" — old-to-new mapping, compat window table, behavior changes, dashboard equivalents.
  • SIP Trunking page: setup steps rewritten around the SIP Trunks dashboard page and per-direction endpoints; new "attach to an agent" step; new VPN-only and IP-whitelisting providers section (managed gateway, contact support — per DS-928 guidance, no internal detail).
  • Phone Numbers page: Import SIP tab documentation replaced by the SIP Trunks flow with a deprecation note.
  • Campaigns page: "Call from" field + fromNumbers API note (frozen list, rotation, retry stability).
  • Nav: SIP Trunks + Agent Telephony API-reference groups; Telephony section in the Deprecations tab.
  • Changelog entry (placeholder date).

Test plan

  • fern check passes; API reference renders the two new groups.
  • Migration guide renders under Deprecations and its cross-links resolve (/voice-agents/deprecations/telephony-migration).
  • SIP Trunking page cross-links (migration guide, wire reference) resolve.
  • Before merge: confirm against the FINAL atoms-platform#3516 diff — it is still under review and the contract may shift (watch: fromNumber normalization, deprecation headers on more paths, integration tests may change error strings).

Documents the telephony model shipping in atoms-platform#3516: SIP
trunks as one-resource-per-direction, agents answering on sources, and
per-call caller IDs. Written against the implementation, not the
proposal (no outbound bindings, transfer caller ID on the agent,
Deprecation header without a Sunset date since none is decided).

- OpenAPI: add /sip-trunk inbound+outbound CRUD, /agent/{id}/answers,
  /agent/{id}/transfer-source; fromNumber on outbound calls (exact-match
  semantics documented) and fromNumbers on campaigns with the legacy
  fields marked deprecated; import-phone-number marked deprecated;
  caller-ID resolution section rewritten (no platform fallback).
- New migration guide under the Deprecations tab: old-to-new mapping,
  the compat window table, behavior changes, dashboard equivalents.
- SIP Trunking page: setup steps rewritten around the SIP Trunks page
  and per-direction endpoints, agent attach step added, plus the
  VPN-only/IP-whitelisting providers section (managed gateway, contact
  support).
- Phone Numbers page: Import SIP tab content replaced with the SIP
  Trunks flow; Campaigns page: Call from field + fromNumbers note.
- Changelog entry (re-date to the release day before merging).

Nav: SIP Trunks + Agent Telephony API groups, Telephony section in the
Deprecations tab.
@crim-app

crim-app Bot commented Sep 3, 2026

Copy link
Copy Markdown

crim doesn't review pull requests automatically here.

Comment crim review on this pull request whenever you want a review.

The literal 400 error string on `POST /conversation/outbound` keeps its
em-dash because the platform emits it verbatim; every other em-dash in
the migration guide, SIP trunking page, phone-numbers Import SIP tab,
and openapi.yaml descriptions is rewritten to periods, semicolons, or
commas.
@abhishekmishragithub

Copy link
Copy Markdown
Collaborator Author

Verification loop — docs claims vs platform PR #3516

Cross-checked every routing, error, and lifecycle claim in the migration guide, sip-trunking.mdx, and openapi.yaml against atoms-platform#3516 at commit 0398573a. Findings:

# Docs claim Platform source (PR #3516) Verdict
1 Real routes are /sip-trunk/{inbound,outbound}, /agent/{id}/answers, PUT /agent/{id}/transfer-source apps/main-backend/src/routes/sip-trunk/sip-trunk.route.ts, apps/main-backend/src/routes/agent-telephony/agent-telephony.route.ts ✅ exact
2 Deprecation: true on legacy paths, no Sunset apps/main-backend/src/compat/sunset.ts (DEPRECATION_HEADERS = { Deprecation: "true" }) ✅ exact
3 POST /product/import-phone-number creates both an inbound and an outbound trunk, returns the same product-id shape apps/main-backend/src/compat/legacy-import.ts (creates outbound + inbound + dispatch rule + product pointer in one transaction) ✅ exact
4 fromNumber on POST /conversation/outbound is matched exactly, no normalization; +1 415 555 2671 / 14155552671 will not resolve resolveDialParamsForNumber in apps/main-backend/src/services/telephony/resolve-dial-params.ts (passes number verbatim to findActivePurchasedNumber / findOutboundTrunksPresentingNumber) ✅ exact
5 Omitting the from-number: single-answer number → used; multi-answer → 400 listing options; no-answer → 400 "No caller ID..." getOutboundCallSipData in apps/main-backend/src/utils/call.utils.ts ✅ exact; 400 message quoted verbatim (em-dash preserved as it appears in the platform string)
6 fromNumbers on POST /campaign rotates positionally across members apps/main-backend/src/services/campaign/core/CallLogFactory.ts (fromNumbers[callIndex % fromNumbers.length]) ✅ exact
7 Retries reuse the same caller ID; fall back to the agent only if the original number no longer resolves apps/main-backend/src/routes/campaign/retry.controller.ts (fromNumber: failedCall.fromNumber; catch-and-retry-without-fromNumber on AppError) ✅ exact
8 Campaign list freezes at creation; a campaign that resolves to zero numbers is refused apps/main-backend/src/routes/campaign/campaign.controller.ts (fromNumbers: callerIds stored on campaign doc; callerIds.length === 0 → 400) ✅ exact
9 Inbound trunk: at least one number OR one allowed peer address; wildcard addresses refused; numbers unique platform-wide createInboundTrunkSchema + isUnrestrictedSipAddress in apps/main-backend/src/routes/sip-trunk/sip-trunk.validation.ts ✅ exact
10 Outbound trunk: numbers required (min 1, max 50); cpsLimit 1–50 default 1; address + transport immutable after create createOutboundTrunkSchema + updateOutboundTrunkSchema (SIP_TRUNK_CPS_CEILING = 50; update schema deliberately excludes address + transport) ✅ exact
11 One outbound trunk per carrier address per org; caller ID on only one outbound trunk (409 naming existing trunk) apps/main-backend/src/routes/sip-trunk/sip-trunk.controller.ts ("You already have a trunk for ${normalized.address} ...") ✅ exact
12 Attaching a taken source returns 409 naming the current agent apps/main-backend/src/routes/agent-telephony/agent-telephony.controller.ts ("Already answered by another agent (${existingAgentId}). Detach it there first.") ✅ correct; names the agent by ID
13 Delete guarded: inbound trunk delete 409 names the answering agent; outbound trunk delete 409 names the numbers still in use sip-trunk.controller.ts ("${agentName} answers on this trunk...", "This trunk's numbers are still used by ${stillUsed.join(", ")}...") ✅ exact
14 transferFromNumber = null clears it; unset → outbound transfers use call's own line, inbound transfers refused setTransferSourceSchema + httpSetTransferSource (nullable schema, $unset on null); resolveTransferTrunkId returns "" on unset and pipecat refuses ✅ exact
15 telephonyProductId on agent create/update — legacy path, still works with replace semantics apps/main-backend/src/routes/agent/agent.controller.ts (metadata-only allowlist + applyLegacyAgentFields + DEPRECATION_HEADERS on the specific request) ✅ exact
16 allowInboundCall: false = kill switch honoured at routing; allowInboundCall: true = no effect apps/main-backend/src/compat/legacy-agent-telephony.ts (isSilencedByLegacyFlag returns true iff false) ✅ exact
17 fromProductId still works — legacy alias resolved to its number apps/main-backend/src/routes/conversation/outbound/outbound.controller.ts (fromNumber: fromNumber ?? fromProductNumber) ✅ exact

Push in this loop

  • Stripped em-dashes from every added prose line in the migration guide, sip-trunking.mdx, openapi.yaml descriptions, and the phone-numbers Import SIP tab. The one remaining em-dash on line 2557 is inside the literal 400 error string emitted by the platform ("No caller ID for this call — pass a number to dial from, or attach one to the agent"), so it stays verbatim. Commit e7eb156.
  • No colon-as-connector or en-dashes in the added prose.

Not blocking merge, but worth watching

  • Docs consistently write phoneNumber (singular) in prose about GET /agent/{agentId}. Zod on that response side is not in this PR's scope; the docs claim (phoneNumber: string[], meaning "what the agent answers on") is aligned with the "one answering source may return [] on an extension-only trunk" behavior, but the spec response schema for GET /agent/{agentId} isn't touched here. If the SDK regen picks up a shape drift, worth a follow-up on the response schema override.
  • Changelog entry filename is dated 2026-09-04 (placeholder). Re-date at merge as the PR body already notes.

Every claim in the migration guide, API-ref descriptions, and SIP Trunking page now matches the platform code at PR #3516's tip.

Per the rework owner's direction, the deprecation list and window are
now stated rather than left open: a Telephony section on the Deprecation
Notices page enumerates every deprecated endpoint, request field, and
response shape with its migration target; the migration guide gains an
'after the window' removal list and names 45 days from release (concrete
sunset date to be published at release); the import-phone-number OpenAPI
description and the changelog entry say the same.
@abhishekmishragithub

Copy link
Copy Markdown
Collaborator Author

Added per review direction: the deprecations are now communicated explicitly. A Telephony section on the Deprecation Notices page enumerates every deprecated endpoint, request field, and response shape with its migration target; the migration guide gains an after-the-window removal list; and the 45-day window is stated on the notices page, the migration guide, the import-phone-number API description, and the changelog entry (concrete sunset date to be published at release). One note for the platform side: the code currently sends only Deprecation: true with no Sunset header. Once the release date is fixed, adding Sunset would make the notices page's machine-readable claim fully true.

@abhishekmishragithub
abhishekmishragithub marked this pull request as ready for review September 5, 2026 04:55
@crim-app

crim-app Bot commented Sep 5, 2026

Copy link
Copy Markdown

crim doesn't review pull requests automatically here.

Comment crim review on this pull request whenever you want a review.

@abhishekmishragithub

Copy link
Copy Markdown
Collaborator Author

crim review

@crim-app

crim-app Bot commented Sep 5, 2026

Copy link
Copy Markdown

crim is reviewing this pull request. Findings will be posted shortly.

"500":
$ref: "#/components/responses/InternalServerErrorResponse"

/sip-trunk/inbound:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low: The new SIP-trunk / agent-answer / transfer-source paths declare x-fern-sdk-group-name/x-fern-sdk-method-name (so SDK methods are generated) but define no request/response body schemas beyond a description string on the responses.

Unlike the surrounding endpoints in this spec, the GET list endpoints have no 200 response schema and the mutating endpoints only describe the success body in prose. Generated SDK methods will return untyped/unknown bodies and the rendered API reference will show no response shape. Confidence: low on whether this is intentional for a docs-first stub.

@crim-app crim-app Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment

A thorough, well-written documentation rework that introduces SIP trunks as first-class API resources, splits "who answers" from "what a call dials from," and provides migration/changelog/deprecation coverage; internal slugs, tags, and #operation/ anchors all check out. The main gap is that the developer-guide (SDK) pages were left untouched and still teach only the now-deprecated fields.

What this PR does

Adds OpenAPI paths for inbound/outbound SIP trunks, agent answer sources, and transfer caller ID; deprecates import-phone-number, fromProductId, phoneNumberIds, telephonyProductId/allowInboundCall; introduces fromNumber/fromNumbers; and adds a telephony migration guide, a changelog entry, deprecation-notice rows, and updates the SIP-trunking / phone-numbers / campaigns platform docs.

Findings

  • Medium: fern/products/atoms/pages/dev/build/phone-calling/outbound-calls.mdx:45 — developer-guide pages still document the deprecated telephony model exclusively, inconsistent with this rework (also creating-campaigns.mdx, outbound-setup.mdx, buying-numbers.mdx, build-with-coding-agent.mdx).
  • Low: fern/apis/atoms/openapi/openapi.yaml:4858 — new SIP-trunk/answer endpoints declare x-fern-sdk methods but no request/response body schemas, unlike surrounding endpoints.

Findings outside the diff

These concern lines GitHub won't allow inline comments on (unchanged or outside this PR's diff).

  • fern/products/atoms/pages/dev/build/phone-calling/outbound-calls.mdx:45Medium: Developer-guide pages still teach the deprecated telephony model exclusively, so they now contradict this rework.

    This PR deprecates fromProductId/phoneNumberIds/from_product_id and makes fromNumber/fromNumbers the recommended surface, and the migration guide even calls campaigns "the one change that forces API integrators to ship code." Yet outbound-calls.mdx (this line), creating-campaigns.mdx (phone_ids/phoneNumberIds), phone-numbers/outbound-setup.mdx, phone-numbers/buying-numbers.mdx, and build-with-coding-agent.mdx still present the deprecated fields as the only/primary path with no deprecation note or pointer to fromNumber/fromNumbers. Readers following the developer guide will build against fields scheduled for removal. Consider updating these pages (or at least adding a deprecation callout linking to the telephony migration guide) as part of this change.

@crim-app

crim-app Bot commented Sep 5, 2026

Copy link
Copy Markdown

Hold off on merging

The trunks got promoted to first-class resources, but the developer guide didn't get the memo and is still cheerfully handing out from_product_id like it's not on the 45-day countdown. Sync the SDK pages before this ships.

An agent answers on any number of sources; each source is answered by
exactly one agent. Was implied by the conflict rule, now said outright
on the SIP Trunking setup step and in the migration guide, with the
transfers-refused-without-a-transfer-source consequence beside it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant