SWML messaging schema (TypeSpec)#315
Merged
Merged
Conversation
Contributor
This was referenced May 12, 2026
…saging
Splits the SWML TypeSpec schema into two sibling namespaces:
* `SWML.Calling` — the existing schema, relocated under `specs/swml/calling/`.
Every method TSP file moves from `specs/swml/Methods/<method>/` to
`specs/swml/calling/Methods/<method>/` (139 renames). Behavior unchanged.
* `SWML.Messaging` — new namespace under `specs/swml/messaging/` covering
inbound SMS/MMS handlers. Methods: execute, goto, label, receive, reply,
request, return, switch, transfer. `SWMLObject` is a `@oneOf` union of a
full document (top-level `sections`) and a light document (single `reply`
or `receive`, including the empty `{}` shorthand).
`specs/package.json` replaces the single `build:swml` script with
`build:swml-calling` and `build:swml-messaging` (both wired into
`build:schema`). The legacy `specs/swml/tsp-output/.../SWMLObject.json` file
is kept in place — consumers may have hardcoded that URL path; the new
calling-only schema lives at `specs/swml/calling/tsp-output/...`.
REST consumers that embed SWML documents are updated to use the new
namespaced types:
* `signalwire-rest/calling-api/calls/models/{requests,examples}.tsp` —
`SWML.Calling.SWMLObject` for call-control SWML payloads.
* `signalwire-rest/fabric-api/swml-scripts/models/{core,requests}.tsp` —
splits the `contents` field across `SWML.Calling.SWMLObject` and
`SWML.Messaging.SWMLObject` based on script type.
* `signalwire-rest/fabric-api/ai-agent/models/{core,ai/main}.tsp` — pulls
AI method types from `SWML.Calling`.
Adds the second SWML inbound webhook to the SWML Webhooks namespace:
`@webhook("inboundMessageWebhook", InboundMessageWebhookPayload, ...)` with
companion `InboundMessageMediaItem`, `InboundMessageContext`, and
`InboundMessageWebhookPayload` models in
`signalwire-rest/fabric-api/swml-webhook/models/webhooks.tsp`. The Calling
sibling `@webhook("inboundCallWebhook", ...)` already shipped with the
decorator PR; this PR completes the pair. `fern/products/apis/apis.yml`
gains the matching nav entry.
Drive-along refactors that landed naturally with this work:
* `uuid` and `jwt` scalars moved from
`specs/signalwire-rest/types/scalar-types/main.tsp` up to
`specs/_shared/types/main.tsp` so compatibility-api can reach them.
`@format("uuid") + string` pairs across ~135 REST + compat spec files
collapse to the bare `uuid` scalar (TypeSpec carries the format).
* `UrlMethodType` (which actually only describes request URLs) renames to
`RequestUrlMethodType`; `cxml-webhooks/models/requests.tsp` propagates
the new name.
* `signalwire-rest/fabric-api/_shared/const.tsp` adds tuple-literal
`SWML_CONTENTS_EXAMPLE` and `SWML_MESSAGING_CONTENTS_EXAMPLE` constants
for use in `@example(...)` decorators.
* `signalwire-rest/message-api/messages/models/webhooks.tsp` clarifies in
prose that the message status callback shape is also fired by SWML
messaging `reply.status_url`.
Targets the SWML messaging integration branch (`Devon/swml-messaging`).
The matching SWML reference MDX docs land as a sibling PR.
Verified by `yarn build:all` from `specs/`: TypeSpec 1.11.0 compiles
signalwire-rest, compatibility-api, swml-calling, and swml-messaging
without errors. Generated `fern/apis/signalwire-rest/openapi.yaml`
exposes both `inboundCallWebhook` and `inboundMessageWebhook` under
`webhooks:`.
ced33ae to
6a993bd
Compare
InboundMessageWebhookPayload - Add optional `vars` field carrying propagated runtime variables on transfer-driven fetches; absent on the initial inbound fetch - `body` typed as nullable (provider intakes leave it null on media-only MMS with no carrier-supplied text) - Loosen from/to descriptions to forward-compatible wording - Tighten timestamp/media descriptions Reply method - ReplyInlineSwitch.case and default accept `string | ReplyPlan` so each branch can carry its own body/media/to/from/status_url - Require `body` be non-empty (@minlength(1)) on both ReplyWithBody and ReplyWithMedia, with matching doc text - status_url cross-links to the message status callback payload Request method - Rewrite description with the four request_result values, the soft vs hard failure semantics, and the response variables (request_response, request_response_code, request_response_body) Transfer method - Description now references the inbound message webhook payload and explains what message/params/vars carry on the transfer fetch SwmlScriptCreate/UpdateRequest - script_type optional with per-branch default; structural oneOf still discriminates via the contents schema. Update docs to call out the recommended-but-optional path.
Move the two receive-shape types out of main.tsp into receive/main.tsp so all receive-related definitions live together. SWMLLightDocument stays in main.tsp since it's a document-level union; it still composes LightReceive via the Methods import. Generated JSON Schema is unchanged.
Response side — both fields are emitted as parsed JSON objects by Rails: - CallFlowSerializer / CallFlowVersionSerializer call RelayBins::ParseJsonOrYaml.call(raw_contents).as_json for relayml - flow_data is a jsonb column that the serializer passes through directly Spec previously typed both as `string`, which would reject the real responses. Updated CallFlow (core), CallFlowVersion, CallFlowVersionDeployResponse, and the dead-but-present CallFlowVersionResponse: - relayml: SWML.Calling.SWMLObject - flow_data: opaque object (Record<unknown>) Request side — CallFlowCreateRequest and CallFlowUpdateRequest were missing the relayml and flow_data fields entirely. Rails contracts require both (with create allowing both to be omitted together to get SignalWire defaults; update requires both present along with document_version). Added the missing fields with required/optional flags matching the Rails contracts. Generated REST OpenAPI regenerated.
Devon-White
added a commit
that referenced
this pull request
May 12, 2026
- reply.mdx: inline switch case/default values are now string OR full reply object (body/media/to/from/status_url). Updated the ParamField descriptions and the trailing constraint paragraph, and added a per-case-routing example. - transfer.mdx: payload section now lists the three keys (message/params/vars) and embeds the inboundMessageWebhook payload snippet. Removed the inaccurate 'same shape as the initial fetch' note. - overview.mdx: tighten the reply summary (inline switch branches the reply, not just the body); replace the over-simplified 'request failures are soft' blurb with the precise hard-vs-soft split, surfacing the four request_result values.
Devon-White
added a commit
that referenced
this pull request
May 12, 2026
The Fern URL slug for the message-logs operation is built from its OpenAPI tag "Message Logs" -> kebab "message-logs", not the parent "Logs" nav section (which uses `skip-slug: true`). Updated both occurrences to point at /docs/apis/rest/message-logs/list-message-logs. The inbound-message-webhook links in variables.mdx are left alone; that target page is generated by the schema PR (#315), and the links will resolve once both PRs land on the Devon/swml-messaging integration branch.
The TypeSpec @doc strings emit to JSON Schema descriptions and ultimately into customer-facing surfaces (REST API reference, SDK docs). They should say the same thing as the MDX reference docs and stay focused on purpose and behavior — type/required/default info already lives in the type signature and ParamField attributes. Changes per method (eight files, ~17 description updates): * execute — clarify that completion happens via `return` or end-of-section, and that URLs/inline documents are not accepted in the messaging context (matches the doc intro that already calls this out). * goto — promote `goto.max` description to "section ends without running further steps" (matches Rails behavior in section_executor.rb; the prior "stopping execution" was vague). Add a goto-scoped @doc on the `label` field instead of inheriting Label.label's @doc which describes labels from the label's perspective, not from goto's. * label — note that label names must be unique within the section. * reply — append "reply does not end execution; subsequent steps continue" to the method @doc, swap the Ruby-resource framing on `from` ("PhoneRoute or ShortCode") for user-facing language ("owned by your project and have messaging capability"), and document the inline-switch no-match failure behavior on `default`. * request — drop redundant "Default X" prose from `method`, `timeout`, and `save_variables` @docs (already declared via `= default` on the type), swap "Hashes" → "Objects" on `request.body` for non-Ruby-flavored docs, add the publicly-reachable URL constraint to `request.url`, and add the "map of header name to value" clarification to `request.headers`. * return — note the `return: null` shorthand for returning without a value. * switch — extend the method @doc with use-case framing ("useful for keyword-driven inbound message handling"), document key/value semantics on `case`, and document the no-match failure behavior on `default`. * transfer — note the URL-only constraint in the method @doc, document embedded basic-auth syntax on `dest`, drop redundant "Default `POST`" prose on `method`, and append the runtime semantic on `params` ("available as `params.*` in the transferred document"). Regenerated specs/swml/messaging/tsp-output/.../SWMLObject.json is the mechanical byproduct of these source edits. Verified by `yarn build:swml-messaging` — compiles clean under TypeSpec 1.11.0 with no warnings or errors.
cassieemb
reviewed
May 12, 2026
Contributor
cassieemb
left a comment
There was a problem hiding this comment.
This is looking really good! I left a few questions about areas I'm unsure about
| used_for: "calling" | "messaging"; | ||
|
|
||
| @doc("Primary request url of the SWML Webhook.") | ||
| @doc("Primary URL SignalWire fetches the SWML document from when the webhook fires. The webhook payload depends on `used_for`: for `calling`, see the [SWML inbound call webhook](/docs/apis/rest/swml-webhook/webhooks/inbound-call-webhook); for `messaging`, see the [SWML inbound message webhook](/docs/apis/rest/swml-webhook/webhooks/inbound-message-webhook).") |
Contributor
There was a problem hiding this comment.
Is this true for calling? I tried assigning a SWML webhook as a call handler and I didn't get a posted body to the webhook URL. Prime doesn't handle inbound SWML for calling, so this isn't a pattern I've seen before we added it for messaging. Maybe I tested incorrectly though, and this is something that FS currently handles?
| primary_request_method: RequestUrlMethodType; | ||
|
|
||
| @doc("Fallback request url of the SWML Webhook.") | ||
| @doc("Fallback URL SignalWire fetches the SWML document from if the primary URL fails. Receives the same payload as `primary_request_url` — see the [SWML inbound call webhook](/docs/apis/rest/swml-webhook/webhooks/inbound-call-webhook) or [SWML inbound message webhook](/docs/apis/rest/swml-webhook/webhooks/inbound-message-webhook) depending on `used_for`.") |
Contributor
There was a problem hiding this comment.
Same here - I'm not sure if this is something FS already handles for calling, but Prime does not.
…into Devon/swml-schema
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Splits the SWML TypeSpec schema into
SWML.Calling(the existing schema, relocated underspecs/swml/calling/) andSWML.Messaging(new — coversreply,receive,request,transfer,execute,goto,switch,label,return).REST consumers (
calling-api/calls,fabric-api/swml-scripts,fabric-api/ai-agent) now embedSWML.Calling.SWMLObjectorSWML.Messaging.SWMLObjectwhere appropriate. The SWML inbound message webhook (@webhook("inboundMessageWebhook", ...)) lands on top of the calling sibling that shipped with the decorator PR.Base branch
This PR targets
Devon/swml-messaging— the integration branch — notmain. Its sibling, the SWML reference docs PR #316, also targets the integration branch. When both merge in, the integration → main PR ships them atomically.Drive-along refactors (kept in scope)
UrlMethodType→RequestUrlMethodTyperename (it only ever described request URLs).SWML_CONTENTS_EXAMPLE+SWML_MESSAGING_CONTENTS_EXAMPLEliteral-object constants for@example(...).Verification
```bash
cd specs && yarn build:all
```
TypeSpec 1.11.0 compiles all four projects (signalwire-rest, compatibility-api, swml-calling, swml-messaging) cleanly. The regenerated `fern/apis/signalwire-rest/openapi.yaml` exposes `inboundCallWebhook` and `inboundMessageWebhook` under `webhooks:`.