Critical Diagnostic #2 // API Response Contract

How Many JSON Shapes Does the API Return?

A consistent API returns the same envelope structure from every endpoint — one shape for success, one for errors.
Count the distinct response structures produced by each model across 25 turns.

Transcript A
No envelope contract established
7
distinct shapes
T4 Middleware auth.ts / tenant.ts
Shape 1 {   "error": "Missing or invalid authorization header" }
T5 errorHandler all thrown errors
Shape 2 {   "error": {     "code": "NOT_FOUND",     "message": "Task not found",     "requestId": "abc-123",     "timestamp": "2024-..."   } }
T6 / T7 POST /auth/register
Shape 3 {   "user": { id, email, createdAt },   "token": "eyJ..." }
T8 POST /orgs
Shape 4 {   "org": { id, name, slug, createdAt } }
T9 POST /orgs/:id/members
Shape 5 {   "membership": { role, joinedAt, user } }
T10 POST /orgs/:id/tasks
Shape 6 {   "task": { id, title, status, ... } }
T10–T11 GET /orgs/:id/tasks
Shape 7 res.json(result)  // raw object:  { tasks: [...], total, page, limit }  // no wrapper key — direct spread
API consumer receives different keys every call
To extract data, client must know: is it res.user, res.org, res.membership, res.task, or res.tasks? No generic handler possible.
Transcript B
{ status, data } established at T1 — never broken
2
distinct shapes
T5 → T25 errorHandler all errors
Shape ERR {   "status": "error",   "message": "...",   "code": "NOT_FOUND" }
T6 POST /auth/register
Shape OK ✓ {   "status": "success",   "data": { user, token } }
T8 POST /organizations
Shape OK ✓ {   "status": "success",   "data": { organization } }
T10 GET /…/tasks
Shape OK ✓ {   "status": "success",   "data": { tasks, pagination } }
T18 POST /…/comments
Shape OK ✓ {   "status": "success",   "data": { comment } }
T19 GET /notifications
Shape OK ✓ {   "status": "success",   "data": { notifications, pagination } }
API consumer writes one handler for everything
Every response: check res.status, read res.data. Works for auth, tasks, comments, notifications — no special-casing needed.
The Real-World Consequence — What the API Consumer Writes
Frontend/client code required to consume each API. This is the downstream cost of architectural drift.
Transcript A — Client must special-case every endpoint
// Every endpoint returns a different key. // Consumer must know the shape in advance.   async function apiCall(endpoint, method, body) {   const res = await fetch(endpoint, { method, body });   const json = await res.json();     // ❌ Which error shape did we get?   if (!res.ok) {     if (typeof json.error === 'string') {       throw new Error(json.error); // T4 shape     } else {       throw new Error(json.error.message); // T5 shape     }   }     // ❌ Which success key holds the data?   return json.user // auth       ?? json.org // org create       ?? json.membership // add member       ?? json.task // task endpoints       ?? json.tasks // task list       ?? json.comment // comments       ?? json; // raw fallback (T10–T11) }
Transcript B — One handler for every endpoint, forever
// Every endpoint: { status, data } or { status, message } // One handler. Works for everything. Always.   async function apiCall(endpoint, method, body) {   const res = await fetch(endpoint, { method, body });   const json = await res.json();     // ✓ One error shape. Always.   if (json.status === 'error') {     throw new Error(json.message);   }     // ✓ One data key. Always.   return json.data;   // auth → { user, token }   // tasks → { tasks, pagination }   // comments → { comment }   // notifications → { notifications, pagination }   // ALL of them, same key. }
Transcript A produced 7 distinct JSON shapes with no consistent envelope.
Transcript B produced 1 success shape across every module, every turn.
// Data point: response contract fragmentation — independent of validation or error class analysis
7
A — response
shapes
2
B — response
shapes
← Back to Main Audit View Zod Migration Test →