twilio / twilio/twilio-python

[BUG] Content API bindings expect underscores, but the API expects slashes

Open
#881 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

type: bug
Dominant language
Python
Stars
2.1k
Forks
815
Avg merge
2d 13h
Merged PRs (30d)
2

Description

Describe the bug

Creating content templates via rest endpoints works, doesn't work in the bindings

The example below demonstrates each step

Code snippet
import json
import requests  # install it
from twilio.rest import Client

# Hardcoded credentials for reproduction (replace with test credentials as needed)
TWILIO_ACCOUNT_SID = "DONT_FORGET_THIS_ONE"
TWILIO_AUTH_TOKEN = "DONT_FORGET_THIS_ONE"


def sdk_attempt_create_delete():
    print("=== SDK attempt (expected to fail) ===")
    client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
    cl = client.content.v1.contents
    try:
        types = cl.Types({'twilio_text': cl.TwilioText({'body': 'Hi {{1}}'})})
        req = cl.ContentCreateRequest({'friendly_name': 'sdk_bug_demo', 'language': 'en', 'types': types})
        # Observe what the SDK will send
        try:
            print('SDK types to_dict:', types.to_dict())
        except Exception:
            pass
        content = cl.create(req)
        print('SDK CREATED_SID', content.sid)
        # cleanup
        client.content.v1.contents(content.sid).delete()
        print('SDK DELETED')
    except Exception as e:
        print('SDK CREATE_ERR', e)


def manual_http_create_delete():
    print("=== Manual HTTP (expected to succeed) ===")
    url = 'https://content.twilio.com/v1/Content'
    payload = {
        'friendly_name': 'manual_http_demo',
        'language': 'en',
        'types': {
            'twilio/text': {'body': 'Hi {{1}}'}
        }
    }
    print('HTTP payload:', json.dumps(payload))
    resp = requests.post(url, json=payload, auth=(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN), timeout=30)
    print('HTTP STATUS', resp.status_code)
    print('HTTP BODY', resp.text)
    resp.raise_for_status()
    sid = resp.json().get('sid')
    print('HTTP CREATED_SID', sid)
    # cleanup
    del_resp = requests.delete(f'{url}/{sid}', auth=(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN), timeout=30)
    print('HTTP DELETE STATUS', del_resp.status_code)


if __name__ == '__main__':
    sdk_attempt_create_delete() # fail
    manual_http_create_delete()  # success
Actual behavior

Bindings fail, API succeeds, logs here

 === SDK attempt (expected to fail) ===
   2   │ SDK types to_dict: {'twilio_text': {'body': 'Hi {{1}}'}, 'twilio_media': None, 'twilio_location': No
       │ ne, 'twilio_list_picker': None, 'twilio_call_to_action': None, 'twilio_quick_reply': None, 'twilio_c
       │ ard': None, 'twilio_catalog': None, 'twilio_carousel': None, 'twilio_flows': None, 'whatsapp_card': 
       │ None, 'whatsapp_authentication': None}
   3   │ SDK CREATE_ERR HTTP 400 error: Unable to create record: Invalid types. At least one content type def
       │ inition is required
   4   │ === Manual HTTP (expected to succeed) ===
   5   │ HTTP payload: {"friendly_name": "manual_http_demo", "language": "en", "types": {"twilio/text": {"bod
       │ y": "Hi {{1}}"}}}
   6   │ HTTP STATUS 201
   7   │ HTTP BODY {"account_sid": "CENSORED", "date_created": "2025-08-29T00:02:13
       │ Z", "date_updated": "2025-08-29T00:02:13Z", "friendly_name": "manual_http_demo", "language": "en", "
       │ links": {"approval_create": "https://content.twilio.com/v1/Content/HX3089b50716f1bb4a5415919ef2788bf
       │ 3/ApprovalRequests/whatsapp", "approval_fetch": "https://content.twilio.com/v1/Content/HX3089b50716f
       │ 1bb4a5415919ef2788bf3/ApprovalRequests"}, "sid": "CENSORED", "types": {"tw
       │ ilio/text": {"body": "Hi {{1}}"}}, "url": "https://content.twilio.com/v1/Content/HX3089b50716f1bb4a5
       │ 415919ef2788bf3", "variables": { }}
   8   │ HTTP CREATED_SID CENSORED
   9   │ HTTP DELETE STATUS 204
Expected behavior

The bindings should work exactly as the API

twilio-python version

9.7.2

Python version

3.11.11

Logs or error messages

No response

Additional context

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the Python client entry point shown in the report, client.content.v1.contents, and inspect how Types.to_dict() serializes content type keys. Compare the SDK output with the successful manual request using twilio/text; done means SDK creation accepts the slash-form key and the regression is covered by an appropriate test.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
api
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.