Skip to content

Commit 13d08bc

Browse files
author
bird-release[bot]
committed
Release v0.20.0 (sdk-python/v0.20.0)
1 parent 4f71d74 commit 13d08bc

20 files changed

Lines changed: 216 additions & 62 deletions

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 0.20.0
4+
5+
- Voice call webhook payloads name the two parties from and to, replacing src_number and dst_number. A handler reading those fields on voice_call.initiated, voice_call.answered or voice_call.ended must rename them; every other field is unchanged.
6+
- WhatsApp and SMS template `language` fields now take a BCP-47 tag (for example `pt-BR`); Meta's underscore form (`pt_BR`) is still accepted as an input alias but is no longer echoed back. WhatsApp template sends can now also address a template by `id`, as an alternative to `slug`: the existing `template` argument takes either, resolving a `wat_`-prefixed value as the id — the same convention it already uses for SMS's `smt_`.
7+
- WhatsApp send: the `to` field now accepts a business-scoped user ID as well as an E.164 phone number, so you can message a WhatsApp user whose phone number you do not have. One-time-passcode templates still require a phone number and return `422 WhatsAppRecipientNotSupportedForTemplate` when sent to a business-scoped user ID.
8+
39
## 0.19.0
410

511
- Add a `datetime` contact property type: an RFC 3339 timestamp with an explicit offset.

examples/onboarding-email.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
# The first send a customer makes from the dashboard's onboarding step. Unlike
2-
# quickstart-email.py this carries the key inline: the dashboard fills it with
3-
# the workspace's real key, so the placeholder is what a reader sees before it
4-
# is substituted, not advice to hardcode a secret.
51
from bird import APIError, Bird
62

73
with Bird(api_key="bk_XXXXXXXXXXXXXXXXXXXXXXXX") as client:

examples/onboarding-sms.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from bird import APIError, Bird
2+
3+
with Bird(api_key="bk_XXXXXXXXXXXXXXXXXXXXXXXX") as client:
4+
try:
5+
message = client.sms.send(
6+
to="+15551234567",
7+
template="bird_otp_verification",
8+
parameters={"code": "493021"},
9+
)
10+
print(message.id, message.status)
11+
except APIError as err:
12+
print("send failed:", err)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from bird import APIError, Bird
2+
3+
with Bird(api_key="bk_XXXXXXXXXXXXXXXXXXXXXXXX") as client:
4+
try:
5+
result = client.verify.verifications.check(
6+
to={"email_address": "user@example.com"},
7+
code="123456",
8+
)
9+
print(result.success)
10+
except APIError as err:
11+
print("could not check the passcode:", err)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from bird import APIError, Bird
2+
3+
with Bird(api_key="bk_XXXXXXXXXXXXXXXXXXXXXXXX") as client:
4+
try:
5+
verification = client.verify.verifications.create(
6+
to={"email_address": "user@example.com"},
7+
)
8+
print(verification.id, verification.status)
9+
except APIError as err:
10+
print("could not start the verification:", err)

examples/onboarding-whatsapp.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from bird import APIError, Bird
2+
3+
with Bird(api_key="bk_XXXXXXXXXXXXXXXXXXXXXXXX") as client:
4+
try:
5+
message = client.whatsapp.send(
6+
to="+15551234567",
7+
template="bird_delivery_update",
8+
components=[
9+
{
10+
"type": "body",
11+
"parameters": [
12+
{"type": "text", "name": "ref", "text": "A1B2C3D4"},
13+
{"type": "text", "name": "date", "text": "10 Jul 2026"},
14+
],
15+
}
16+
],
17+
)
18+
print(message.id, message.status)
19+
except APIError as err:
20+
print("send failed:", err)

src/bird/_generated.py

Lines changed: 113 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2089,8 +2089,9 @@ class SMSTemplateSend1(BaseModel):
20892089
language: Annotated[
20902090
str | None,
20912091
Field(
2092-
description="Language tag (BCP 47, for example `fr` or `pt-BR`) selecting the localized body. Falls back to the closest available language, then English, when the exact tag is not stocked. Omit for English.\n",
2092+
description="Which of the template's localized bodies to send, as a BCP-47 tag. Falls back to the closest available language, then English, when the exact tag is not stocked. Omit for English.\n",
20932093
examples=["fr"],
2094+
max_length=35,
20942095
min_length=2,
20952096
),
20962097
] = None
@@ -2129,8 +2130,9 @@ class SMSTemplateSend2(BaseModel):
21292130
language: Annotated[
21302131
str | None,
21312132
Field(
2132-
description="Language tag (BCP 47, for example `fr` or `pt-BR`) selecting the localized body. Falls back to the closest available language, then English, when the exact tag is not stocked. Omit for English.\n",
2133+
description="Which of the template's localized bodies to send, as a BCP-47 tag. Falls back to the closest available language, then English, when the exact tag is not stocked. Omit for English.\n",
21332134
examples=["fr"],
2135+
max_length=35,
21342136
min_length=2,
21352137
),
21362138
] = None
@@ -2965,9 +2967,10 @@ class WhatsAppMessageTemplate(BaseModel):
29652967
language: Annotated[
29662968
str,
29672969
Field(
2968-
description="The language code of the template variant that was sent (for example `en`).",
2969-
examples=["en"],
2970-
min_length=1,
2970+
description="The canonical BCP-47 tag of the template variant that was sent.",
2971+
examples=["pt-BR"],
2972+
max_length=35,
2973+
min_length=2,
29712974
),
29722975
]
29732976
components: Annotated[
@@ -3105,10 +3108,59 @@ class WhatsAppMessageList(FieldListEnvelope):
31053108
]
31063109

31073110

3108-
class WhatsAppTemplateSend(BaseModel):
3111+
class WhatsAppTemplateSend1(BaseModel):
3112+
model_config = ConfigDict(
3113+
extra="allow",
3114+
)
3115+
id: Annotated[
3116+
str,
3117+
Field(
3118+
description="The template to send, by its id.",
3119+
examples=["wat_01krdgeqcxet5s7t44vh8rt9mg"],
3120+
min_length=1,
3121+
pattern="^wat_[0-9a-hjkmnp-tv-z]{26}$",
3122+
),
3123+
]
3124+
slug: Annotated[
3125+
str | None,
3126+
Field(
3127+
description="The template to send, by its slug (for example `bird_otp`).",
3128+
examples=["bird_otp"],
3129+
max_length=63,
3130+
min_length=1,
3131+
pattern="^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$",
3132+
),
3133+
] = None
3134+
language: Annotated[
3135+
str | None,
3136+
Field(
3137+
description="Which of the template's languages to send, as a BCP-47 tag (for example `en` or `pt-BR`). Meta's underscore form (`pt_BR`) is accepted and normalized; the accepted message echoes the canonical BCP-47 form. May be omitted when the template has a single language; when it is stocked in several, omitting the language returns a `422` that names the available tags.\n",
3138+
examples=["pt-BR"],
3139+
max_length=35,
3140+
min_length=2,
3141+
),
3142+
] = None
3143+
components: Annotated[
3144+
list[WhatsAppMessageTemplateComponent] | None,
3145+
Field(
3146+
description="The values that fill the template's placeholders: one entry per content block that has placeholders, each carrying its `parameters`. A positional template takes its parameters in `{{n}}` order; a template with named parameters requires each parameter's `name` to match one the template declares. Either way, sending parameters that do not match what the template declares returns a `422` `WhatsAppTemplateParameterMismatch`.\n"
3147+
),
3148+
] = None
3149+
3150+
3151+
class WhatsAppTemplateSend2(BaseModel):
31093152
model_config = ConfigDict(
31103153
extra="allow",
31113154
)
3155+
id: Annotated[
3156+
str | None,
3157+
Field(
3158+
description="The template to send, by its id.",
3159+
examples=["wat_01krdgeqcxet5s7t44vh8rt9mg"],
3160+
min_length=1,
3161+
pattern="^wat_[0-9a-hjkmnp-tv-z]{26}$",
3162+
),
3163+
] = None
31123164
slug: Annotated[
31133165
str,
31143166
Field(
@@ -3122,9 +3174,10 @@ class WhatsAppTemplateSend(BaseModel):
31223174
language: Annotated[
31233175
str | None,
31243176
Field(
3125-
description="Language code of the template variant to send (for example `en` or `pt_BR`). May be omitted when the template has a single language; when it is stocked in several, omitting the language returns a `422` that names the available codes. The accepted message echoes the resolved language.\n",
3126-
examples=["en"],
3127-
min_length=1,
3177+
description="Which of the template's languages to send, as a BCP-47 tag (for example `en` or `pt-BR`). Meta's underscore form (`pt_BR`) is accepted and normalized; the accepted message echoes the canonical BCP-47 form. May be omitted when the template has a single language; when it is stocked in several, omitting the language returns a `422` that names the available tags.\n",
3178+
examples=["pt-BR"],
3179+
max_length=35,
3180+
min_length=2,
31283181
),
31293182
] = None
31303183
components: Annotated[
@@ -3142,15 +3195,58 @@ class WhatsAppMessageSendRequest(BaseModel):
31423195
to: Annotated[
31433196
str,
31443197
Field(
3145-
description="The message recipient's phone number in E.164 format (for example `+31612345678`). A value that is not a valid phone number returns a `422` `WhatsAppInvalidRecipient`.\n",
3198+
description="The message recipient: a phone number in E.164 format (for example `+31612345678`), or the recipient's business-scoped user ID (for example `US.13491208655302741918`), which addresses a WhatsApp user whose phone number you do not have. A value that is neither returns a `422` `WhatsAppInvalidRecipient`. One-time-passcode templates require a phone number and return a `422` `WhatsAppRecipientNotSupportedForTemplate` when sent to a business-scoped user ID.\n",
31463199
examples=["+31612345678"],
31473200
min_length=1,
31483201
),
31493202
]
31503203
template: Annotated[
3151-
WhatsAppTemplateSend | None,
3204+
WhatsAppTemplateSend1 | WhatsAppTemplateSend2 | None,
31523205
Field(
3153-
description="The template to send. Bird selects the sender number from the template's category, so there is no sender field on this request. Templates are the only supported content type today: a request without one is rejected with a `422`.\n"
3206+
description="The template to send. Bird selects the sender number from the template's category, so there is no sender field on this request. Templates are the only supported content type today: a request without one is rejected with a `422`.\n",
3207+
examples=[
3208+
{
3209+
"id": "wat_01ky4x8e4genzb7way45txfkm1",
3210+
"language": "en",
3211+
"components": [
3212+
{
3213+
"type": "body",
3214+
"parameters": [{"type": "text", "text": "1234"}],
3215+
},
3216+
{
3217+
"type": "button",
3218+
"parameters": [{"type": "text", "text": "1234"}],
3219+
},
3220+
],
3221+
},
3222+
{
3223+
"slug": "bird_order_confirmation",
3224+
"language": "en",
3225+
"components": [
3226+
{
3227+
"type": "body",
3228+
"parameters": [
3229+
{"type": "text", "name": "ref", "text": "A1B2C3D4"},
3230+
{"type": "text", "name": "amount", "text": "EUR 49.99"},
3231+
],
3232+
}
3233+
],
3234+
},
3235+
{
3236+
"slug": "bird_otp",
3237+
"language": "en",
3238+
"components": [
3239+
{
3240+
"type": "body",
3241+
"parameters": [{"type": "text", "text": "1234"}],
3242+
},
3243+
{
3244+
"type": "button",
3245+
"parameters": [{"type": "text", "text": "1234"}],
3246+
},
3247+
],
3248+
},
3249+
],
31543250
),
31553251
] = None
31563252
tags: Annotated[
@@ -5532,7 +5628,7 @@ class MailboxCreate(BaseModel):
55325628
retention_tier: Annotated[
55335629
RetentionTier1 | None,
55345630
Field(
5535-
description="How long the mailbox remembers message metadata and extracted text. Original rendered source is always available for 30 days regardless of tier. Only `30d` is available today; longer tiers (`90d`, `1y`, and beyond) are coming soon."
5631+
description="How long the mailbox remembers message metadata and extracted text. Original rendered source is always available for 30 days regardless of tier. Only `30d` is available today; additional tiers are planned."
55365632
),
55375633
] = "30d"
55385634
metadata: Annotated[
@@ -5567,7 +5663,7 @@ class MailboxUpdate(BaseModel):
55675663
retention_tier: Annotated[
55685664
RetentionTier1 | None,
55695665
Field(
5570-
description="How long the mailbox remembers message metadata and extracted text. Lowering the tier deletes memory older than the new horizon and requires `confirm=true` when messages older than the new horizon would be deleted. Only `30d` is available today; longer tiers (`90d`, `1y`, and beyond) are coming soon."
5666+
description="How long the mailbox remembers message metadata and extracted text. Lowering the tier deletes memory older than the new horizon and requires `confirm=true` when messages older than the new horizon would be deleted. Only `30d` is available today; additional tiers are planned."
55715667
),
55725668
] = "30d"
55735669
metadata: Annotated[
@@ -8606,15 +8702,16 @@ class EventVoiceBase(BaseModel):
86068702
),
86078703
]
86088704
direction: VoiceCallDirection
8609-
src_number: Annotated[
8705+
from_: Annotated[
86108706
str,
86118707
Field(
8708+
alias="from",
86128709
description="Calling party number in E.164 format.",
86138710
examples=["+14155551234"],
86148711
min_length=1,
86158712
),
86168713
]
8617-
dst_number: Annotated[
8714+
to: Annotated[
86188715
str,
86198716
Field(
86208717
description="Called party number in E.164 format.",

src/bird/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "0.19.0"
1+
__version__ = "0.20.0"

src/bird/resources/contact_properties_gen.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ def update(
121121
fallback_value: Any | None = None,
122122
options: RequestOptions | None = None,
123123
) -> ContactProperty:
124-
"""Update a contact property's fallback value. The key and type are immutable; create a new property instead.
124+
"""Update a contact property's fallback value. Only the fallback value can change; the key and type are fixed at creation, so a different key or type needs a new property.
125125
126126
```python
127127
prop = client.contact_properties.update("prp_01krdgeqcxet5s7t44vh8rt9mg", fallback_value="free")
@@ -264,7 +264,7 @@ async def update(
264264
fallback_value: Any | None = None,
265265
options: RequestOptions | None = None,
266266
) -> ContactProperty:
267-
"""Update a contact property's fallback value. The key and type are immutable; create a new property instead.
267+
"""Update a contact property's fallback value. Only the fallback value can change; the key and type are fixed at creation, so a different key or type needs a new property.
268268
269269
```python
270270
prop = await client.contact_properties.update("prp_01krdgeqcxet5s7t44vh8rt9mg", fallback_value="free")

0 commit comments

Comments
 (0)