Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
527 changes: 527 additions & 0 deletions fern/apis/signalwire-rest/openapi.yaml

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions fern/products/apis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ navigation:
- section: Messaging
skip-slug: true
contents:
- messages:
- section: Webhooks
slug: webhooks
contents:
- subpackage_messages.message_status_callback
- section: Campaign Registry
contents:
- section: Brands
Expand Down
75 changes: 75 additions & 0 deletions fern/products/apis/pages/core/error-codes.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ When using SignalWire REST APIs, some errors will include error codes. Below, yo
- Unable to locate a billing route.
</ParamField>

<ParamField path="body_must_be_empty" type="Error" toc={true}>

- The `body` value must be an empty string to redact the message.
</ParamField>

<ParamField path="body_or_media_required" type="Error" toc={true}>

- A `body` must be included if there is no `media`.
</ParamField>

<ParamField path="brand_not_unique" type="Error" toc={true}>

- The CSP Brand Reference must be unique within the Space
Expand Down Expand Up @@ -97,6 +107,11 @@ When using SignalWire REST APIs, some errors will include error codes. Below, yo
- The request cannot be processed right now.
</ParamField>

<ParamField path="cannot_redact_in_progress_message" type="Error" toc={true}>

- Cannot redact a message that is in progress. Messages in `queued` or `initiated` state cannot be redacted; only messages in terminal states (`delivered`, `undelivered`, `failed`) are eligible.
</ParamField>

<ParamField path="cannot_redirect_not_in_progress" type="Error" toc={true}>

- Call is not in-progress. Cannot redirect.
Expand Down Expand Up @@ -132,6 +147,11 @@ When using SignalWire REST APIs, some errors will include error codes. Below, yo
- The number of channels exceeds the allowed limit.
</ParamField>

<ParamField path="character_limit_exceeded" type="Error" toc={true}>

- The message `body` exceeds the provider-specific character limit.
</ParamField>

<ParamField path="chat_token_invalid_for_session" type="Error" toc={true}>

- The provided token is invalid for the provided room session.
Expand Down Expand Up @@ -222,6 +242,11 @@ When using SignalWire REST APIs, some errors will include error codes. Below, yo
- The provided from_fabric_address_id does not match an address for the provided subscriber.
</ParamField>

<ParamField path="inactive_campaign" type="Error" toc={true}>

- The `from` number must belong to an active campaign.
</ParamField>

<ParamField path="incompatible_parameters" type="Error" toc={true}>

- The parameters that were input cannot be specified together.
Expand Down Expand Up @@ -252,6 +277,11 @@ When using SignalWire REST APIs, some errors will include error codes. Below, yo
- This account has an insufficient balance.
</ParamField>

<ParamField path="integration_test_verified_caller_required" type="Error" toc={true}>

- The `to` number must be the verified caller ID for the trial campaign.
</ParamField>

<ParamField path="invalid_addresses_for_transfer_skill" type="Error" toc={true}>

- Addresses must contain non-empty values for name and destination
Expand Down Expand Up @@ -332,6 +362,11 @@ When using SignalWire REST APIs, some errors will include error codes. Below, yo
- Provided chunking parameters are not valid for sliding chunking. Allowed parameters are chunk_size and overlap_size.
</ParamField>

<ParamField path="invalid_from_number" type="Error" toc={true}>

- The `from` value must be a valid purchased phone number on your SignalWire project. Verified caller IDs cannot be used for outbound messaging.
</ParamField>

<ParamField path="invalid_group_configuration" type="Error" toc={true}>

- Fabric addresses must form a valid conversation group (a single room or two non-room subscribers) and group ID must start with 'sw_'.
Expand Down Expand Up @@ -472,6 +507,11 @@ When using SignalWire REST APIs, some errors will include error codes. Below, yo
- has exceeded the maximum number of queued messages/calls for this number
</ParamField>

<ParamField path="media_limit_exceeded" type="Error" toc={true}>

- Exceeded the maximum number of media items allowed per message (currently 8).
</ParamField>

<ParamField path="media_size_exceeds_limit" type="Error" toc={true}>

- The media size exceeds the allowed limit.
Expand All @@ -482,6 +522,11 @@ When using SignalWire REST APIs, some errors will include error codes. Below, yo
- A media URL is required.
</ParamField>

<ParamField path="message_backlog_limit_exceeded" type="Error" toc={true}>

- The project's messaging backlog limit has been exceeded. Wait before sending more messages.
</ParamField>

<ParamField path="message_body_required" type="Error" toc={true}>

- The message body is required.
Expand All @@ -497,6 +542,11 @@ When using SignalWire REST APIs, some errors will include error codes. Below, yo
- Messaging provider is invalid.
</ParamField>

<ParamField path="missing_messaging_capability" type="Error" toc={true}>

- The `from` number is not capable of sending the determined message kind (SMS or MMS).
</ParamField>

<ParamField path="missing_required_parameter" type="Error" toc={true}>

- The passed value is blank or empty, but it is required.
Expand Down Expand Up @@ -652,6 +702,11 @@ When using SignalWire REST APIs, some errors will include error codes. Below, yo
- Unable to locate a route to the destination number. Please check geographic permissions and the destination number, and contact Support if issues persist.
</ParamField>

<ParamField path="not_routeable" type="Error" toc={true}>

- The `to` number is not routeable.
</ParamField>

<ParamField path="not_valid_for_caller_id" type="Error" toc={true}>

- The value is not valid (must be an E.164 number, caller ID string or SIP URI)
Expand Down Expand Up @@ -717,6 +772,11 @@ When using SignalWire REST APIs, some errors will include error codes. Below, yo
- The page token is required when the page number is passed and greater than 0.
</ParamField>

<ParamField path="pft_daily_limit_exceeded" type="Error" toc={true}>

- The Platform Free Trial campaign daily message cap of 200 messages has been reached.
</ParamField>

<ParamField path="provided_id_is_unrecognized" type="Error" toc={true}>

- The provided ID is unrecognized. Verify that the ID points to a valid resource belonging to the current project.
Expand Down Expand Up @@ -787,11 +847,21 @@ When using SignalWire REST APIs, some errors will include error codes. Below, yo
- The provided send_as number is missing or invalid.
</ParamField>

<ParamField path="sending_restricted_to_verified" type="Error" toc={true}>

- Sending is restricted to verified numbers for this account.
</ParamField>

<ParamField path="sip_gateway_uri_must_be_external" type="Error" toc={true}>

- Only external SIP entities are supported for the SIP Gateway. Do not provide a SignalWire-managed SIP entity.
</ParamField>

<ParamField path="sole_proprietor_daily_limit_exceeded" type="Error" toc={true}>

- The sole proprietor campaign daily message cap of 1,000 messages has been reached.
</ParamField>

<ParamField path="sort_order_validations" type="Error" toc={true}>

- Sort order cannot be specified without sort by.
Expand Down Expand Up @@ -925,4 +995,9 @@ When using SignalWire REST APIs, some errors will include error codes. Below, yo
<ParamField path="verification_request_too_soon" type="Error" toc={true}>

- This number has received a verification call too recently. Please wait a minute before requesting another call.
</ParamField>

<ParamField path="verified_caller_id_not_permitted" type="Error" toc={true}>

- Verified caller IDs are not permitted for outbound messaging.
</ParamField>
6 changes: 6 additions & 0 deletions fern/products/platform/pages/messaging/sms/overview.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,12 @@ You can see this list in full detail [here](/docs/platform/messaging/sms-best-pr

</Accordion>

<Accordion title="How do I redact the body of a sent message?">

For compliance, privacy, or moderation use cases, you can redact the body of a previously sent message by sending a `PATCH` request to `/api/messaging/messages/:message_id` with a `body` of `""`. Only messages in terminal states (`delivered`, `undelivered`, `failed`) are eligible; messages still in progress (`queued` or `initiated`) cannot be redacted. Once redacted, the original body is overwritten and cannot be recovered. See the [Redact a message](/docs/apis/rest/signalwire-rest/messages/update-message) API reference for full details.

</Accordion>

<Accordion title="What are Carrier Passthrough Fees?">

Carrier Passthrough Fees, also known as Network Access Fees (NAFs),
Expand Down
36 changes: 26 additions & 10 deletions specs/_shared/webhook/decorator.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { setExtension, getExtensions, getTagsMetadata } from "@typespec/openapi";
import { serializeValueAsJson } from "@typespec/compiler";

// ── String helpers ───────────────────────────────────────────────────

Expand All @@ -24,9 +25,22 @@ function getDecoratorArg(type, decoratorName) {
return undefined;
}

// Returns the TypeSpec Value (not jsValue) for a decorator argument. Use for
// values that need to be JSON-serialized via serializeValueAsJson — jsValue
// for complex args (enum members, scalar constructors like utcDateTime.fromISO)
// contains cyclic TypeSpec objects that crash the YAML emitter.
function getDecoratorArgValue(type, decoratorName) {
for (const dec of type.decorators || []) {
if (dec.definition?.name === decoratorName) {
return dec.args?.[0]?.value;
}
}
return undefined;
}

// ── Schema serialization ─────────────────────────────────────────────

function modelToJsonSchema(model, seen) {
function modelToJsonSchema(model, seen, program) {
if (!seen) seen = new Set();
if (seen.has(model)) return { type: "object" };
seen.add(model);
Expand All @@ -35,11 +49,13 @@ function modelToJsonSchema(model, seen) {
const required = [];

for (const [name, prop] of model.properties) {
const schema = typeToSchema(prop.type, seen);
const schema = typeToSchema(prop.type, seen, program);
const doc = getDecoratorArg(prop, "@doc");
if (doc) schema.description = doc;
const example = getDecoratorArg(prop, "@example");
if (example !== undefined) schema.example = example;
const exampleValue = getDecoratorArgValue(prop, "@example");
if (exampleValue !== undefined && program) {
schema.example = serializeValueAsJson(program, exampleValue, prop.type);
}
properties[name] = schema;
if (!prop.optional) required.push(name);
}
Expand All @@ -53,7 +69,7 @@ function modelToJsonSchema(model, seen) {
return result;
}

function typeToSchema(type, seen) {
function typeToSchema(type, seen, program) {
if (!seen) seen = new Set();

switch (type.kind) {
Expand All @@ -64,7 +80,7 @@ function typeToSchema(type, seen) {
if (["int32", "int64", "integer", "uint32", "uint64"].includes(n)) return { type: "integer" };
if (["float", "float32", "float64", "numeric"].includes(n)) return { type: "number" };
if (n === "utcDateTime") return { type: "string", format: "date-time" };
if (type.baseScalar) return typeToSchema(type.baseScalar, seen);
if (type.baseScalar) return typeToSchema(type.baseScalar, seen, program);
return { type: "string" };
}
case "Intrinsic":
Expand All @@ -79,7 +95,7 @@ function typeToSchema(type, seen) {
return nullVariant ? { oneOf: [schema, { type: "null" }] } : schema;
}

const schemas = variants.map((v) => typeToSchema(v.type, seen));
const schemas = variants.map((v) => typeToSchema(v.type, seen, program));
if (schemas.length === 2 && nullVariant) {
const s = schemas.find((s) => s.type !== "null");
if (s) return { ...s, nullable: true };
Expand All @@ -94,9 +110,9 @@ function typeToSchema(type, seen) {
return { type: "boolean", enum: [type.value] };
case "Model": {
if (type.indexer?.key?.name === "integer") {
return { type: "array", items: type.indexer.value ? typeToSchema(type.indexer.value, seen) : {} };
return { type: "array", items: type.indexer.value ? typeToSchema(type.indexer.value, seen, program) : {} };
}
return modelToJsonSchema(type, seen);
return modelToJsonSchema(type, seen, program);
}
case "Enum":
return { type: "string", enum: [...type.members.values()].map((m) => m.value ?? m.name) };
Expand Down Expand Up @@ -197,7 +213,7 @@ export function $webhook(context, target, name, payload, tag, operationId) {
summary: getDecoratorArg(payload, "@summary") ?? toHumanReadable(name),
requestBody: {
required: true,
content: { "application/json": { schema: modelToJsonSchema(payload) } },
content: { "application/json": { schema: modelToJsonSchema(payload, undefined, context.program) } },
},
responses: { 200: { description: "Webhook received" } },
};
Expand Down
1 change: 1 addition & 0 deletions specs/signalwire-rest/main.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ using TypeSpec.OpenAPI;
// Voice API
@tagMetadata(VOICE_LOGS_TAG, VOICE_LOGS_TAG_METADATA)
// Message API
@tagMetadata(MESSAGES_TAG, MESSAGES_TAG_METADATA)
@tagMetadata(MESSAGE_LOGS_TAG, MESSAGE_LOGS_TAG_METADATA)
// Fax API
@tagMetadata(FAX_LOGS_TAG, FAX_LOGS_TAG_METADATA)
Expand Down
1 change: 1 addition & 0 deletions specs/signalwire-rest/message-api/main.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import "@typespec/http";
import "@typespec/openapi";
import "../types";
import "./logs";
import "./messages";
import "../_globally_shared/const.tsp";
import "../../_shared/auth.tsp";
import "./tags.tsp";
Expand Down
58 changes: 58 additions & 0 deletions specs/signalwire-rest/message-api/messages/main.tsp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import "@typespec/http";
import "@typespec/openapi";
import "../../types/status-codes/main.tsp";
import "../tags.tsp";
import "./models/core.tsp";
import "./models/requests.tsp";
import "./models/responses.tsp";
import "./models/errors.tsp";
import "./models/webhooks.tsp";
import "../../../_shared/alias/token-permissions.tsp";
import "../../../_shared/webhook/decorator.tsp";

using TypeSpec.Http;
using TypeSpec.OpenAPI;
using Types.StatusCodes;

@webhook("messageStatusCallback", MessageStatusCallbackPayload, MESSAGES_TAG)
@route("/messages")
namespace SignalWireAPI.Message.Messages {
@tag(MESSAGES_TAG)
@friendlyName("Messages")
interface Messages {
@operationId("create_message")
@summary("Send a message")
@doc("""
Create and queue an outbound SMS or MMS message for delivery. The system determines whether the message is SMS or MMS based on the presence of `media` or the `send_as_mms` flag. The `from` number must be a purchased SignalWire phone number on the authenticated project.

${tokenPermissions<"_Messaging_">}
""")
@post
create(...CreateMessageRequest):
| CreateMessageResponse
| StatusCode400
| StatusCode401
| MessagesCreateStatusCode422
| StatusCode500;

@operationId("update_message")
@summary("Redact a message")
@doc("""
Redact the body of a previously sent message. This endpoint clears the message body for compliance, privacy, or moderation purposes — it does not support arbitrary updates to message attributes. The only accepted value for `body` is an empty string (`""`); any other value is rejected.

Messages that are still in progress (`queued` or `initiated`) cannot be redacted. Messages in terminal states such as `delivered`, `undelivered`, or `failed` are eligible. Once redacted, the original body is overwritten and cannot be recovered.

The `:message_id` path parameter is the message segment ID — the same ID returned by the create endpoint and shown in `/api/messaging/logs`.

${tokenPermissions<"_Messaging_">}
""")
@patch(#{ implicitOptionality: false })
update(...MessagePathID, ...UpdateMessageRequest):
| UpdateMessageResponse
| StatusCode400
| StatusCode401
| StatusCode404
| MessagesUpdateStatusCode422
| StatusCode500;
}
}
Loading
Loading