The Documentation Paradox
I have spent more hours reading API documentation than I care to admit. Some of that time was productive. Most of it was not. The pattern is always the same: a team builds an API, realizes nobody can figure out how to call it, and then scrambles to write docs that explain what should have been obvious from the code itself. Here is my position, stated plainly: if your API requires a 400-page documentation site before a developer can make their first successful request, your API is badly designed. The best APIs are the ones where the structure, naming, and behavior are so clear that the code itself is the documentation.
This is not an argument against documentation. Reference material has its place for edge cases, authentication flows, and architectural overviews. But the default path—the happy path that 90% of consumers will follow—should be discoverable without reading anything. If it is not, the fault lies with the interface, not with the developer trying to use it.

What “Self-Documenting” Actually Means
Self-documenting is not magic. It is the result of applying specific design constraints consistently. An API that does not need documentation achieves three things:
1. Predictable Endpoints
The URL structure tells you what resource you are interacting with and what operation you are performing. Consider the difference between these two designs:
// Bad: You need docs to know this is a user creation endpoint
POST /api/v2/resource?action=create&type=user
// Good: The URL says exactly what is happening
POST /api/v2/users
The first example requires you to know that action is a query parameter, that create is the valid value, and that type specifies the resource. None of this is guessable. The second example follows the REST convention that a POST to a collection creates a new member. Any developer with basic REST knowledge can predict this without reading a single line of documentation.
2. Consistent Naming
Names in your API should follow the same conventions everywhere. If you use created_at in one place, do not use createdAt in another. If a field is called user_id in the request, it should not be called account_id in the response when it refers to the same entity.
// Bad: Same concept, three different names
// Request body
{ "user_identifier": "abc123" }
// Response body
{ "userId": "abc123", "account_ref": "abc123" }
// Good: One concept, one name
// Request body
{ "user_id": "abc123" }
// Response body
{ "user_id": "abc123" }
This seems obvious. It is not. I have worked with APIs from major technology companies where the same field had different names depending on whether it appeared in a request body, a response body, or a query parameter. That inconsistency forces developers to keep documentation open at all times, because nothing can be assumed.
3. Sensible Defaults
Every optional parameter should have a default that works for the common case. If calling an endpoint with no parameters returns a reasonable result, developers can experiment without reading configuration guides.
// Bad: Requires knowing page size, sort order, and filter syntax
GET /api/v2/users?page=1&per_page=25&sort=last_name:asc&filter=status:active
// Better: Defaults handle the common case
GET /api/v2/users
// Returns first 25 active users sorted by last name ascending
// Documentation is for people who need to change those defaults

Code Examples as Arguments
Let me make this concrete. Stripe’s API is widely praised for its design. Here is why: you can make a working integration by guessing. The endpoints follow predictable patterns, the request and response bodies use consistent naming, and the error messages tell you exactly what went wrong and how to fix it.
// Creating a charge with Stripe—you could guess this
POST /v1/charges
{
"amount": 2000,
"currency": "usd",
"source": "tok_visa",
"description": "Charge for example@example.com"
}
// The response mirrors the request structure
{
"id": "ch_1A9ZXYZ",
"object": "charge",
"amount": 2000,
"currency": "usd",
"source": { ... },
"description": "Charge for example@example.com",
"status": "succeeded"
}
Now compare this with a fictional but realistic badly designed payment API:
// You would never guess this
POST /v2/financial/transactions/debit/init
{
"txn_amount": 2000,
"curr_cd": "USD",
"payment_token_id": "tok_visa",
"memo_text": "Charge for example@example.com"
}
// The response uses completely different field names
{
"transaction_ref": "TXN-29381",
"type": "DEBIT",
"value": 2000,
"currencyCode": "USD",
"paymentMethod": { "token": "tok_visa" },
"notes": "Charge for example@example.com",
"state": "PROCESSED"
}
The second API forces you to memorize that txn_amount becomes value, curr_cd becomes currencyCode, memo_text becomes notes, and the status is called state with values like PROCESSED instead of succeeded. Every one of those translations is a point of failure that documentation has to bridge. Stripe’s reference documentation exists, but it confirms what you already suspect rather than revealing what you could never guess.
Principles of Discoverable APIs
Follow Convention Over Configuration
Use the conventions of your protocol and format. REST APIs should use HTTP methods correctly: GET to read, POST to create, PUT or PATCH to update, DELETE to remove. URL paths should be nouns, not verbs. The HTTP status code should tell you the outcome. If you follow these rules, a developer who knows HTTP can use your API without learning your specific dialect.
// This is a verb in the URL—fight the urge
POST /api/v2/users/123/activate
// This uses the protocol correctly
PATCH /api/v2/users/123
{ "status": "active" }
Make Errors Explain Themselves
A well-designed error response is worth more than a documentation page. When something goes wrong, the API should tell you what the problem was, what value caused it, and what valid values look like.
// Bad: Mystery error
{
"error": "INVALID_REQUEST",
"message": "The request was invalid."
}
// Good: The error teaches you the correct usage
{
"error": "INVALID_PARAMETER",
"message": "The value 'abc' is not valid for 'per_page'. Must be an integer between 1 and 100.",
"parameter": "per_page",
"provided_value": "abc",
"valid_range": { "min": 1, "max": 100, "type": "integer" }
}
The second error response makes the endpoint self-correcting. A developer who hits this error can fix their code immediately, without switching to a browser to look up parameter constraints.
Use Hypermedia Links
HATEOAS—Hypermedia as the Engine of Application State—is not just an academic REST constraint. It is a practical technique for making APIs explorable. When the response includes links to related actions and resources, the API becomes its own navigation system.
{
"id": "order_123",
"status": "pending",
"links": {
"self": "/api/v2/orders/order_123",
"cancel": "/api/v2/orders/order_123/cancel",
"confirm": "/api/v2/orders/order_123/confirm",
"receipt": "/api/v2/orders/order_123/receipt"
}
}
A developer reading this response immediately knows what actions are available on this order. They do not need to consult a state diagram or a routes reference. The API surface reveals itself through usage, not through external documentation.

The Cost of Documentation-Dependent APIs
When an API cannot be used without documentation, the cost is real and measurable. Onboarding time increases because new developers must read before they can act. Integration bugs multiply because documentation drifts out of sync with implementation—a well-documented problem in API design literature. Support burden grows because every unclear endpoint generates tickets. And the API becomes fragile, because any change requires not just code updates but documentation updates across multiple formats and locations.
I have seen teams spend weeks writing and maintaining OpenAPI specifications, generating static documentation sites, and building interactive API explorers—all to compensate for interfaces that were not designed to be understood on their own. That effort would be better spent redesigning the API so that the specification is obvious from the behavior.
When Documentation Is Necessary
To be fair, there are cases where external documentation adds genuine value. Authentication and authorization flows often require explanation because they involve external systems and security constraints that cannot be expressed in the API structure alone. Webhook delivery and retry logic need documentation because the consumer is implementing the server, not the client. Rate limiting policies and quota rules need documentation because they are policy decisions, not interface design.
The point is not that documentation should not exist. The point is that documentation should be supplementary, not foundational. A developer should be able to achieve basic functionality by reading the API responses and error messages, then turn to documentation only for the non-obvious parts.
FAQ
Does this mean I should not write documentation at all?
No. Write documentation. Write good documentation. But treat documentation as a supplement to a well-designed interface, not a substitute for one. If you find that your documentation is explaining things that should be obvious—like what a field means or what an endpoint does—redesign the API so those things are self-evident. Use documentation for the parts that genuinely need explanation: business rules, authentication flows, integration patterns, and architectural decisions.
How do I test whether my API is self-documenting?
Give the API base URL and an authentication token to a developer on your team who has never seen the API. Ask them to perform a few common tasks without providing any documentation. Watch where they get stuck, what they guess correctly, and what they cannot figure out. Those stuck points are your design failures. Fix the API, not the onboarding instructions.
What about APIs that have complex domain logic?
Domain complexity is real, and no amount of naming convention will make a tax calculation API obvious to someone who does not understand tax rules. But domain complexity is different from interface complexity. You can present a complex domain through a simple, predictable interface. The fields may need explanation, but the structure, naming patterns, and error handling should not. Separate what is inherently complex about the domain from what is accidentally complex about your design, and fix the latter.
The Standard You Should Aim For
An API that requires its consumers to keep a documentation tab open at all times is an API that has failed at its most basic job: being usable. The best interfaces—whether they are graphical user interfaces or programmatic APIs—are the ones where you can figure out the next step from the current state. Your API endpoint should tell me what data I need to provide. Your error messages should tell me what I did wrong. Your response bodies should tell me what I can do next.
When you achieve that, documentation becomes what it should be: a helpful reference for edge cases, not a survival guide for the common case. Design your API so that a competent developer can integrate with it by making requests and reading responses. If they cannot, the problem is not that they skipped the docs. The problem is that your API speaks a language only you understand.