The 7 layers of a predictable API
Keyboard: ← → to move, F for full screen, O for overview.

Muhammet Şafak — Presentations
The 7 layers of a predictable API
An API that never surprises its client is the consistency of seven small decisions.
Muhammet Şafak

Flow
Four stops
- 01Resource and status codeLayers 1 and 2
- 02List endpointsLayer 3: pagination, filtering, sorting
- 03Response and errorLayers 4 and 5
- 04Version and contractLayers 6 and 7
Seven layers
Every layer is a consistency decision
- Resource
- The URL is noun-based and plural; the HTTP method carries the action.
- Status
- Success is 2xx, client error is 4xx, server error is 5xx.
- List
- page, per_page, sort, order: the same names on every endpoint.
- Envelope
- Every response has the same skeleton: success, data, message, errors.
- Error
- Errors come back as error.code, error.message, error.details.
- Version
- A breaking change calls for a new version.
- OpenAPI
- The contract is written before the code.
Layer 1 · Resource and method
The method carries the action, not the URL
| Method | Meaning | Example |
|---|---|---|
| GET | Read | GET /users/42 |
| POST | Create | POST /users |
| PUT | Replace entirely | PUT /users/42 |
| PATCH | Update partially | PATCH /users/42 |
| DELETE | Delete | DELETE /users/42 |
Layer 2 · Status code
The status code tells the outcome
| Class | Codes | Meaning |
|---|---|---|
| 2xx | 200 · 201 · 204 | Succeeded; created; no content |
| 4xx | 400 · 401 · 403 · 404 · 422 | Bad data, identity, permission, not found, validation |
| 5xx | 500 | Unexpected error on the server side |
Layer 3 · Pagination
The identity of a page
- 2current_pagelast_page: 15
- 20per_pageUpper limit: min($adet, 100)
- 21–40from – toRecords on this page
- 287totalTotal records
Example from the source · OFFSET
Deeper pages, costlier OFFSET.
To reach page ten thousand, the database has to read through and skip 200,000 records; one solution is cursor pagination.
Layer 4 · Response envelope
Every response has the same skeleton
The client reads the same four fields on every endpoint: success, data, message, errors.
1{2"success": true,3"data": {},4"message": null,5"errors": null6}Layer 4 · Response envelope
Lists use the same envelope
A paginated list comes back as data.items and data.meta; the envelope does not change.
1{2"success": true,3"data": {4 "items": [],5 "meta": {6 "current_page": 1,7 "per_page": 15,8 "total": 243,9 "last_page": 1710 }11},12"message": null,13"errors": null14}Layer 5 · Error contract
An error is a contract too
code is fixed for machines, message can change for humans, details carries extra context.
1{2"error": {3 "code": "VALIDATION_FAILED",4 "message": "Gönderilen veriler doğrulanamadı.",5 "details": [6 {7 "field": "email",8 "message": "Geçerli bir e-posta adresi giriniz."9 },10 {11 "field": "phone",12 "message": "Telefon numarası zorunludur."13 }14 ]15}16}Layers 4 and 5
Envelope and error rules
- Error with 200Returning 200 + "error": true for errors works but goes against REST; caching and logging break.
- FormatError codes in uppercase with underscores: QUOTA_EXCEEDED.
- CodesNew codes can be added, old codes should not be removed.
- ScaleIn a small service with a single client, a status code and a short message are enough.
Layer 6 · Versioning
Three strategies, URI preferred
| Strategy | Example | Trade-off |
|---|---|---|
| URI | GET /api/v1/users | Clearly visible in logs and proxies; multiplies URLs |
| Header | X-API-Version: 2 | URLs stay clean; a missing header creates ambiguity |
| Query | GET /api/users?version=2 | Practical at the start; proxies and CDNs may not see it |
Layer 6 · Versioning
At a basic level: which change calls for a new version?
Compatible
- Adding a new field to the response.
- Adding an optional parameter.
- Enriching error messages.
Breaking
- Removing or renaming an existing field.
- Adding a required parameter.
- Changing the response structure or the status code.
Deprecation
Give at least 6–12 months' notice.
For an API open to external clients, a deprecation period of at least 6-12 months is reasonable.
Layer 6 · RFC 8594 (2019) · RFC 9745 (2025)
Announce a version's lifespan with a header
| Header | Document | What it says |
|---|---|---|
Deprecation: @1780272000 | RFC 9745 | When it was deprecated; the value is a date, true is invalid |
Sunset: Sat, 31 Oct 2026 23:59:59 GMT | RFC 8594 | When it will stop responding |
Link: <…/docs/migrate-v2>; rel="deprecation" | RFC 9745 | The address of the migration guide |
Layer 7 · Contract-first
Contract first, code later
- 1Step 1: Contract
OpenAPI 3.0.3 - 2Step 2: Parallel work
backend · UI · test - 3Step 3: Mock
Prism - 4Step 4: Validation
contract testing - 5Step 5: Update
contract or code
Layer 7 · OpenAPI
The contract is written before the code
The schema excerpt of the example contract. The /orders endpoint restricts the status filter to pending, completed, cancelled.
1components:2schemas:3 Order:4 type: object5 required: [id, status, total]6 properties:7 id:8 type: integer9 status:10 type: string11 total:12 type: numberLayer 7
When does contract-first pay off?
- Small projectOne developer, small project: contract-first can be overkill.
- Many consumersWeb, mobile, third parties: it definitely saves time.
- Long lifeIn an API that will live for years, the contract is a decision record.

Thank you
Seven layers, seven promises of consistency. The details are in the six source posts.