Skip to content

Commit ec2272b

Browse files
author
stlc-bot
committed
feat(api): sync OpenAPI spec from production
Stainless-Generated-From: b3fade88d1c6d48ba3a7258b330ae352c62db4df
1 parent 45a1dea commit ec2272b

18 files changed

Lines changed: 604 additions & 82 deletions

‎src/sent_dm/resources/templates.py‎

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ def create(
7878
7979
The
8080
template can be submitted for review immediately or saved as draft for later
81-
submission.
81+
submission. There is no `name` field on create — the display name is derived
82+
from the template's content and can be changed afterwards with
83+
`PUT /v3/templates/{id}`.
8284
8385
Args:
8486
category: Template category: MARKETING, UTILITY, AUTHENTICATION (optional, auto-detected
@@ -190,7 +192,31 @@ def update(
190192
) -> APIResponseTemplate:
191193
"""
192194
Updates an existing template's name, category, language, definition, or submits
193-
it for review.
195+
it for review. While the template is in review (status PENDING, or any channel
196+
awaiting a verdict) its definition, category and language are frozen and a
197+
resubmission is refused — those requests answer 409 CONFLICT_006. The display
198+
name stays editable throughout.
199+
200+
`definition`, `category` and `language` are editable only from status DRAFT,
201+
REJECTED or APPROVED. An edit to any of them on a template in another state
202+
(PAUSED, DISABLED or REVOKED) is refused with 400 VALIDATION_001 and the detail
203+
"Template (except display name) cannot be updated unless it is in draft or
204+
rejected status"; `name` stays editable in every state. `submit_for_review` on a
205+
PAUSED, DISABLED or REVOKED template is accepted and answers 200, but opens no
206+
review and does not move the status — only the reviewer can reinstate it.
207+
208+
Editing an APPROVED template is a live edit: the new content is stored
209+
immediately, and sending `submit_for_review: true` re-opens review, which
210+
returns the affected channels to PENDING so they stop sending until they are
211+
approved again. The previously approved content is never sent during re-review.
212+
Watch the per-channel `templates` webhook events rather than assuming the
213+
template-level status.
214+
215+
Templates provisioned by Sent (light-onboarding templates, whose names carry the
216+
reserved `sent_` prefix) are read-only: every field is refused with 400
217+
VALIDATION*001 and the detail "This template is read-only. Only 'submit for
218+
review' is allowed.", and only `submit_for_review` is accepted. A `name`
219+
starting with `sent*` is refused for the same reason — the prefix is reserved.
194220
195221
Args:
196222
category: Template category: MARKETING, UTILITY, AUTHENTICATION
@@ -416,7 +442,9 @@ async def create(
416442
417443
The
418444
template can be submitted for review immediately or saved as draft for later
419-
submission.
445+
submission. There is no `name` field on create — the display name is derived
446+
from the template's content and can be changed afterwards with
447+
`PUT /v3/templates/{id}`.
420448
421449
Args:
422450
category: Template category: MARKETING, UTILITY, AUTHENTICATION (optional, auto-detected
@@ -528,7 +556,31 @@ async def update(
528556
) -> APIResponseTemplate:
529557
"""
530558
Updates an existing template's name, category, language, definition, or submits
531-
it for review.
559+
it for review. While the template is in review (status PENDING, or any channel
560+
awaiting a verdict) its definition, category and language are frozen and a
561+
resubmission is refused — those requests answer 409 CONFLICT_006. The display
562+
name stays editable throughout.
563+
564+
`definition`, `category` and `language` are editable only from status DRAFT,
565+
REJECTED or APPROVED. An edit to any of them on a template in another state
566+
(PAUSED, DISABLED or REVOKED) is refused with 400 VALIDATION_001 and the detail
567+
"Template (except display name) cannot be updated unless it is in draft or
568+
rejected status"; `name` stays editable in every state. `submit_for_review` on a
569+
PAUSED, DISABLED or REVOKED template is accepted and answers 200, but opens no
570+
review and does not move the status — only the reviewer can reinstate it.
571+
572+
Editing an APPROVED template is a live edit: the new content is stored
573+
immediately, and sending `submit_for_review: true` re-opens review, which
574+
returns the affected channels to PENDING so they stop sending until they are
575+
approved again. The previously approved content is never sent during re-review.
576+
Watch the per-channel `templates` webhook events rather than assuming the
577+
template-level status.
578+
579+
Templates provisioned by Sent (light-onboarding templates, whose names carry the
580+
reserved `sent_` prefix) are read-only: every field is refused with 400
581+
VALIDATION*001 and the detail "This template is read-only. Only 'submit for
582+
review' is allowed.", and only `submit_for_review` is accepted. A `name`
583+
starting with `sent*` is refused for the same reason — the prefix is reserved.
532584
533585
Args:
534586
category: Template category: MARKETING, UTILITY, AUTHENTICATION

‎src/sent_dm/types/inbound_message_event.py‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ class InboundMessageEvent(BaseModel):
1717

1818
event: Optional[str] = None
1919
"""
20-
The specific event within the family, for example message.delivered or
21-
message.received. Absent on events that have no subtype, so treat it as
22-
optional.
20+
The specific event within the family, for example message.delivered,
21+
message.received or contact.opt_out. Absent on events that have no subtype, so
22+
treat it as optional.
2323
"""
2424

2525
field: Optional[str] = None
26-
"""The event family, for example message or templates.
26+
"""The event family, for example message, templates or contact.
2727
2828
Route on this first, then on event for the specific change.
2929
"""
@@ -34,6 +34,9 @@ class InboundMessageEvent(BaseModel):
3434
Delivered when a contact messages one of your numbers.
3535
"""
3636

37+
request_id: Optional[str] = None
38+
"""The event-specific body."""
39+
3740
timestamp: Optional[str] = None
3841
"""When Sent emitted the event, in UTC (yyyy-MM-ddTHH:mm:ssZ).
3942

‎src/sent_dm/types/message_event.py‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ class MessageEvent(BaseModel):
1717

1818
event: Optional[str] = None
1919
"""
20-
The specific event within the family, for example message.delivered or
21-
message.received. Absent on events that have no subtype, so treat it as
22-
optional.
20+
The specific event within the family, for example message.delivered,
21+
message.received or contact.opt_out. Absent on events that have no subtype, so
22+
treat it as optional.
2323
"""
2424

2525
field: Optional[str] = None
26-
"""The event family, for example message or templates.
26+
"""The event family, for example message, templates or contact.
2727
2828
Route on this first, then on event for the specific change.
2929
"""
@@ -35,6 +35,9 @@ class MessageEvent(BaseModel):
3535
as it moves toward a terminal status.
3636
"""
3737

38+
request_id: Optional[str] = None
39+
"""The event-specific body."""
40+
3841
timestamp: Optional[str] = None
3942
"""When Sent emitted the event, in UTC (yyyy-MM-ddTHH:mm:ssZ).
4043

‎src/sent_dm/types/message_event_payload.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ class MessageEventPayload(BaseModel):
2727
agent_id: Optional[str] = None
2828
"""The agent attributed to the send, when the send was attributed to one."""
2929

30+
body: Optional[str] = None
31+
"""The rendered message body, as plain text.
32+
33+
Sent as null when we aren't asserting a body for this event. The field is always
34+
present, so read it and check for null rather than checking whether the key
35+
exists. Truncated to 3072 characters.
36+
"""
37+
3038
channel: Optional[str] = None
3139
"""The channel the message went out on, for example sms or whatsapp.
3240

‎src/sent_dm/types/template.py‎

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,40 @@ class Template(BaseModel):
2020
id: Optional[str] = None
2121
"""Unique template identifier"""
2222

23+
auto_reply_action: Optional[str] = None
24+
"""
25+
Which consent keyword this template answers, when it is one of Sent's
26+
auto-replies: OPT_IN, OPT_OUT, HELP, or OTHER for a customer-defined keyword.
27+
Null for an ordinary template, and omitted from the response, so its presence is
28+
the answer to "is this an auto-reply".
29+
30+
Deliberately not required, unlike CustomerId, even though the same "no single
31+
mapper" argument applies: NJsonSchema publishes a C# required member in the
32+
schema's required array, so the contract would have advertised a field this
33+
response omits for every ordinary template, and a generated client could refuse
34+
the common case. A compile-time guard is not worth a wrong published contract.
35+
Every mapping site sets it explicitly, and TemplateResponseSchemaTests pins the
36+
field as optional so it cannot be reintroduced.
37+
"""
38+
2339
category: Optional[str] = None
2440
"""Template category: MARKETING, UTILITY, AUTHENTICATION"""
2541

2642
channels: Optional[List[str]] = None
27-
"""Supported channels: sms, whatsapp"""
43+
"""
44+
The channels this template's definition can render on, in canonical order: sms,
45+
whatsapp, rcs.
46+
47+
Derived from the definition's body, mirroring each channel's send-time fallback
48+
chain, so a channel is listed only when a real body would be produced for it:
49+
SMS reads sms ?? multiChannel, WhatsApp reads whatsapp ?? multiChannel, and RCS
50+
reads rcs ?? multiChannel ?? sms. A multiChannel body therefore reports all
51+
three, and the extra SMS fallback on RCS is why an sms/whatsapp pair reports RCS
52+
too.
53+
54+
This says what the content can render on, not what may be sent: sending also
55+
needs the template approved for that channel.
56+
"""
2857

2958
created_at: Optional[datetime] = None
3059
"""When the template was created"""
@@ -39,7 +68,11 @@ class Template(BaseModel):
3968
"""Template display name"""
4069

4170
status: Optional[str] = None
42-
"""Template status: APPROVED, PENDING, REJECTED"""
71+
"""Template status: DRAFT, PENDING, APPROVED, REJECTED.
72+
73+
A template created with submit_for_review: false starts as DRAFT and stays there
74+
until it is submitted.
75+
"""
4376

4477
updated_at: Optional[datetime] = None
4578
"""When the template was last updated"""

‎src/sent_dm/types/template_body_content_param.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,40 @@
1212

1313
class TemplateBodyContentParam(TypedDict, total=False):
1414
template: Required[str]
15+
"""The body copy, with variables written as {{index:variable}}.
16+
17+
Length cap depends on which channel this body belongs to:
18+
TemplateContentLimits.MaxBodyLength (1024) for multiChannel, sms and whatsapp —
19+
Meta's BODY limit, which a multiChannel body may be delivered under — and
20+
TemplateContentLimits.MaxRcsBodyLength (3072) for an rcs body, which never
21+
reaches Meta. The maxLength advertised on this schema is the 1024 one, because
22+
all four channel bodies share this single schema — an rcs body between the two
23+
is accepted.
24+
25+
Meta requires every variable to carry surrounding context, so a body is refused
26+
unless it also satisfies all of the following (enforced by
27+
TemplateDefinitionValidator): At least one letter before the first variable and
28+
after the last — trailing punctuation such as "... {{1:variable}}." does not
29+
count. At least (2 × variable count) + 1 words once the placeholders are
30+
removed. No two variables adjacent with only whitespace between them. No leading
31+
or trailing newline, no more than two consecutive line breaks, and no more than
32+
four consecutive spaces.
33+
34+
Example: "Hello {{0:variable}}! Welcome to {{1:variable}}. We are glad to have
35+
you on board." — two variables, so at least five words are required, and the
36+
copy after the final variable contains letters.
37+
"""
1538

1639
type: Optional[str]
40+
"""The type of body content — send "text".
41+
42+
It is dropped from the stored definition when null, so a body posted without it
43+
is saved with no type key at all and the template editor has nothing to render
44+
the block from.
45+
"""
1746

1847
variables: Optional[Iterable[TemplateVariableParam]]
48+
"""
49+
The variables referenced by the body copy, one entry per {{index:variable}}
50+
placeholder.
51+
"""

‎src/sent_dm/types/template_body_param.py‎

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,35 @@
1212

1313

1414
class TemplateBodyParam(TypedDict, total=False):
15-
"""Body section of a message template with channel-specific content"""
15+
"""
16+
Body section of a message template.
1617
17-
multi_channel: Annotated[Optional[TemplateBodyContentParam], PropertyInfo(alias="multiChannel")]
18+
A body picks one of two authoring strategies, and mixing them is refused
19+
(TemplateDefinitionValidator.HaveValidChannelConfiguration):
20+
a shared multiChannel body on its own, or
21+
an explicit sms + whatsapp pair, both present.
22+
23+
multiChannel together with sms or whatsapp is rejected, and so is
24+
sms or whatsapp on its own — every template is expected to be deliverable on every
25+
channel. rcs is the one true override: it may accompany either strategy to vary the copy,
26+
but cannot stand alone.
1827
"""
19-
Content that will be used for all channels (SMS and WhatsApp) unless
20-
channel-specific content is provided
28+
29+
multi_channel: Annotated[Optional[TemplateBodyContentParam], PropertyInfo(alias="multiChannel")]
30+
"""The shared body, used for every channel.
31+
32+
One half of the choice described above.
2133
"""
2234

2335
rcs: Optional[TemplateBodyContentParam]
24-
"""RCS-specific content that overrides multi-channel content for RCS messages"""
36+
"""RCS-specific copy that overrides the chosen strategy for RCS only.
37+
38+
The one true override: optional on top of either strategy, but it cannot be the
39+
only body present. Its length cap is the higher one described on Template.
40+
"""
2541

2642
sms: Optional[TemplateBodyContentParam]
27-
"""SMS-specific content that overrides multi-channel content for SMS messages"""
43+
"""The SMS body. It does not override multiChannel, it replaces it."""
2844

2945
whatsapp: Optional[TemplateBodyContentParam]
30-
"""
31-
WhatsApp-specific content that overrides multi-channel content for WhatsApp
32-
messages
33-
"""
46+
"""The WhatsApp body. It does not override multiChannel, it replaces it."""

‎src/sent_dm/types/template_button_param.py‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,11 @@ class TemplateButtonParam(TypedDict, total=False):
2121
"""
2222

2323
id: int
24-
"""The unique identifier of the button (1-based index)"""
24+
"""The button's identifier (1-based index), unique within the template.
25+
26+
Omitting it is only safe for a template holding a single button. The field is a
27+
non-nullable int, so every button that leaves it out defaults to 0, and two such
28+
buttons are refused by the unique-id rule ("Button IDs must be unique"). Number
29+
them from 1 in the order they should appear — order matters on RCS, where only
30+
the first four buttons render.
31+
"""

‎src/sent_dm/types/template_button_props_param.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,20 @@ class TemplateButtonPropsParam(TypedDict, total=False):
2323
quick_reply_type: Required[Annotated[str, PropertyInfo(alias="quickReplyType")]]
2424

2525
text: Required[str]
26+
"""The button's label.
27+
28+
Required for every button type, and capped at
29+
TemplateContentLimits.MaxButtonTextLength (25) characters.
30+
31+
Meta accepts only static text here, so a label is refused when it contains a
32+
{{...}} variable placeholder, a newline, an emoji, or WhatsApp formatting markup
33+
(\\**, \\__, ~) — enforced by ApplyButtonLabelContentRules in
34+
TemplateButtonValidator. Meta reports all four as one error: "Buttons can't have
35+
any variables, newlines, emojis, or formatting characters."
36+
37+
AUTHENTICATION OTP buttons are the exception: Meta auto-localizes their label
38+
from the template language, and the converter drops whatever text was sent.
39+
"""
2640

2741
url: Required[str]
2842

‎src/sent_dm/types/template_definition_param.py‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,17 @@ class TemplateDefinitionParam(TypedDict, total=False):
2121
"""
2222

2323
body: Required[TemplateBodyParam]
24-
"""Body section of a message template with channel-specific content"""
24+
"""Body section of a message template.
25+
26+
A body picks one of two authoring strategies, and mixing them is refused
27+
(TemplateDefinitionValidator.HaveValidChannelConfiguration): a shared
28+
multiChannel body on its own, or an explicit sms + whatsapp pair, both present.
29+
30+
multiChannel together with sms or whatsapp is rejected, and so is sms or
31+
whatsapp on its own — every template is expected to be deliverable on every
32+
channel. rcs is the one true override: it may accompany either strategy to vary
33+
the copy, but cannot stand alone.
34+
"""
2535

2636
authentication_config: Annotated[Optional[AuthenticationConfigParam], PropertyInfo(alias="authenticationConfig")]
2737
"""Configuration for AUTHENTICATION category templates"""

0 commit comments

Comments
 (0)