I've spent six years designing APIs. I've written REST APIs. I've written GraphQL APIs. I've written an API that only returns XML because the client was a bank and banks believe XML is alive and well. It is not. But they're a bank and they have money, so: XML.
But the most fascinating API design philosophy I've ever encountered is one I'm calling API Design by Obscurity, or ADBO. I first observed it while integrating with a major social media platform's developer APIs. I will not name the company because (a) I'm still using their APIs and (b) their legal team is larger than our entire engineering department.
"The best documentation is no documentation. The second-best documentation is documentation that contradicts itself. The third-best is documentation from 2016 that references API versions that no longer exist."
Core Principles of ADBO
1. Version Everything, Explain Nothing
The API has versioned endpoints: /v1/, /v2.0/, /v2.1/, /v3.0/, /v3.2/, /v3.3/, /v14.0/, /v18.0/. The version numbers jump seemingly at random. There was no /v4/ through /v13/. Nobody knows what happened. The changelog for v14.0 says "various improvements" and links to a page that 404s.
2. The Response Shape Is a Suggestion
The documentation says a user object returns id, name, and email. The actual response returns id, name, something called name_format (undocumented), and a nested object called picture that contains a data object that contains a url field — but only if you request it via a fields parameter that you learned about from a Stack Overflow answer from 2017.
{
"id": "10228847293847",
"name": "Brad Kowalski",
"name_format": "{first} {last}",
"picture": {
"data": {
"height": 50,
"is_silhouette": false,
"url": "https://platform-lookaside.fbsbx.com/...",
"width": 50
}
},
"updated_time": "2014-03-15T00:00:00+0000"
}You requested three fields. You received six. Two are undocumented. The updated_time is from 2014 and does not correspond to any known event. This is ADBO working as intended.
3. Permissions Are a Labyrinth
To read a user's email, you need the email permission. Straightforward. To read their birthday, you need user_birthday. Also straightforward. To read their friends list, you need user_friends, which returns friends who also use your app, which is a subset so small it's functionally an empty array. The permission exists. The data does not.
To post on behalf of a user, you need publish_actions. This permission was deprecated in 2018. The documentation still references it. There is no replacement. There is a footnote that says "use the Share Dialog instead." The Share Dialog is a client-side component that cannot be called from a server. If you were doing server-side posting: sorry.
I spent four days implementing an integration with a permission that, it turned out, requires a manual review process that takes 6-8 weeks, during which your app is in a state the dashboard calls "In Review" but which I would call "purgatory." My app has been In Review since February. It is now June. I have moved on emotionally.
4. Rate Limits Are a Mystery
The documentation says you get "200 calls per hour per user." In practice, I've made 847 calls in an hour with no issues and gotten rate-limited after 12 calls in a different hour. The rate limit headers are present but the numbers don't correlate with observable behavior. My working theory is that the rate limiter is also a machine learning model and it's learning that I'm annoying.
5. Error Messages Are Poetry
{
"error": {
"message": "(#100) Tried accessing nonexisting field (email) on node type (User)",
"type": "OAuthException",
"code": 100,
"fbtrace_id": "A8dK3jH2LMNOP"
}
}The field email exists. It's in the documentation. It's in the Graph API Explorer. But it's a "nonexisting field" for my app because I don't have the email permission, which I do have, but which hasn't been approved yet, because my app is In Review. The error type is OAuthException, which implies an authentication problem, but authentication succeeded. The fbtrace_id is useless to me but presumably means something to someone at the company. I imagine it printed on a ticket in a queue that no one checks.
Why ADBO Works (Against All Reason)
Here's the paradox: the platform has millions of developers building on it. The APIs are the foundation of a massive ecosystem. They work — in the sense that if you invest enough time, read enough Stack Overflow answers, and accept that the documentation is more of a mood board than a reference, you can build functional integrations.
ADBO works because:
- The community is the documentation. The real API docs are on Stack Overflow, GitHub issues, and a developer forum where the top-voted answer to most questions is "this broke in v8.0 and was never fixed."
- Obscurity filters for commitment. Only dedicated developers make it through the permission review, rate limit mystery, and documentation archaeology. Those who survive are loyal. They have to be. They've invested too much to switch.
- Breaking changes create engagement. Every time the API changes without warning, thousands of developers post about it on social media. This is technically engagement.
I'm now designing all my APIs using ADBO principles. My first endpoint returns either a user object or a haiku, depending on the request timestamp. Documentation coming never.