
Efe Omoregie
Efe Omoregie is an Associate Staff Engineer at Yellow Card. With nearly a decade of software engineering experience, he has worked on payment systems, healthcare platforms, and cloud infrastructure. He is deeply passionate about computer science, programming and cloud computing.
Article by Gigson Expert
An API is not merely a technical interface; it is a product in its own right. The developer experience it delivers can be the difference between rapid adoption and abandonment. Two companies are excellent examples of what great API design looks like: Stripe, the payments infrastructure giant, and Twilio, the cloud communications platform. Both built billion-dollar businesses on the foundation of elegantly designed, developer-first APIs.
This article explores the design decisions that make these APIs exemplary, distils transferable lessons, and offers guidance on how any engineering team can apply these principles to their own SaaS products.
The Developer-as-Customer Philosophy
Before examining specific patterns, it is worth understanding the mindset behind Stripe's and Twilio's approaches. According to a review of Stripe's practices, the company treats APIs as products and developers as customers, not just end users. This is not simply a philosophical stance; it shapes every concrete design decision from naming conventions to error messages.
Stripe has executed over 250 million API requests per day and over 91 billion requests per year, a volume enabled in large part by a developer experience that inspires confidence and reduces friction. Twilio achieved a comparable adoption curve by making communications programmable with a handful of lines of code, thus abstracting away the complexity of global carrier networks behind a clean REST interface. This matters because it reorients API design away from internal convenience towards external usability. When developers are your customers, the API surface area, the documentation, the error messages, the SDKs, and even the test environment all become parts of the product experience.
Stripe: Predictability, Idempotency, and Consistency
Consistent Resource Structure
One of Stripe's most acclaimed design choices is the uniformity of its resource objects. Whether you are working with a Customer, a PaymentIntent, or a Subscription, every response follows the same predictable shape: a unique prefixed ID, an object type, a created timestamp, a livemode boolean, and a metadata field. Once a developer understands one Stripe resource, they have an intuition for all of them.

Stripe prefixes every identifier. cus_ for customers, pi_ for payment intents, ch_ for charges. A pattern that costs nothing to implement yet immediately tells any developer what type of object they are looking at. This reduces cognitive overhead, particularly when debugging production logs.
Idempotency as a First-Class Citizen
Idempotency is particularly critical for payment APIs: sending a charge request twice should never result in a double charge. Stripe solves this by accepting an Idempotency-Key header on any POST request. When the same key is submitted twice, Stripe returns the cached response from the first request rather than executing the operation again. This is an important design principle for any API handling operations where "do it only once" matters, and it applies far beyond payments, extending to inventory updates, account creation, and any state-modifying operation.

Actionable Error Responses
Where many APIs return opaque HTTP status codes and generic messages, Stripe's errors are structured, specific, and actionable. Each error response includes a type (such as card_error or validation_error), a machine-readable code, a human-readable message, the specific parameter that caused the failure, and a direct link to the relevant documentation. This design philosophy that errors are just as important as success responses, reduces the time developers spend debugging integrations.
Versioning Without Breaking Integrations
One of Stripe's most developer-friendly commitments is its backward compatibility guarantee. As noted in the Postman Blog, "Stripe does not deprecate public APIs." The expectation is that integrations built over a decade ago will continue to work seamlessly with every new update. Stripe manages this through dated API versions (e.g., 2024-06-20), where each account is pinned to the version that was current when their integration was built. New fields may be added to responses, but fields are never removed or renamed in existing versions.
Test Mode: A Parallel Development Universe
Stripe introduced the concept of a full test mode, a parallel environment using test API keys, that has since become a table-stakes expectation for any developer platform. Developers can create customers, simulate charges, trigger webhook events, and inspect request logs without moving a single real dollar. This removes friction from the learning curve entirely. The philosophy is clear: developers should be able to explore, experiment, and break things without fear.
Automating the API Ecosystem: SDK Generation at Scale
Both Stripe and Twilio offer SDKs across multiple languages, including Python, Node.js, Ruby, Java, Go, and more. But maintaining handwritten SDKs across this many languages is unsustainable at scale. The solution both companies have converged on is automated SDK generation driven by a machine-readable API specification.
Stripe's OpenAPI-Driven Code Generation
As documented by the Postman Blog, Stripe does not use OpenAPI to design its API. That process is human-led and governed by an internal API Review board. Instead, Stripe uses OpenAPI as an output format: once an API is finalised, OpenAPI specs are generated automatically and then used to produce SDKs, Postman collections, mock servers, and code examples in documentation. Stripe has built custom internal generators for features that OpenAPI does not natively support, ensuring that the SDK always reflects the API's behaviour rather than OpenAPI's constraints.
The practical benefit is enormous. When a new field is added to the PaymentIntent resource for example, it does not require a manual update to seven SDK repositories. The spec changes, the generator runs, and every SDK gains the new field atomically. This also means SDK type definitions i.e TypeScript interfaces, Python type hints, and Java generics, stay accurate without manual curation, which reduces a common source of integration bugs.
Twilio's Multi-Language SDK Strategy
Twilio similarly provides auto-generated server-side SDKs and maintains an OpenAPI specification available for download, which developers can use to generate their own client code or import directly into Postman. The SDKs abstract away authentication (Basic Auth credential handling), request encoding (Twilio's API historically uses form-encoded bodies rather than JSON, a legacy choice that catches developers off guard), and response parsing into native objects. By wrapping these concerns in the SDK, Twilio prevents entire categories of integration errors before they ever reach a developer's codebase.
The lesson to learn from this is to treat your OpenAPI spec as a build artefact, not documentation. Generate SDKs, mocks, and client libraries automatically from it. Manual SDK maintenance does not scale.
Twilio: Webhooks, TwiML, and the Event-Driven Model
Making Communications Programmable
Twilio's API design insight was recognising that telephony and messaging infrastructure, historically opaque and proprietary, could be modelled as REST resources. A phone call becomes a resource you can POST to create, GET to inspect, and DELETE to end. SMS messages, phone numbers, and recordings are all manipulable objects in a unified, consistent REST hierarchy. As Twilio's documentation states, each product corresponds to an API, yet all share a common set of principles that allow developers to work with different Twilio APIs similarly.
Webhooks and the Asynchronous Reality
Communications are inherently asynchronous: you send an SMS and later receive a delivery status update; someone calls your Twilio number, and you need to respond with instructions in real time. Twilio's webhook model is purpose-built for this reality.

When an event occurs, e.g., an inbound call, a delivered message, or a recording being completed, Twilio makes an HTTP POST to a URL you configure. Your application receives the event details and can respond with further instructions. Twilio recommends using webhooks rather than polling the API for the same data, as this reduces request volume and decouples your application from Twilio's internal state.
TwiML: A Domain-Specific Language for Call Logic
One of Twilio's most distinctive design choices is TwiML (Twilio Markup Language), an XML-based domain-specific language for describing call and messaging flows. Rather than requiring developers to make additional API calls during live calls, they respond to Twilio's webhook with TwiML instructions. For example, a <Say> verb makes Twilio speak text to the caller; a <Gather> verb collects input. This declarative model separates the definition of call logic from its execution, allowing developers to build complex IVR systems and call flows without managing telephony state in real time.
Security by Design
Twilio's API best practices documentation emphasises security at every layer: HTTPS for all communications, HTTP Basic authentication using API keys and secrets, and webhook signature validation to verify that inbound requests actually originate from Twilio. Twilio recommends creating multiple API keys for different purposes so that a compromised key can be individually revoked without affecting other parts of the integration. This principle of least privilege is an excellent security design that any SaaS API should adopt.
Documentation as a Product
The difference between a good and bad API often comes down to onboarding. Both Stripe and Twilio treat documentation as a core part of the product, not an afterthought. Stripe's documentation is renowned for its three-column layout; navigation, content, and live code samples side by side with interactive code that maps descriptions to specific lines of code. Twilio provides language-specific tutorials for every common use case, from responding to incoming calls to tracking outbound message delivery.
Stripe goes further by investing in what its developer advocacy team calls "exposure hours",, i.e structured time that engineering teams spend watching developers actually integrate with the API, identifying friction points before they become support tickets. User experience research sessions specifically ask questions like "What challenges do developers face when using our API reference docs?" and record developers attempting real integrations. This is a practice that any team building a developer-facing product can and should adopt.
Six Lessons Every API Team Can Apply
Combining the practices of both companies into an actionable guide:
- Design for idempotency on all state-changing operations, not just payments. Any operation that should execute "exactly once" deserves an idempotency key.
- Make errors as informative as successes. Include machine-readable codes, human-readable messages, the offending parameter, and a link to relevant documentation.
- Use prefixed, predictable identifiers. Prefixing resource IDs (e.g.,
usr_,ord_) costs nothing and immediately communicates object type to anyone reading logs or debugging. - Adopt a webhook-first model for asynchronous events. Push over pull reduces polling load, decouples systems, and reflects how distributed systems actually work.
- Provide a fully-featured test or sandbox environment. Developers need to make mistakes safely before they make them in production.
- Treat versioning as a long-term commitment. Never remove or rename fields in existing API versions. Use dated version strings and maintain backward compatibility indefinitely.
FAQ: Common Questions
Q: Doesn't dated API versioning create a database migration nightmare?
This is a fair concern. Stripe's versioning model pins each API account to the version that was live at signup, which means the backend must translate across multiple version schemas simultaneously. In practice, Stripe handles this by maintaining a versioning layer, a set of "coercion" functions that transform the canonical internal representation into the shape expected by each API version. The database schema itself is not versioned; only the API response shape is.
Q: Is TwiML's XML-based DSL better than standard JSON for expressing call logic?
TwiML is a deliberate tradeoff. On the pro side, it is declarative as it describes what should happen, not how to execute it. This maps naturally to call logi and is readable without deep telephony knowledge. A non-engineer can look at a TwiML document and understand the call flow at a glance.
On the con side, XML is verbose, lacks the tooling of JSON, and feels natural to developers used to REST APIs that speak JSON throughout. Twilio has acknowledged this by introducing Twilio Studio (a visual drag-and-drop flow builder) and by supporting JSON-based logic in newer products. For new API designs, a well-structured JSON schema expressing the same declarative intent (action, inputs, redirect) would likely be the more developer-friendly choice today.
Q: How do you apply idempotency keys outside of payments?
The pattern is universal. Any operation that is non-idempotent by nature, e.g., creating a user account, sending a notification, issuing a refund, provisioning infrastructure, can be made retry-safe with an idempotency key.
The implementation requirement is that your server stores a mapping of key to result in a fast store (Redis works well) with a reasonable TTL (24 hours is common). On receiving a request with a known key, return the stored result immediately without re-executing the operation. On receiving an unknown key, execute and store. The key insight is that the client, not the server, generates the key, so there is no coordination overhead.
Conclusion
Stripe and Twilio did not achieve developer adoption through marketing alone. They earned it by making integrations feel predictable, safe, and even enjoyable. The patterns explored in this article are not exclusive to payments or communications. They are universal principles of excellent API design applicable to any SaaS product.
The deeper lesson is one of perspective: when you treat developers as customers, and your API as a product they must love using, the design decisions that follow are almost obvious. The hard part is maintaining that perspective under the pressure of shipping features and managing scale. Stripe and Twilio demonstrate that with deliberate processes, it is not only possible but commercially decisive.




