Trial-And-Error Example From A Product Manager Learning APIs From Scratch
Working in API-first companies means working close to the code — even if you’re not the one shipping it.
I was a Staff Product Manager at Twilio in an API-first product team a few years ago. I didn’t just want to understand the product — I wanted to experience the developer journey firsthand. Our users were developers, so I aimed to feel their friction points directly by walking through the setup, integration, and testing flows.
This was my form of empathy-driven onboarding for the PM.
Now, 4 years later, I revisited those notes to resume API design. Below are snippets from my trials with:
Twilio’s SMS API
Technical documentation and console
Postman and cURL
Migration to Python code.
I spent 1 hour and 21 minutes writing this. You need 5 minutes to read this.

Related:
Goal
My goal was to:
Learn how the API works by using it myself
Identify friction points that new or less technical users might encounter
I’ll directly jump to takeaways and keep rest of the API trials for bottom of the article.
Takeaways: A Few Years Ago
Using a developer-focused product like Twilio as a semi-technical PM required persistence a few years ago. I wasn’t writing new APIs or building full apps, but I did:
Run and modify API calls
Interpret responses
Investigate errors
Compare SDKs with raw REST usage
Test messaging services at different delivery rates
These hands-on experiments helped me:
Build comfort with APIs
Identify UX gaps in developer tools
Understand how non-developers onboard to API-first products
You don’t need to be a backend engineer to build this skill. But you need a structured approach to debugging, and the patience to keep going when the docs don’t explain everything.
This trial-and-error approach later shaped how I designed APIs from first principles. When I worked on simplifying APIs for marketing and emergency notifications, I drew directly from these frustrations. I pushed for:
Clearer error messages
Consistent resource paths
Better onboarding flows
API Design: What I Wish I Knew Earlier
Years later, after reading and working on multiple APIs, I’ve built a better mental model for what separates good API design from frustrating ones. These best practices stand out:
Use clear, resource-oriented paths. Good: /api/messages/{id} and Bad: /getMessageInfo
Surface helpful, actionable errors. “Invalid phone number format” is better than “400 Bad Request.”
Stick to standard formats. JSON is table stakes. YAML or form-encoded data adds friction unless truly needed.
Document everything, but especially edge cases. A good dev experience doesn’t just explain the happy path — it prepares you for the weird stuff.
Likely Takeaway: Developer Experience In 2025
I haven’t repeated this Twilio experiment using AI, but I’ve tackled other coding projects recently — and it’s clear the developer experience has become way easier. I could “Vibe code” API interfaces and iteratively fix errors using tools like ChatGPT or GitHub Copilot. These can now:
Explain unfamiliar cURL flags
Convert shell scripts to Python
Debug confusing error messages
Help with API authentication workflows
In my projects, I used AI to create shell, Python, SQL, Javascript, Excel, and GCP scripts from scratch — and to debug fine-tuning failures in ML pipelines. In many ways, AI has become a second brain for developer experience: quick to try things, fast to explain, and endlessly patient.
Onboarding: Account, Login, Trial Message
Twilio’s web interface made it easy to create an account, log in, and send a test SMS. After signing up, I navigated to the Messages section and used the visual UI to send a message. It worked.
Next, I wanted to go deeper and try the API.
Trying cURL
The documentation suggested using cURL commands to trigger the API. It seemed like a simple place to start — no IDE, no project setup, just terminal commands.
But I quickly ran into issues:
cURL didn’t work in the Windows command prompt
It also failed in IPython
Small errors were hard to debug, and retrying required guessing what to tweak
I needed a better tool.
Using Postman
Many developers recommend Postman, so I installed it. Signing up and creating a project was straightforward. I copied the cURL command from Twilio’s console into Postman — but I wasn’t sure where each part of the request belonged.
For example:
```curl 'https://api.twilio.com/2010-04-01/Accounts/ACxxxxx/Messages.json' -X POST \
--data-urlencode 'To=+16692640602' \
--data-urlencode 'MessagingServiceSid=MGxxxxxx' \
--data-urlencode 'Body=test from msg svc 1' \
-u ACxxxxx:[AuthToken]```
At first, I manually pasted the command into the Postman request. It didn’t work. Then I discovered the Import feature, which parsed the command into fields. Even then, it wasn’t clear where Postman stored parameters like Body or To.
I eventually sent the message. But editing the request and checking delivery status felt slow and clunky. Postman worked better for one-off calls, not for experimenting or iterating.
Moving To Code
I switched to writing code. Since I had used Python before, I chose it again. Setting it up took time:
Installed Node.js (required for some samples)
Installed Python and pip
Installed the Twilio Python SDK with pip install twilio

After setup, I copied sample code from Twilio’s documentation:
from twilio.rest import Client
account_sid = "ACxxxxx"
auth_token = "your_auth_token"
client = Client(account_sid, auth_token)
message = client.messages.create(
to="+15558671234",
from_="+15017251234",
body="Hello from Python!")
print(message.sid)
This worked — I received the SMS on my phone. But now I had new questions:
What else could I do with message?
What other fields were available?

Exploring Message Metadata
I wanted to compare messages sent via Messaging Services versus direct API calls. Specifically, I wanted to:
Add a timestamp to each message
Check how long it took to deliver
Understand delivery status

The Try It Out section of Twilio’s console offered limited insights. It wasn’t obvious how to fetch message status or delivery timestamps.
Eventually, I found the right endpoint in the (now deprecated) API Explorer. It returned detailed fields like:
"status": "delivered",
"date_created": "2021-02-21 12:36:44+00:00",
"date_sent": "2021-02-21 12:36:44+00:00",
"date_updated": "2021-02-21 12:36:49+00:00"
By comparing date_created, date_sent, and date_updated, I could estimate delivery time. If I queried the message too early, the status appeared as sent instead of delivered. That confirmed how status updates worked.

Discovering API Nuances
Even simple tasks had hidden complexities:
Sending a message with a timestamp in the body helped track delivery.
Checking message status required using client.messages(SID).fetch() — whereas I assumed get() would work.
Using Messaging Services meant dealing with rate limits, sender pools, and other constraints that weren’t clearly explained in one place.
I also disabled Sticky Sender to see if it improved delivery time. It did — slightly.

Other Explorations
I tested opt-in/opt-out flows by texting “STOP” and “START” from my phone. That worked smoothly.

I also experimented with Twilio’s Usage API to query charges. But I couldn’t figure out how to filter by date or calculate totals for a single day. The API structure felt unintuitive.

Looking back, it's clear how much easier things have become with conversational AI in 2025.
Recently, I used AI to generate shell, Python, SQL, and GCP Cloud Shell scripts. I also used it to debug AI fine-tuning errors and set up GCP.
Related: