BUILDING A PYTHON SDK CHANGED HOW I DESIGN APIS
When I started building OpenWA-Python, I assumed the hardest part would be implementing the API itself. Like many developers, I thought the…
BUILDING A PYTHON SDK CHANGED HOW I DESIGN APIS

When I started building OpenWA-Python, I assumed the hardest part would be implementing the API itself. Like many developers, I thought the job of an SDK was straightforward: send HTTP requests, receive responses, and expose a clean interface for users. It sounded simple enough.
It didn’t take long to realize I was solving the wrong problem.
Writing the code that communicated with the API was only a small part of the work. The real challenge was designing a library that other developers could understand, trust, and enjoy using. Every decision - from how methods were named to how errors were reported and how modules were organized - shaped the developer’s experience. A technically correct SDK could still be frustrating if it were inconsistent, difficult to navigate, or poorly documented.
That realization changed how I think about APIs.
Before working on OpenWA-Python, I viewed an API primarily as a collection of endpoints that exposed functionality. Building an SDK forced me to see something different: an API is also a product, and an SDK is the interface through which developers experience that product. Good API design is not just about making features available; it is about making those features intuitive, predictable, and easy to integrate into real applications.
This perspective is becoming increasingly important. Modern software is built by composing services rather than creating every component from scratch. Developers rely on SDKs to interact with payment platforms, cloud providers, messaging services, artificial intelligence models, and countless other APIs. As these ecosystems grow, the quality of the SDK often determines how quickly developers can adopt a platform and how confidently they can build on top of it. Recent discussions across the software engineering community have emphasized developer experience, strong typing, clear documentation, and consistent interfaces as key factors in successful SDK design.
Building OpenWA-Python gave me a front-row seat to these challenges. Some design decisions worked well from the beginning. Others required multiple iterations before they felt natural. Along the way, I learned that creating a good SDK has far less to do with writing HTTP requests than with designing an interface that developers rarely have to think about. When an SDK feels intuitive, developers can focus on solving their own problems instead of learning yours.
In this article, I share the lessons that have most influenced how I now design APIs. These lessons are drawn from my experience building an open-source Python SDK, supported by current software engineering practices and recent discussions within the Python and API development communities. They are not presented as universal rules, but as principles that have fundamentally changed the way I approach software design.
Why I Built OpenWA-Python
Every software project begins with a problem, and OpenWA-Python was no exception.
While working with APIs, I noticed that many services offered well-documented REST endpoints but lacked a Python SDK that felt natural to use. Developers were often expected to construct HTTP requests manually, manage authentication, parse JSON responses, and handle errors themselves. Although this approach works, it introduces repetitive code and increases the likelihood of inconsistent implementations across different projects.
I wanted to provide a more Pythonic experience — one that would allow developers to focus on building applications rather than repeatedly writing the same networking logic. Instead of exposing raw HTTP requests, the SDK would present a clear, intuitive interface that abstracted away the implementation details while remaining flexible enough for advanced use cases.
At first, I underestimated the scope of the challenge. I assumed the primary task would be mapping API endpoints to Python methods. In reality, that was only a small fraction of the work. The more time I spent developing the SDK, the more I realized that every design decision influenced how developers would perceive and use the library.
For example, a simple question such as Where should this function live? quickly became an architectural decision. Should authentication be handled automatically or explicitly? Should API responses be returned as raw dictionaries or converted into Python objects? Should every possible API endpoint be exposed immediately, or should the SDK prioritize a smaller, well-designed interface before expanding?
These questions had no single correct answer. Each choice involved trade-offs between simplicity, flexibility, maintainability, and long-term compatibility. More importantly, every decision affected the experience of developers who would eventually use the SDK.
As the project evolved, I stopped thinking of the SDK as a thin wrapper around an API. Instead, I began to see it as a product in its own right. Its responsibility was not merely to communicate with a server, but to provide an interface that developers could understand with minimal effort. The SDK became a bridge between a complex web service and the people building applications on top of it.
This shift in perspective fundamentally changed how I approach software development. I realized that good API design is not only about exposing functionality; it is about reducing complexity. A well-designed SDK should guide developers toward correct usage, make common tasks straightforward, and communicate clearly when something goes wrong. In many cases, the quality of the developer experience matters just as much as the capabilities of the underlying API.
Building OpenWA-Python also reinforced an important principle that extends far beyond SDK development: software is written once but read many times. Every method name, module, class, and exception becomes part of the conversation between the library and its users. Writing code that machines can execute is relatively easy; designing software that humans can understand is a far greater challenge.
Looking back, I no longer consider OpenWA-Python to be just a wrapper around an API. It became a practical lesson in software architecture, developer experience, and the importance of thoughtful API design. Those lessons continue to influence how I structure projects, design interfaces, and think about the responsibilities of software engineers.
An SDK Is More Than a Wrapper Around an API
When I began developing OpenWA-Python, my mental model of an SDK was surprisingly simple. I imagined it as a collection of Python functions that would send HTTP requests to an API and return the responses. If every endpoint had a corresponding method, then the SDK would be complete.
Technically, that approach would have worked. Developers could authenticate, make requests, and receive data. From a functional perspective, the SDK would have fulfilled its purpose.
But functionality alone does not create a good developer experience.
The turning point came when I stopped asking How do I expose this endpoint? and started asking a different question:
How would I want to use this library if I were discovering it for the first time?
That single question changed almost every design decision that followed.
Instead of treating the API documentation as a blueprint to copy directly into Python, I began thinking about how Python developers naturally expect libraries to behave. Python has a well-established philosophy that emphasizes readability, consistency, and simplicity. A good SDK should feel like it belongs within that ecosystem rather than feeling like an HTTP client with Python syntax layered on top.
Consider a simple messaging endpoint.
A direct translation from a REST API might require developers to manually build URLs, prepare JSON payloads, specify HTTP methods, and parse the server’s response. Although this mirrors how the API works internally, it exposes implementation details that most SDK users neither need nor want to manage.
Instead, an SDK should allow developers to think in terms of actions rather than network requests.
Rather than worrying about endpoints and payloads, they should be able to express their intent through a clear, readable interface.
client.messages.send(
phone="+250791348888",
text="Hello, World!"
)
This small example hides a considerable amount of complexity. Authentication, request construction, serialization, network communication, response validation, and error handling all happen behind the scenes. The developer interacts with a concise interface while the SDK manages the implementation details.
That realization fundamentally changed how I viewed abstraction.
Initially, I believed abstraction meant hiding complexity. Now I see it differently. Good abstraction does not simply conceal details — it organizes them. It exposes the concepts developers care about while keeping implementation details in the background until they are genuinely needed.
This distinction is subtle but important.
If an SDK hides too much, developers lose flexibility and struggle when they need advanced functionality. If it exposes too much, the library becomes overwhelming and difficult to learn. Finding the right balance became one of the most challenging aspects of the project.
As OpenWA-Python grew, I also realized that an SDK has responsibilities beyond making API calls. It becomes the primary interface between a platform and its developer community. For many users, the SDK defines their entire perception of the service. A well-designed backend can still feel frustrating if the SDK is confusing, inconsistent, or poorly documented. Conversely, a thoughtfully designed SDK can make a complex platform feel approachable and enjoyable to work with.
This shift in thinking also changed how I evaluate APIs themselves. I no longer judge an API solely by the number of endpoints it offers or the features it supports. Instead, I ask different questions:
- Can developers predict how methods are named?
- Are similar operations handled consistently?
- Do error messages help users solve problems?
- Does the interface encourage correct usage?
- Will this design still make sense six months from now?
These questions have little to do with HTTP and everything to do with developer experience.
Looking back, the most valuable lesson I learned was that building an SDK is not about wrapping endpoints. It is about designing an interface that developers can trust. Every class, method, parameter, and exception contributes to that experience. The better those pieces fit together, the less developers have to think about the SDK itself — and the more they can focus on solving the problems their own applications were built to address.
Building an SDK isn’t about wrapping endpoints. It’s about designing an interface that developers can trust.
Good APIs Are Designed for Humans, Not Endpoints
One of the biggest mistakes I made at the beginning of the project was believing that every API endpoint deserved an equally visible place in the SDK.
At first, this seemed logical. If the API exposes an endpoint, the SDK should expose it too. The relationship appeared almost mechanical: one endpoint becomes one method, one request becomes one function call.
The more I worked on the project, however, the more I realized that this approach was driven by the API’s internal structure rather than the developer’s mental model.
An API is designed around resources, routes, and protocols. Developers, on the other hand, think in terms of tasks.
When someone uses an SDK, they rarely think:
“I need to send a POST request to this endpoint.”
Instead, they think:
“I want to send a message.”
That difference may seem subtle, but it completely changes how an SDK should be designed.
Developers shouldn’t need to understand how an API is organized internally before they can use it effectively. A good SDK translates technical implementation details into concepts that feel natural within the programming language.
This realization led me to rethink almost every public method in OpenWA-Python.
Instead of asking whether a method accurately reflected an endpoint, I started asking a different question:
Would another Python developer guess that this method exists?
That question became surprisingly powerful.
If a method name feels obvious, developers are less likely to search through documentation. Modern IDEs provide autocomplete, type hints, and inline documentation, meaning developers often discover an SDK simply by exploring it interactively.
A well-designed interface supports that style of exploration.
Imagine opening a new Python library and seeing something like this:
client.contacts.
Within seconds, your editor begins suggesting methods:
create()
delete()
find()
get()
list()
search()
update()
Even without reading the documentation, you already understand what the library can do.
Now imagine encountering this instead:
client.executeOperation()
client.run()
client.process()
client.handle()
Each method could perform an entirely different task, but their names reveal very little about their purpose. The developer is forced to stop writing code and start searching through documentation or source files.
Nothing interrupts productivity faster than uncertainty.
That experience taught me that naming is not a cosmetic decision. Every public method becomes part of the language your SDK shares with its users.
Good method names reduce cognitive effort. Poor method names increase it.
This principle extends beyond methods.
Class names, parameter names, return values, modules, and even exception classes all contribute to the developer’s understanding of the library. Together, they form a vocabulary. The more consistent that vocabulary becomes, the easier the SDK is to learn.
I also discovered that consistency is often more important than cleverness.
A clever abstraction might impress its author, but consistency builds trust.
If one resource exposes a list() method while another uses get_all(), developers immediately begin wondering whether those methods behave differently. Even if they are functionally identical, the inconsistency creates unnecessary doubt.
Small inconsistencies accumulate.
A different naming style here, an unexpected return type there, an exception that behaves differently somewhere else — each decision adds a tiny amount of friction. Individually, these choices may seem insignificant. Collectively, they shape how developers perceive the entire library.
The longer I worked on OpenWA-Python, the more I appreciated that developer experience is often measured not by what surprises users, but by what doesn’t.
When developers can confidently predict how the next method behaves, they spend less time learning the SDK and more time building their own applications.
In retrospect, I no longer believe that an SDK should mirror an API.
Instead, I believe an SDK should interpret an API.
It should take the complexity of a web service and present it through an interface that feels natural within its programming language. The goal isn’t to expose endpoints — it is to expose intent.
That shift in thinking changed how I design every API today.
An SDK shouldn’t mirror an API. It should interpret it.
Your Project Structure Teaches Developers Before Your Documentation Does
When I started structuring OpenWA-Python, I treated the folder organization as an internal implementation detail. In my mind, what mattered was that the code worked — how it was arranged in the repository felt secondary.
That assumption didn’t last long.
As the project grew, I noticed something subtle but important: developers don’t start with the documentation. They start with the codebase itself.
Even before reading a single line of explanation, a developer opening an SDK will usually do the same thing:
- browse the repository
- scan folder names
- look for entry points
- try to understand the structure
And in that moment, the structure of your project is already communicating something — whether you intended it to or not.
A confusing structure creates hesitation. A clear structure builds confidence.
The SDK starts at the folder level
In OpenWA-Python, I initially grouped files based on implementation concerns: HTTP logic in one place, utilities in another, and API-related code scattered depending on when it was added.
It worked technically, but it didn’t feel intuitive from the outside.
A developer doesn’t think in terms of internal implementation layers. They think in terms of capabilities.
They want to know:
- How do I authenticate ?
- How do I send a message ?
- How do I manage contacts ?
- How do I handle errors?
- How do I manage contacts?
- How do I handle errors?
So I had to reorganize the structure to reflect usage, not internals.
A more intentional structure started to emerge:
client/
auth/
messages/
contacts/
exceptions/
models/
This change seems small, but it completely changed how the SDK was perceived.
Instead of being a collection of Python files, it started to feel like a coherent system with discoverable capabilities.
Structure is a form of documentation
One of the most surprising realizations was this:
Developers read structure before they read words.
Before opening the README, a developer already forms expectations based on how the project is organized.
If authentication logic is buried deep inside unrelated modules, it signals that authentication is an afterthought.
If messaging, contacts, and core client logic are clearly separated, it signals intentional design.
In other words, the folder structure silently communicates architectural priorities.
This is why project structure is not just an internal concern — it is the first layer of documentation your SDK provides.
A practical example: reducing cognitive friction
Imagine two SDKs.
SDK A
core/
utils/
helpers/
manager/
service/
handlers/
SDK B
client/
auth/
messages/
contacts/
media/
errors/
Both might be equally powerful internally, but only one of them helps a developer build a mental model quickly.
SDK B reduces cognitive effort because it maps directly to what the developer is trying to accomplish.
They don’t need to guess where something lives — they can predict it.
The moment I stopped reorganizing for myself
The turning point for me was realizing that I was no longer organizing code for my own understanding.
I was organizing it for someone who had never seen the project before.
That changes everything.
What feels “logical” to the author is not always “discoverable” to the user.
So I started asking a new question:
If a developer explores this repository for 30 seconds, what story does the structure tell them?
That question became more important than any technical constraint.
Because at that point, the structure stopped being just organization.
It became communication.
The hidden lesson
Over time, I began to see a pattern:
- Methods teach how to use the SDK
- Naming teaches how to think about it
- Structure teaches what exists in it
And structure is the first thing developers encounter.
So if the structure is unclear, everything else starts at a disadvantage.
Even a well-designed API can feel confusing if the entry points are hidden behind unclear organization.
Before developers read your documentation, they read your structure.
Type Hints Are a Conversation with Your IDE
When I first wrote OpenWA-Python, type hints were not a priority.
Python allows you to get a lot done without them, and at the beginning, I preferred speed over strict structure. If the code ran, that was enough.
But as the SDK grew and more features were added, I started noticing a new category of problems — ones that had nothing to do with runtime behavior.
They were pre-runtime confusion problems.
Developers using the SDK would often ask questions like:
- “What type does this method return?”
- “Is this parameter required or optional?”
- “What format should this value take?”
The SDK was working correctly, but the experience was unclear.
That’s when I realized something important:
If developers need to read the documentation for every method, the SDK is already too expensive to use.
Type hints change how developers explore code
The real shift happened when I started adding type hints consistently.
Suddenly, something interesting occurred: developers didn’t need to guess anymore.
Their editor started doing part of the thinking for them.
Instead of opening documentation, they could simply write:
id="t1"
client.messages.send(
And immediately see suggestions like:
phone: `str`
text: `str`
timeout: `Optional[int]`
The SDK was no longer something they had to memorize.
It became something they could interact with gradually.
Type hints are not for machines — they are for humans
A common misunderstanding is that type hints exist for static analysis tools like mypy.
In practice, their biggest impact is on developer experience inside modern IDEs.
They answer questions before they are asked.
For example:
id="t2"
def send_message(
phone: str,
message: str,
timeout: Optional[int] = None
) -> Message:
From this alone, a developer already understands:
- what is required
- what is optional
- what the function returns
- how it should be used
Without reading a single line of documentation.
That changes the onboarding experience dramatically.
Without types, every function is a guess
Before introducing consistent typing, OpenWA-Python had a different problem: ambiguity.
A method might return:
- a dictionary in one case
- a boolean in another
- or a raw API response in another edge case
Even if all of those behaviors were technically correct, they created uncertainty.
And uncertainty is expensive.
Because developers don’t trust uncertain APIs — they wrap them in defensive code, add checks everywhere, and slow down their own development process.
Type hints helped remove that ambiguity.
Not by changing runtime behavior — but by making expectations explicit.
The hidden value: self-documenting APIs
One of the most powerful side effects of type hints is that they reduce the need for explanation.
Instead of writing long comments like:
This function sends a message to a phone number and returns a message object containing metadata.
The function signature itself becomes the documentation.
id="t3"
def send_message(phone: str, message: str) -> Message:
When combined with a good IDE, this becomes almost self-explanatory.
And that’s when I realized:
The best documentation is the one developers don’t need to open.
Type hints enforce better design decisions
Another unexpected benefit was that typing forced me to confront design issues earlier.
For example:
- Should this return
dictor a structured object? - Should this accept a
stror a dedicatedPhoneNumbertype? - Should optional parameters actually be optional?
Without type hints, it is easy to postpone these decisions.
With type hints, you are forced to define contracts explicitly.
That leads to cleaner architecture because ambiguity is no longer hidden — it is exposed immediately.
Consistency becomes visible through types
Once the SDK became fully typed, inconsistencies that were previously invisible became obvious.
For example:
- similar methods returning different shapes
- inconsistent naming across modules
- unclear optional parameters
Type checking tools and IDE warnings made these issues visible early in development.
So instead of discovering problems in production, I started discovering them while writing code.
That alone significantly improved the stability of the SDK.
A shift in mindset
Over time, I stopped seeing type hints as optional annotations.
I started seeing them as part of the public contract of the SDK.
They define:
- what inputs are valid
- what outputs are expected
- how the SDK should be used correctly
In other words, they are not decoration.
They are part of the design.
Type hints are not about catching errors — they are about eliminating uncertainty before it becomes a problem.
Documentation Starts Long Before the README
When I first started OpenWA-Python, I treated documentation as the final step.
The workflow was simple in my mind:
- Build features
- Clean up the code
- Write a README
- Publish
Documentation was something I would “add later” once the SDK was stable.
In practice, that mindset created a problem I didn’t expect: by the time I started writing the documentation, I no longer remembered how a new user would experience the project.
Everything felt obvious to me — but not to someone seeing it for the first time.
That gap between builder knowledge and user knowledge became impossible to ignore.
Documentation is not a layer — it is a design constraint
At some point, I realized something uncomfortable:
If something is hard to explain, it is probably hard to use.
This completely changed how I approached the SDK.
Instead of asking:
“How do I document this feature?”
I started asking:
“If I had to explain this in one sentence, would the design still make sense?”
That question forced me to simplify interfaces, rename methods, and sometimes remove features entirely.
Because if a feature requires too much explanation, it often means the abstraction is wrong.
The README is only the surface
Most developers think of documentation as the README file.
But in reality, documentation starts much earlier:
- method names
- parameter names
- class structure
- return types
- error messages
- examples in code
All of these are already part of the documentation whether you write anything or not.
A developer reading your SDK is constantly building a mental model based on these signals.
So when I redesigned OpenWA-Python, I started treating every public element as if it were already part of the README.
Because in practice, it is.
Good documentation is predictable usage
One of the biggest improvements came when I started designing examples before finalizing APIs.
Instead of writing documentation after implementation, I would reverse the process:
- Write how I want the SDK to be used
- Adjust the API to match that usage
- Implement the feature
For example, I would start with something like:
id="d1"
client.messages.send(
phone="+250791348888",
text="Hello!"
)
Then I would design the SDK around making that usage natural and unavoidable.
This approach reduced friction significantly because the API was shaped by intended usage, not internal structure.
Examples are the most important form of documentation
One thing I underestimated early on was the importance of examples.
Developers rarely read full explanations first.
They scan:
- “How do I install this?”
- “How do I use it quickly?”
- “What does real code look like?”
If the first example is confusing, they often leave immediately.
So I started treating examples as the most important part of documentation — not an afterthought.
A good example is not just correct code. It is:
- minimal
- realistic
- directly runnable
- representative of real usage
Because for many developers, that example is the documentation.
Documentation forces clarity in design
Writing documentation early exposed another issue: unclear APIs are hard to describe consistently.
For example:
- Why does this method exist?
- When should it be used?
- What is the difference between two similar functions?
If I couldn’t clearly answer those questions in writing, it usually meant the API itself was unclear.
So documentation became a diagnostic tool.
It revealed design problems before users ever saw them.
The hidden feedback loop between code and docs
Over time, I noticed a cycle forming:
- better code made documentation easier
- clearer documentation exposed design flaws
- fixing those flaws improved the code again
This feedback loop gradually improved both the SDK and my thinking as a developer.
Eventually, I stopped separating “code” and “documentation” as different phases.
They became part of the same design process.
Documentation is empathy encoded in text
The biggest shift in perspective came when I realized:
Documentation is not about describing the code. It is about anticipating confusion.
Every sentence in a README or guide is an attempt to answer a question the developer hasn’t asked yet.
That requires empathy.
Not just technical accuracy — but an understanding of where users will struggle.
Good documentation does not explain a good API. It reveals one.
Consistency Matters More Than Features
When OpenWA-Python started growing, I reached a point most developers eventually face: I had more ideas for features than I had time to implement them properly.
It becomes tempting to add more functionality quickly:
- new methods
- new endpoints
- new helpers
- new abstractions
At first, this feels like progress.
But over time, I noticed something uncomfortable: the SDK was becoming harder to use, not easier.
Not because it lacked power — but because it lacked predictability.
More features does not mean better design
A mistake I made early on was equating “complete SDK” with “SDK that exposes everything the API offers.”
So I started adding methods as soon as endpoints were available.
But this created a hidden problem:
Similar operations did not behave in similar ways.
For example:
- one method returned a raw dictionary
- another returned a structured object
- one raised exceptions
- another returned error codes
- some used pagination implicitly
- others required manual handling
Individually, none of these decisions were wrong.
But together, they created friction.
Because developers don’t experience features one by one — they experience them as a system.
And a system that behaves inconsistently is harder to trust.
Predictability is what developers actually want
I started noticing a pattern in how developers used the SDK:
They weren’t just trying to use features.
They were trying to predict behavior.
They would ask things like:
- “Does this work like the previous method?”
- “Will this return the same type?”
- “Should I expect an exception here too?”
Every time they had to stop and think, the SDK was adding cognitive load.
That’s when I realized something important:
A good SDK is not the one with the most capabilities. It is the one with the least surprises.
Consistency across naming, structure, and behavior
Consistency is not limited to function names. It spans the entire SDK.
1. Naming consistency
If one module uses:
id="c1"
list_messages()
then another should not use:
id="c2"
getAllMessages()
Even if both are correct, the inconsistency forces developers to constantly switch mental models.
2. Return type consistency
If one method returns:
id="c3"
dict
then similar methods should not return:
id="c4"
custom objects
Consistency allows developers to reuse patterns instead of relearning behavior.
3. Error consistency
As discussed earlier, errors must behave in predictable ways:
- same structure
- same style
- same level of detail
- same categories
If error behavior changes between modules, developers lose confidence in the system.
4. Behavioral consistency
This is the most subtle but most important part.
For example:
- Does every “create” operation immediately return the created object?
- Do all “list” methods support pagination the same way?
- Do filters behave consistently across endpoints?
If the answer is “it depends”, then the SDK is still unfinished.
The real cost of inconsistency
What makes inconsistency dangerous is that it does not break code immediately.
It breaks trust gradually.
Developers start writing defensive patterns:
- extra validation
- redundant checks
- repeated documentation lookups
- unnecessary try/except blocks
Eventually, the SDK becomes something they are afraid to rely on fully.
And once that happens, adoption slows down — even if the SDK is technically powerful.
Consistency reduces thinking cost
A consistent SDK does something very powerful:
It removes decisions.
If all “send” methods behave the same way, developers don’t need to re-evaluate usage every time.
They can focus on the problem they are solving instead of the tool they are using.
That is the real value of consistency:
It turns a library into a set of predictable mental shortcuts.
The hardest part: saying no to features
The most difficult lesson for me was realizing that improving consistency often means not adding new features yet.
Sometimes the best decision is:
- delay a feature
- refactor existing behavior
- unify inconsistent patterns
- simplify overlapping methods
Because every new feature increases the surface area of inconsistency if the foundation is not stable.
A shift in mindset
At some point, I stopped asking:
“What can I add next?”
And started asking:
“Does this behave like everything else in the SDK?”
That question slowed development down — but dramatically improved quality
A powerful SDK is not defined by how much it can do, but by how predictably it behaves.
What I Would Do Differently Today
Looking back at OpenWA-Python, I don’t think about the lines of code I would rewrite first.
I think about the assumptions I would challenge.
When I started the project, I believed that building an SDK was mostly an implementation problem. I thought success depended on correctly mapping API endpoints into Python methods and making sure every request behaved as expected.
Today, I see it very differently.
If I were starting the project again, I would spend far less time writing code in the first week and far more time designing the public interface.
Before implementing a single feature, I would ask questions like:
- What should the first five minutes with this SDK feel like?
- What are the most common tasks developers want to accomplish?
- Which names would they naturally search for in their editor?
- Which mistakes are they most likely to make?
- How can the SDK help them avoid those mistakes?
These questions have little to do with HTTP requests or JSON payloads, yet they have everything to do with the quality of the developer experience.
One of the biggest lessons I learned is that implementation is relatively easy to change. Public interfaces are not.
A private helper function can be refactored tomorrow without affecting anyone else. A public method, however, becomes part of an agreement between the SDK and every application that depends on it. Once developers adopt that interface, changing it has consequences far beyond your own repository.
That realization fundamentally changed how I approach software design.
I now spend much more time thinking about the stability of interfaces than the speed of implementation.
Another thing I would change is the order in which I design features.
Earlier in the project, my workflow looked something like this:
- Read the API documentation.
- Implement the endpoint.
- Test that it works.
- Document the feature.
Today, I would reverse much of that process.
I would begin by writing the code example I hope developers will eventually write.
Something like this:
client.messages.send(
phone="+250791348888",
text="Hello from OpenWA-Python!"
)
Only after that example feels natural would I begin implementing the underlying functionality.
This simple change keeps the focus where it belongs: on the developer’s experience rather than the internal mechanics of the API.
I would also invest earlier in consistency.
Throughout the project, I discovered that inconsistencies rarely appear all at once. They accumulate gradually. A slightly different method name here, an unexpected return type there, a new exception that doesn’t quite match the existing pattern. None of these decisions seems significant in isolation, but together they slowly increase the mental effort required to use the SDK.
If I were starting again, I would define clear design principles before writing most of the code. Those principles would guide every new feature and make it easier to maintain a coherent interface as the project grows.
Perhaps the biggest change, however, is philosophical.
When I started OpenWA-Python, I measured progress by the number of endpoints the SDK supported.
Today, I measure progress differently.
I ask whether the SDK has become easier to understand.
Has the interface become more predictable?
Can a new developer discover how to use it without constantly referring to the documentation?
Do the names, types, and error messages communicate the right ideas?
These questions are harder to answer than counting features, but they are much closer to what defines a successful SDK.
Building OpenWA-Python also changed the way I evaluate other software projects.
When I explore a new library today, I pay attention to details that I barely noticed before:
- How quickly can I understand its structure?
- Are method names consistent?
- Do the examples reflect real-world usage?
- Are exceptions meaningful?
- Does the library guide me toward the correct way of using it?
These are not superficial details. They reveal the care that went into the design.
Ultimately, I no longer believe that great SDKs are created by exposing every capability of an API.
They are created by making the right capabilities feel obvious.
That distinction has influenced not only how I build SDKs, but how I think about software engineering as a whole.
Every interface is a conversation between the people who build software and the people who depend on it.
The quality of that conversation is determined long before the first request reaches a server.
It begins with thoughtful design.
If I could start over, I wouldn’t write less code — I would design more before writing it.
Conclusion
Building OpenWA-Python taught me lessons that extend far beyond Python or SDK development. It changed the way I think about software itself.
I began the project believing that good engineering meant exposing functionality accurately and efficiently. Along the way, I discovered that functionality is only the starting point. The real challenge is designing software that helps people succeed without forcing them to understand every implementation detail behind it.
That shift changed the questions I ask whenever I begin a new project.
Instead of asking, “How do I implement this feature?” I now ask, “How will someone experience this feature?”
That difference has shaped every design decision I make today.
Throughout this article, I have shared lessons that emerged from building an open-source Python SDK:
- An SDK is more than a wrapper around an API.
- Good APIs are designed for people, not endpoints.
- Project structure teaches before documentation does.
- Error messages are part of the user experience.
- Type hints reduce uncertainty long before runtime.
- Documentation begins with thoughtful design.
- Consistency builds trust more effectively than an ever-growing list of features.
None of these ideas came from a single breakthrough. They emerged gradually, through experimentation, refactoring, mistakes, and countless small decisions that collectively reshaped my understanding of software engineering.
OpenWA-Python continues to evolve, and so do I. There are still interfaces I would redesign, abstractions I would simplify, and decisions I would revisit with the benefit of experience. That is one of the rewarding aspects of building software: every project leaves you better prepared for the next one.
If there is one lesson I hope readers take away, it is this:
- Great SDKs are not remembered because they expose every feature.
- They are remembered because developers stop thinking about the SDK and start thinking about the problems they are trying to solve.
- When that happens, the design has done its job.
Final Reflection
The best APIs are not the ones that show developers everything the system can do. They are the ones that make developers forget there is a system underneath at all.
Before you go
- Please take a moment to like the post and follow the writer!
- Did you know that over 400,000 developers share what they’re building, learning, and discovering across our platforms every month? Learn how you can contribute here
메타데이터
- post_id
- d99ae2ed50bb
- slug
- building-a-python-sdk-changed-how-i-design-apis-d99ae2ed50bb
- url
- https://python.plainenglish.io/building-a-python-sdk-changed-how-i-design-apis-d99ae2ed50bb
- canonical_url
- https://python.plainenglish.io/building-a-python-sdk-changed-how-i-design-apis-d99ae2ed50bb
- author_url
- https://medium.com/@bonheurndezenc
- status
- ok
- fetched_at
- 2026-07-17 02:44:42