Most developers spend a lot of time designing databases, infrastructure, and application architecture.
But surprisingly little time is spent designing something every client consumes:
The API response format.
A poorly designed response structure creates confusion, duplicate code, inconsistent error handling, and unnecessary bugs across frontend, mobile, backend, and QA teams.
Let's explore a practical approach to JSON API response design.
The Problem
Many APIs start like this:
{
"name": "John Doe",
"email": "john@example.com"
}
Seems fine.
Until an error occurs:
{
"error": "User not found"
}
Then another endpoint returns:
{
"success": false,
"message": "Invalid token"
}
And another one returns:
{
"status": "error",
"detail": "Permission denied"
}
Now every consumer needs special handling for every endpoint.
As the API grows, the problem grows with it.
A Consistent Response Envelope
A common pattern is using a standard wrapper around all responses.
Success:
{
"success": true,
"data": {
"id": 1,
"name": "John Doe"
}
}
Failure:
{
"success": false,
"error": {
"code": "USER_NOT_FOUND",
"message": "User not found"
}
}
This gives clients a predictable structure regardless of the endpoint.
Include Machine-Readable Error Codes
Avoid relying solely on error messages.
Bad:
{
"message": "Something went wrong"
}
Better:
{
"error": {
"code": "EMAIL_ALREADY_EXISTS",
"message": "Email already exists"
}
}
Frontend applications can now implement logic using the code instead of parsing text.
if (error.code === "EMAIL_ALREADY_EXISTS") {
showEmailError();
}
Messages may change.
Codes should not.
Return Metadata Separately
Avoid mixing business data with metadata.
Bad:
{
"users": [...],
"page": 1,
"total": 500,
"limit": 20
}
Better:
{
"data": [...],
"meta": {
"page": 1,
"limit": 20,
"total": 500
}
}
This becomes especially useful when implementing pagination, filtering, sorting, and analytics.
Timestamps Should Be ISO 8601
Bad:
{
"created_at": "06/21/2026 14:30"
}
Good:
{
"created_at": "2026-06-21T14:30:00Z"
}
ISO 8601 avoids timezone confusion and is supported by nearly every platform.
Avoid Null When Possible
Many APIs return:
{
"items": null
}
Now clients must handle:
- null
- undefined
- empty array
Instead:
{
"items": []
}
An empty collection is often easier to reason about than a nullable collection.
Pagination Should Be Explicit
A paginated response should provide enough information for clients to continue fetching.
{
"data": [...],
"meta": {
"page": 2,
"limit": 20,
"total": 100,
"has_next": true
}
}
Or cursor-based pagination:
{
"data": [...],
"next_cursor": "eyJpZCI6MTIzfQ=="
}
This avoids guesswork on the client side.
Don't Leak Internal Errors
Never expose raw stack traces.
Bad:
{
"error": "SQLSTATE[23000]: Integrity constraint violation..."
}
Better:
{
"error": {
"code": "INTERNAL_SERVER_ERROR",
"message": "Something went wrong"
}
}
Log details internally.
Return safe information externally.
Versioning Matters
APIs live longer than most developers expect.
Instead of breaking existing clients:
/api/v1/users
/api/v2/users
or
Accept: application/vnd.company.v2+json
A versioning strategy saves painful migrations later.
Example Final Response Format
Success:
{
"success": true,
"data": {
"id": 1,
"name": "John Doe"
},
"meta": {
"request_id": "req_123456"
}
}
Error:
{
"success": false,
"error": {
"code": "USER_NOT_FOUND",
"message": "User not found"
},
"meta": {
"request_id": "req_123456"
}
}
Notice the request_id.
When a customer reports an issue, support can immediately locate the exact request in logs.
Final Thoughts
A good API response format is not about aesthetics.
It is about reducing complexity across every consumer of the API:
- Frontend applications
- Mobile applications
- QA automation
- Integrations
- AI agents
- Future developers
The best API responses are boring, predictable, and easy to reason about.
Your future teammates will thank you for it.
Tip: When designing or debugging APIs, inspect the actual HTTP traffic instead of relying only on application logs. Seeing the raw request and response often reveals inconsistencies, missing fields, incorrect status codes, and serialization issues much faster.
Top comments (0)