Welcome! This site hosts the JsonDispatch specification and examples.
- What is it? A lightweight, production-ready JSON response spec.
- Why use it? Stable envelope (
success
/fail
/error
), server-generated tracing withX-Request-Id
and clear version signaling viaX-Api-Version
.
1. Introduction
1.2 Why Another Spec?
1.3 Core Principles
2. Media Types & Versioning
2.1 Media Type Explained (with Examples)
2.2 Versioning Strategy (Major/Minor/Patch)
2.3 Required & Recommended Headers
2.4 Version Selection & Migration
2.5 Non-JSON Responses (Reports & Exports)
2.5.1 Direct File Delivery
2.5.2 Orchestration via JsonDispatch (Recommended)
2.5.3 Streaming & Large Datasets
3. Request & Response Identification
3.1 X-Request-Id – Server-Generated Trace ID
3.2 X-Correlation-Id – Linking Related Operations
3.3 Distributed Tracing (traceparent, tracestate)
4. Response Envelope (The Outer Wrapper)
4.1 Top-Level Members at a Glance
4.2 status
— Success, Fail or Error
4.3 message
— Keep It Human-Friendly
4.4 data
— Your Actual Payload
4.5 _references
— Turning IDs Into Meaning
4.6 _properties
— Describing Data Context
4.7 _links
— Pagination and Beyond
4.8 Envelope Summary
5. Response Examples
5.1 A Simple Success Response
5.2 A Fail Response (Validation Issue)
5.3 An Error Response (System Failure)
5.4 Paginated Collection Response
5.5 References in Action
6. Error Handling
6.1 fail
vs error
— Know the Difference
6.2 Error Object Structure
6.3 Field-Level vs Request-Level Errors
6.4 Error Codes (code
) — Symbolic and Actionable
6.5 Recommended HTTP Status Mapping
6.6 Key Takeaways
7. Properties & References
7.1 _properties
— Describe Your Data
7.2 _references
— Replace IDs with Meaning
7.3 Choosing Between Them
7.4 Example — Combined in One Response
7.5 Nested _references
Example
8. Links
8.1 _links
for Pagination
8.2 _links
for Related Resources
8.3 _links
with Metadata (Enriched Links)
8.4 Best Practices for _links
8.5 _links
for Files and Downloads
8.6 _links
for Inline Media (Thumbnails, Avatars, Previews)
9. Compatibility & Evolution
9.1 Core Compatibility Rules
9.2 How to Introduce Breaking Changes
9.3 Recommended Evolution Workflow
10. Best Practices
10.1 Always Log X-Request-Id
10.2 Deprecation Lifecycle (Field or Endpoint Evolution)
10.3 Response Media Types & Fallbacks
10.4 Security Headers & CORS Policy Recommendations
10.5 Use _properties
and _references
Generously
10.6 Operational Logging, Correlation, Monitoring & Security
11. Appendix
11.1 Reserved Headers (Response Only)
11.2 Reserved Keywords (Top-Level JSON Keys)
11.3 Middleware & Utility Patterns
11.4 Minimal JSON Schema for the Envelope (Dev Tooling)
11.5 Example cURL Requests (Version & Headers)
11.6 Developer Notes
JsonDispatch is a lightweight API response specification built on top of JSON. It defines a predictable, flexible response envelope for REST APIs so clients always know where to look for the status, data and helpful metadata.
Think of it as the contract between your backend and your clients (mobile, web, services). Instead of every project reinventing its own shape, JsonDispatch gives you:
- Consistency — The same envelope across all endpoints
- Traceability — Every response carries a server-generated
X-Request-Id
- Clarity — Clean separation between
success
,fail
anderror
- Flexibility — Optional
_references
,_properties
and_links
for richer responses
If you've worked with APIs before, you've probably seen:
{ "ok": true }
here{ "status": "success", "payload": … }
there- And somewhere else… a raw stack trace in JSON. 😬
This chaos makes it hard to build generic clients, reason about failures and correlate logs across services. JsonDispatch standardizes the response shape while staying practical and easy to adopt in real systems.
JsonDispatch is built around a few simple rules:
Responses evolve, but we don't break clients. Deprecate fields instead of deleting them.
The server must generate and return a unique X-Request-Id
on every response (clients don't send it). This makes correlation and debugging straightforward.
success
→ Everything workedfail
→ The request was invalid (validation, preconditions, etc.)error
→ The server or a dependency failed
_references
→ Turn IDs into human-friendly values_properties
→ Describe the data shape, pagination and deprecations_links
→ Make collections navigable
- Response carries
X-Api-Version
(full SemVer) — clients can log and reason about the exact server implementation. Accept
staysapplication/json
— clients don't need custom accept negotiation to consume JsonDispatch.
As your API evolves, clients need a stable way to understand which shape they're getting and how to migrate when it changes. JsonDispatch solves this by versioning request media types and signaling the exact server version in a response header.
For requests with a body, set a JsonDispatch vendor media type in Content-Type
:
Content-Type: application/vnd.infocyph.jd.v1+json
application
→ Application payload (static)vnd.infocyph
→ Your vendor namespace (configurable)jd
→ JsonDispatch spec identifier (static for this spec)v1
→ Major version of the request format (configurable)+json
→ JSON syntax suffix (static)
Responses can simply use
Content-Type: application/json
while still following the JsonDispatch envelope.
- Project "Acme":
application/vnd.acme.jd.v1+json
- Personal/OSS (not recommended for org APIs):
application/prs.yourname.jd.v1+json
JsonDispatch follows semantic Versioning:
- Major (v1 → v2): Breaking changes to the envelope or field types
- Minor (v1.1 → v1.2): Backward-compatible additions
- Patch (v1.0.0 → v1.0.1): Backward-compatible bug fixes
Key point: The request media type carries the major version:
…jd.v1+json
. The server's full version is inX-Api-Version
(e.g.,1.3.0
).
- Do not use
Accept
for version negotiation; keep itapplication/json
if you send it at all.
-
MUST send:
Content-Type: application/vnd.<vendor>.jd.v<MAJOR>+json
Example:
Content-Type: application/vnd.infocyph.jd.v1+json
-
SHOULD NOT use
Accept
for versioning. If present, keep it:Accept: application/json
-
MUST send:
X-Api-Version: <MAJOR.MINOR.PATCH>
Example:
X-Api-Version: 1.4.0
-
MAY send:
Content-Type: application/json
Note: Servers can still set a vendor media type on responses if needed, but
application/json
is sufficient and preferred.
X-Request-Id
,X-Correlation-Id
and other tracing headers are covered in Section 3.
- The server controls the response envelope version. Clients do not negotiate via
Accept
. - When you introduce a breaking change, bump the request major (e.g.,
v1 → v2
) and keep returningapplication/json
in the response with an updatedX-Api-Version
.
Create (client → server)
POST /articles
Content-Type: application/vnd.infocyph.jd.v1+json
{ "title": "Hello JD" }
Response (server → client)
HTTP/1.1 201 Created
Content-Type: application/json
X-Api-Version: 1.4.0
X-Request-Id: 1f1f6a2e-0c8f-4f7f-9b4b-5d2d0a3f5b99
{
"status": "success",
"data": { ... }
}
After a breaking change (client updates request major):
POST /articles
Content-Type: application/vnd.infocyph.jd.v2+json
{ "title": "Hello JD v2" }
Server response
HTTP/1.1 201 Created
Content-Type: application/json
X-Api-Version: 2.0.0
X-Request-Id: 6c2a3d7e-9d5a-4b61-9b05-d1d8f6a8f4a1
{
"status": "success",
"data": { ... }
}
Clients signal request major via Content-Type
(when they send a body) and servers announce the exact
implementation via X-Api-Version
in the response. No Accept
gymnastics and responses stay plain
application/json
.
JsonDispatch defines the response envelope for JSON. For binary or tabular deliverables (CSV, PDF, ZIP, images), return the native media type directly and keep JsonDispatch for orchestration endpoints (job creation, metadata, links).
When the endpoint returns a file stream:
Request
GET /reports/activity/download?from=2025-09-01&to=2025-09-30
Accept: text/csv
Response
HTTP/1.1 200 OK
Content-Type: text/csv
Content-Disposition: attachment; filename="activity-report-2025-09.csv"
X-Api-Version: 1.4.0
X-Request-Id: 7bfae01e-771c-41d4-b1cf-0ad52d8bce19
Use the correct
Content-Type
(e.g.,text/csv
,application/pdf
,application/zip
) and addContent-Disposition
for downloads.
Create the report asynchronously and return a JsonDispatch envelope with status and links:
Request
POST /reports/activity
Content-Type: application/vnd.infocyph.jd.v1+json
{ "from": "2025-09-01", "to": "2025-09-30", "format": "csv" }
Response
HTTP/1.1 202 Accepted
Content-Type: application/json
X-Api-Version: 1.4.0
X-Request-Id: 3b2c7a5a-0b2e-4b69-b1a2-1a2c3d4e5f6a
Body
{
"status": "success",
"message": "Report job accepted",
"data": {
"report_id": "rep_9KfGH2",
"state": "queued",
"format": "csv",
"expires_at": "2025-10-31T23:59:59Z"
},
"_links": {
"self": "https://api.example.com/reports/activity/rep_9KfGH2",
"download": {
"href": "https://cdn.example.com/reports/rep_9KfGH2.csv",
"meta": { "method": "GET", "expires": "2025-10-31T23:59:59Z" }
}
},
"_properties": {
"data": { "type": "object", "name": "report" }
}
}
For very large exports, consider streaming formats:
- CSV/TSV stream:
text/csv
- NDJSON stream:
application/x-ndjson
- Gzip: add
Content-Encoding: gzip
when appropriate
Pair streaming/download endpoints with a JsonDispatch status endpoint so clients can poll readiness and discover _links.download
.
Status polling
GET /reports/activity/rep_9KfGH2
Accept: application/json
Response (ready)
HTTP/1.1 200 OK
Content-Type: application/json
X-Api-Version: 1.4.0
X-Request-Id: d1c9b15f-9c1e-42d5-9a9e-8b8e5a2b1c0a
Body
{
"status": "success",
"data": {
"report_id": "rep_9KfGH2",
"state": "ready",
"size_bytes": 9421331
},
"_links": {
"download": "https://cdn.example.com/reports/rep_9KfGH2.csv",
"retry": "https://api.example.com/reports/activity/rep_9KfGH2/retry"
}
}
Rule of thumb: Use JsonDispatch for control, status and discovery; use native media types for the actual file bytes.
In production, the hardest bugs are the ones you can’t trace. JsonDispatch enforces consistent tracing identifiers on every response so developers can connect logs, monitor latency and debug across distributed systems — automatically.
Every response must include a globally unique request identifier generated by the server. Clients do not send this header.
Rules
- Generated once per inbound request (UUID v4, ULID or equivalent unique token).
- Must be a string.
- Included in all responses, including errors.
- Logged internally for correlation in monitoring and debugging.
Example
Client → Server
GET /articles
Accept: application/json
Server → Client
HTTP/1.1 200 OK
Content-Type: application/json
X-Api-Version: 1.4.0
X-Request-Id: 7e0e7b45-1e89-4a7f-bbd3-f7ac73fae951
👉 When a user reports an issue, the X-Request-Id
from the response can be searched in logs to reconstruct the full
execution path.
When multiple requests belong to the same business operation (e.g., checkout, batch processing, workflow chains), a shared correlation ID links them together.
Rules
- Optional.
- May be provided by a client or generated at the workflow entry point.
- The server echoes it in the response and propagates it to any downstream service calls.
- Must be a string, unique per logical workflow.
Example
Client → Server
POST /checkout
Content-Type: application/vnd.infocyph.jd.v1+json
X-Correlation-Id: order-2025-10-05-777
{ "cartId": "C10045" }
Server → Client
HTTP/1.1 201 Created
Content-Type: application/json
X-Api-Version: 1.4.0
X-Request-Id: 019fb440-4e83-4b1b-bef9-44a80771f181
X-Correlation-Id: order-2025-10-05-777
If the checkout flow triggers downstream services (e.g., payments, shipping), each internal call must reuse the same correlation ID.
For systems instrumented with distributed-tracing tools such as OpenTelemetry, Jaeger or Zipkin, JsonDispatch is compatible with the W3C Trace Context.
These headers complement (not replace) JsonDispatch identifiers:
traceparent
→ carries trace ID and span ID.tracestate
→ vendor-specific trace metadata.
Example
GET /profile
Accept: application/json
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
tracestate: congo=t61rcWkgMzE
Response
HTTP/1.1 200 OK
Content-Type: application/json
X-Api-Version: 1.4.0
X-Request-Id: 1b0c9d4b-eaa2-40d0-8715-fc93e6fefb99
X-Correlation-Id: session-998877
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
tracestate: congo=t61rcWkgMzE
Summary
Header | Direction | Purpose | Required |
---|---|---|---|
X-Request-Id |
Response → | Unique server-generated request trace ID | ✅ Yes |
X-Correlation-Id |
Both ↔ | Link related requests in a workflow | ⚪ Optional |
X-Api-Version |
Response → | Server JsonDispatch version identifier | ✅ Yes |
traceparent |
Both ↔ | Distributed tracing (W3C) | ⚪ Optional |
tracestate |
Both ↔ | Vendor-specific tracing metadata | ⚪ Optional |
Every JsonDispatch response is wrapped in a predictable, minimal envelope. This gives every API the same shape — regardless of what the data is — making client logic simpler and responses easier to debug.
The envelope helps you quickly understand:
- what happened (
status
,message
) - what’s returned (
data
) - how to interpret it (
_properties
) - how to resolve references (
_references
) - how to navigate further (
_links
)
A JsonDispatch response body may contain these top-level members:
Key | Type | Required | Purpose |
---|---|---|---|
status |
string | ✅ | Overall result — success , fail or error |
message |
string | ⚪ | Short, human-readable explanation |
data |
mixed | ⚪ | Main payload (object, array or scalar) |
_references |
object | ⚪ | ID-to-label mapping dictionary |
_properties |
object | ⚪ | Metadata describing structure, count or schema |
_links |
object | ⚪ | Navigation links (pagination, related resources) |
⚪ = optional, depending on the
status
value and context.
The status
field defines the overall outcome of the request.
success
→ The request completed successfully and data is returned.fail
→ The client provided invalid input (validation, missing fields, etc.).error
→ The server or an external dependency failed to process the request.
Example
{
"status": "success",
"data": {
"id": 42,
"title": "JsonDispatch in Action"
}
}
message
is a short sentence for humans (not parsers).
Use it as a quick, meaningful summary in logs or UI alerts.
Context | Example message |
---|---|
success | "Article fetched successfully" |
fail | "Invalid email address" |
error | "Payment service unavailable" |
Everything your API returns lives under data
.
The format should remain consistent for the same (version, method and endpoint)
combination.
Status | Expected data type |
Description |
---|---|---|
success | object / array / any | The requested resource(s). |
fail | array | List of validation issues. |
error | array | List of system or dependency errors. |
Example — Success Payload
"data": {
"type": "article",
"attributes": {
"id": 42,
"title": "JsonDispatch in Action",
"category": 1
}
}
Instead of making clients hard-code enums or category lookups, _references
provides an instant dictionary for ID
resolution.
Example
{
"_references": {
"category": {
"1": "News",
"2": "Tutorial",
"3": "Opinion"
}
}
}
Now clients can map "category": 2
→ “Tutorial” without additional API calls.
_properties
gives structure-level metadata that describes your payload — useful for UI builders, pagination or
deprecation notices.
Common keys include:
Key | Type | Purpose |
---|---|---|
type |
string | Resource type (array , object , etc.) |
name |
string | Logical name of the resource |
count |
int | Total item count (if paginated) |
page |
int | Current page number (if applicable) |
range |
string | Item range in current response (e.g., "21–40" ) |
template |
url | Optional schema or structure reference |
deprecation |
url | Optional migration or deprecation notice |
Example
"_properties": {
"data": {
"type": "array",
"name": "articles",
"count": 20,
"page": 2,
"range": "21–40",
"deprecation": "https://api.example.com/docs/v2/articles"
}
}
_links
makes your API navigable.
It can include pagination links, related resources or documentation references.
Example — Pagination
{
"_links": {
"self": "https://api.example.com/articles?page=2",
"next": "https://api.example.com/articles?page=3",
"prev": "https://api.example.com/articles?page=1"
}
}
Example — Related Resources
{
"_links": {
"self": "https://api.example.com/articles/42",
"author": "https://api.example.com/users/99",
"comments": "https://api.example.com/articles/42/comments"
}
}
Section | Purpose | Optional |
---|---|---|
status |
Defines success/fail/error outcome | ❌ No |
message |
Human-readable summary | ⚪ Yes |
data |
Payload or error details | ⚪ Yes |
_references |
Lookup tables for enums or IDs | ⚪ Yes |
_properties |
Metadata about the response | ⚪ Yes |
_links |
Pagination or relational navigation | ⚪ Yes |
👉 Together, these create a consistent, machine-parsable yet human-friendly response pattern across all JsonDispatch APIs.
Examples are the fastest way to understand JsonDispatch. Below are common scenarios — success, fail, error and paginated responses — exactly as they should appear in production.
Request
GET /articles/42
Accept: application/json
Response
HTTP/1.1 200 OK
Content-Type: application/json
X-Api-Version: 1.3.1
X-Request-Id: aabbccdd-1122-3344-5566-77889900aabb
Body
{
"status": "success",
"message": "Article fetched successfully",
"data": {
"type": "article",
"attributes": {
"id": 42,
"title": "JsonDispatch in Action",
"category": 2
}
},
"_references": {
"category": {
"1": "News",
"2": "Tutorial",
"3": "Opinion"
}
}
}
Request
POST /articles
Content-Type: application/vnd.infocyph.jd.v1+json
Accept: application/json
Body
{
"title": "Hi",
"category": 5
}
Response
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
X-Api-Version: 1.3.1
X-Request-Id: f4b44a6e-d593-11ec-9d64-0242ac120002
Body
{
"status": "fail",
"message": "Validation failed",
"data": [
{
"status": 422,
"source": "/data/attributes/title",
"title": "Title too short",
"detail": "The title must be at least 5 characters long."
},
{
"status": 422,
"source": "/data/attributes/category",
"title": "Invalid category",
"detail": "Category must be one of: 1, 2, 3."
}
]
}
Request
GET /articles
Accept: application/json
Response
HTTP/1.1 503 Service Unavailable
Content-Type: application/json
X-Api-Version: 1.3.1
X-Request-Id: c043e23a-4b26-4a05-96c4-5c60fcc18d50
Body
{
"status": "error",
"message": "Temporary backend outage",
"code": "ARTICLES_SERVICE_DOWN",
"data": [
{
"status": 503,
"source": "articles-service",
"title": "Service unavailable",
"detail": "The Articles microservice is currently offline."
}
]
}
Request
GET /articles?page=2&limit=3
Accept: application/json
Response
HTTP/1.1 200 OK
Content-Type: application/json
X-Api-Version: 1.3.1
X-Request-Id: 77aa88bb-ccdd-eeff-0011-223344556677
Body
{
"status": "success",
"message": "Articles listed successfully",
"data": [
{
"type": "article",
"attributes": {
"id": 4,
"title": "Scaling JsonDispatch",
"category": 1
}
},
{
"type": "article",
"attributes": {
"id": 5,
"title": "Error Handling Patterns",
"category": 3
}
},
{
"type": "article",
"attributes": {
"id": 6,
"title": "Backward Compatibility Rules",
"category": 2
}
}
],
"_properties": {
"data": {
"type": "array",
"name": "articles",
"count": 3,
"page": 2,
"range": "4–6"
}
},
"_links": {
"self": "https://api.example.com/articles?page=2&limit=3",
"next": "https://api.example.com/articles?page=3&limit=3",
"prev": "https://api.example.com/articles?page=1&limit=3"
},
"_references": {
"category": {
"1": "News",
"2": "Tutorial",
"3": "Opinion"
}
}
}
Clients no longer need to fetch category labels from another endpoint:
"attributes": {
"id": 42,
"title": "JsonDispatch in Action",
"category": 2
}
Can be immediately resolved using _references
:
"_references": {
"category": {
"1": "News",
"2": "Tutorial",
"3": "Opinion"
}
}
→ category: "Tutorial"
✅ Key Takeaways
- Responses always have
Content-Type: application/json
. X-Api-Version
andX-Request-Id
are generated by the server, not clients.- The envelope is consistent — clients only need to check
status
and readdata
.
Errors are where consistency matters most.
If every service formats errors differently, debugging turns into chaos.
JsonDispatch enforces a clean, uniform structure for client-side issues (fail
) and server-side issues (
error
) — so you always know where the problem came from.
Type | Cause | Who Fixes It | Typical HTTP Status | Example |
---|---|---|---|---|
fail |
The client sent something invalid | The client | 400 – 422 | Missing required fields, invalid format |
error |
The server or an external dependency failed | The server | 500 – 504 | Timeout, DB crash, network outage |
Think of it like this:
- 🧑💻 fail → “Fix your request and try again.”
- 🧩 error → “It’s not you, it’s us.”
This split helps:
- Clients decide whether to retry or correct their data.
- Developers log and alert on real outages separately from validation noise.
Both fail
and error
responses contain an array of objects under data
.
Each describes one issue in a standard format:
Field | Type | Description |
---|---|---|
status |
integer | HTTP status code for this error |
source |
string | Where the problem occurred — field path or subsystem name |
title |
string | Short summary of the problem |
detail |
string | Human-readable explanation for logs or UI |
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
X-Api-Version: 1.3.1
X-Request-Id: 9d2e7f6a-2f3b-45b0-9ff2-dc3d99991234
{
"status": "fail",
"message": "Validation failed",
"data": [
{
"status": 422,
"source": "/data/attributes/email",
"title": "Invalid email",
"detail": "Email must be a valid address."
},
{
"status": 422,
"source": "/data/attributes/password",
"title": "Password too short",
"detail": "Password must be at least 8 characters."
}
]
}
HTTP/1.1 503 Service Unavailable
Content-Type: application/json
X-Api-Version: 1.3.1
X-Request-Id: e13a97b3-5a2d-46db-a3b2-f401239b0cba
{
"status": "error",
"message": "Database unavailable",
"code": "DB_CONN_TIMEOUT",
"data": [
{
"status": 503,
"source": "db-service",
"title": "Timeout",
"detail": "No response from database after 30s."
}
]
}
JsonDispatch lets you express where an issue occurred so clients can react precisely.
Two scopes:
- Field-level — a specific input is invalid.
Use a JSON Pointer–style path in
source
(e.g.,/data/attributes/email
). - Request-level — the whole request failed for a non-field reason (rate limit, auth, dependency outage).
Use a concise string naming the subsystem or concern (e.g.,
auth
,rate-limit
,payments-gateway
).
Rules
- Prefer the most specific
source
you can provide. - You may mix field-level and request-level items in the same
data
array. - Keep
title
short; put human-friendly detail indetail
. - Don’t leak internals; subsystem names should be stable, public-safe identifiers.
Examples
{
"status": "fail",
"message": "Validation failed",
"data": [
{
"status": 422,
"source": "/data/attributes/email",
"title": "Invalid email",
"detail": "Email must be a valid address."
},
{
"status": 422,
"source": "/data/attributes/age",
"title": "Out of range",
"detail": "Age must be between 13 and 120."
}
]
}
{
"status": "error",
"message": "Upstream dependency unavailable",
"code": "PAYMENTS_GATEWAY_DOWN",
"data": [
{
"status": 503,
"source": "payments-gateway",
"title": "Service unavailable",
"detail": "No response from provider within 30s."
}
]
}
{
"status": "fail",
"message": "Request cannot be processed",
"data": [
{
"status": 422,
"source": "/data/attributes/items/0/sku",
"title": "Unknown SKU",
"detail": "SKU ABC-123 was not found."
},
{
"status": 429,
"source": "rate-limit",
"title": "Too many requests",
"detail": "Burst limit exceeded; retry after 15 seconds."
}
]
}
Client guidance
- Highlight fields with pointer paths; show inline messages near inputs.
- Banner or dialog for request-level issues (
auth
,rate-limit
,maintenance
). - Consider
status
code for retry/backoff behavior (see the next subsections).
The optional top-level code
field adds a business-level meaning beyond the HTTP status.
It’s ideal for client logic, dashboards and automation.
Guidelines
- Use uppercase
UPPER_SNAKE_CASE
. - Keep them short and descriptive.
- Document them in your internal or public API guide.
- Stay consistent across microservices.
Examples
Code | Meaning |
---|---|
USER_NOT_FOUND |
The requested user doesn’t exist |
DB_CONN_TIMEOUT |
Database connection timeout |
PAYMENT_GATEWAY_DOWN |
Payment provider not reachable |
ARTICLE_TITLE_TOO_SHORT |
Validation failure on title |
Scenario | status |
HTTP Status |
---|---|---|
Read / List / Create / Update Success | success | 200 / 201 / 204 |
Validation error / bad input | fail | 400 / 422 |
Authentication required or failed | fail | 401 |
Forbidden (no permission) | fail | 403 |
Not found | fail | 404 |
Conflict (duplicate / version mismatch) | fail | 409 |
Server exception / crash | error | 500 |
Bad gateway (upstream failure) | error | 502 |
Service unavailable / maintenance | error | 503 |
Upstream timeout | error | 504 |
- Clients never send
X-Request-Id
— the server generates it for traceability. - All error and fail responses share the same envelope shape.
- Each issue in
data[]
must be explicit and readable. code
+status
+X-Request-Id
= everything you need for quick debugging.
JsonDispatch goes beyond just returning status
and data
.
It introduces two companion sections — _properties
and _references
— to make responses self-descriptive and *
context-aware* without extra API calls.
The _properties
object carries metadata about the data
field.
It helps clients understand the payload’s structure, count, pagination and even its lifecycle (like deprecation or
schema changes).
Field | Type | Purpose |
---|---|---|
type |
string | Type of data (array , object , string , number , boolean ) |
name |
string | Logical name for this data block |
template |
string (URL) | Optional link to a JSON Schema or template definition |
deprecation |
string (URL) | Optional link marking this resource as deprecated |
count |
integer | Total number of items if the data is an array |
range |
string | Range of records in paginated results (e.g., "21-40" ) |
page |
integer | Current page number, if paginated |
"_properties": {
"data": {
"type": "array",
"name": "articles",
"count": 20,
"page": 2,
"range": "21-40",
"deprecation": "https://api.example.com/docs/v2/articles"
}
}
This tells the client:
- The
data
is an array namedarticles
. - It contains 20 items, displaying page 2 (items 21–40).
- The endpoint is deprecated in favor of the linked v2 spec.
✅ Best practice: Always include at least type
and name
.
This enables automated clients (CLI tools, SDKs, data grids) to interpret results correctly.
The _references
object defines lookup tables that map internal IDs or codes to human-readable labels.
It eliminates the need for extra “dictionary” or “enum” endpoints.
"_references": {
"category": {
"1": "News",
"2": "Tutorial",
"3": "Opinion"
},
"status": {
"A": "Active",
"I": "Inactive",
"S": "Suspended"
}
}
When a record includes "category": 2
, the client can instantly render “Tutorial” using the mapping.
✅ Best practice: Keep keys short and meaningful (e.g., category
, status
, role
).
These should directly correspond to the field names inside data
.
Use Case | Use _properties |
Use _references |
---|---|---|
Describe structure, pagination or schema | ✅ | |
Translate IDs or codes to labels | ✅ | |
Mark field as deprecated or link to template | ✅ | |
Enumerate possible options or states | ✅ |
{
"status": "success",
"message": "Articles listed successfully",
"data": [
{
"id": 1,
"title": "Intro to JsonDispatch",
"category": 1
},
{
"id": 2,
"title": "Error Handling Patterns",
"category": 3
}
],
"_properties": {
"data": {
"type": "array",
"name": "articles",
"count": 2,
"page": 1,
"range": "1-2"
}
},
"_references": {
"category": {
"1": "News",
"2": "Tutorial",
"3": "Opinion"
}
}
}
Clients can now:
- Display labels directly using
_references
. - Understand that
data
is paginated and typed via_properties
. - Do all this without extra round-trips or hard-coded logic.
Complex datasets often include hierarchical or multi-level relationships — e.g. categories, subcategories or status groups.
JsonDispatch supports nested _references
so you can express those relationships cleanly while keeping the payload compact.
This allows clients to resolve multi-level identifiers without multiple API calls.
{
"status": "success",
"message": "Product list with hierarchical references",
"data": [
{ "id": 1, "name": "iPhone 15", "category": 10, "subcategory": 101 },
{ "id": 2, "name": "Galaxy S24", "category": 10, "subcategory": 102 },
{ "id": 3, "name": "MacBook Air", "category": 20, "subcategory": 201 }
],
"_references": {
"category": {
"10": {
"label": "Mobile",
"children": {
"101": "Apple",
"102": "Samsung"
}
},
"20": {
"label": "Laptop",
"children": {
"201": "Apple",
"202": "Windows"
}
}
}
},
"_properties": {
"data": {
"type": "array",
"name": "products",
"count": 3
}
}
}
Field | Purpose |
---|---|
category |
Maps high-level IDs (10 , 20 ) to parent labels |
children |
Maps subcategory IDs to their names under each parent |
label |
A friendly label for the parent category |
_references |
Keeps the hierarchy readable and cacheable on the client |
- Fewer round-trips — clients can render category and subcategory labels directly.
- Extensible — future nesting levels (e.g., regions → countries → cities) follow the same pattern.
- Localized references —
_references
can hold language-specific labels if needed.
When parsing, clients should:
- Resolve
subcategory
first within its parentcategory
. - Fallback to top-level label if no match found.
- Cache
_references
by version to avoid redundant lookups.
✅ Best practice:
Use nested _references
only when relationships are stable and bounded. For dynamic trees, prefer dedicated endpoints (/categories
, /locations
).
APIs aren’t just about data — they’re about navigating between resources.
JsonDispatch provides a _links
object that helps clients discover where to go next without guessing or relying on
hard-coded routes.
The goal is simple: ➡️ Make your API self-navigable — so clients can move through it like a website, not a maze.
Whenever your response includes a collection or list, include pagination links to guide the client.
Key | Purpose |
---|---|
self |
URL of the current page |
next |
Next page (if available) |
prev |
Previous page (if available) |
first |
First page in the dataset |
last |
Last page in the dataset |
"_links": {
"self": "https://api.example.com/articles?page=2&limit=10",
"next": "https://api.example.com/articles?page=3&limit=10",
"prev": "https://api.example.com/articles?page=1&limit=10",
"first": "https://api.example.com/articles?page=1&limit=10",
"last": "https://api.example.com/articles?page=50&limit=10"
}
This lets clients paginate seamlessly — no manual query-building or separate pagination logic required.
✅ Tip: Include self
in every response. It makes your payload self-documenting when viewed in isolation.
Beyond pagination, _links
can expose relationships or related resources.
These hints tell the client where additional or contextual information lives.
"_links": {
"self": "https://api.example.com/articles/42",
"author": "https://api.example.com/users/99",
"comments": "https://api.example.com/articles/42/comments",
"related": "https://api.example.com/tutorials/jsondispatch"
}
Now, clients can:
- Navigate to the author or comments without extra documentation.
- Preload related data efficiently.
- Treat your API like a graph of connected resources.
Sometimes you need to describe how a link should be used — not just where it points.
In such cases, _links
entries can be full objects containing an href
and a meta
section.
"_links": {
"self": {
"href": "https://api.example.com/articles/42",
"meta": {
"method": "GET",
"auth": "required"
}
},
"edit": {
"href": "https://api.example.com/articles/42",
"meta": {
"method": "PUT",
"auth": "editor-role"
}
},
"delete": {
"href": "https://api.example.com/articles/42",
"meta": {
"method": "DELETE",
"auth": "admin-role"
}
}
}
This pattern makes _links
expressive and safe for HATEOAS-style APIs.
It tells clients both where to go and how to interact, without needing extra documentation.
- Always include
self
. It’s essential for debugging and introspection. - Keep URLs absolute (never relative).
- Use consistent naming (
next
,prev
,author
,related
,edit
,delete
, etc.). - Avoid embedding authentication tokens directly in URLs.
- When possible, pair
_links
with_properties
to describe pagination metadata (page
,count
, etc.).
APIs frequently serve or reference files — such as reports, invoices, exports or media.
JsonDispatch supports this cleanly through _links
, using either direct URLs or expirable signed URLs under a dedicated file
or download
key.
This keeps your responses self-descriptive, avoids embedding raw file data and enables clients to access assets securely.
{
"status": "success",
"message": "Activity report generated successfully",
"data": {
"report_id": "RPT-2025-10-06",
"type": "activity",
"period": "2025-09",
"size": "2.4 MB"
},
"_links": {
"self": "https://api.example.com/reports/RPT-2025-10-06",
"file": "https://cdn.example.com/reports/RPT-2025-10-06.pdf"
},
"_properties": {
"data": {
"type": "object",
"name": "report"
}
}
}
✅ Use this when files are public or long-lived (e.g., CDN-hosted assets or documentation).
{
"status": "success",
"message": "Report ready for download",
"data": {
"report_id": "RPT-2025-10-06",
"type": "activity",
"expires_in": 900
},
"_links": {
"self": "https://api.example.com/reports/RPT-2025-10-06",
"download": {
"href": "https://api.example.com/reports/RPT-2025-10-06/download?token=eyJhbGciOiJIUzI1...",
"meta": {
"method": "GET",
"auth": "required",
"expires": "2025-10-06T12:30:00Z"
}
}
}
}
✅ Use this when:
- Files are user-specific or sensitive (exports, invoices, personal data).
- Links must expire or require authentication.
- You want to guide clients explicitly on how to fetch the file (via
meta
).
{
"_links": {
"pdf": "https://cdn.example.com/reports/RPT-2025-10-06.pdf",
"csv": "https://cdn.example.com/reports/RPT-2025-10-06.csv",
"json": "https://api.example.com/reports/RPT-2025-10-06.json"
}
}
Clients can choose their preferred format programmatically — ideal for exports or analytics dashboards.
Guideline | Why |
---|---|
Always use HTTPS links | Prevents man-in-the-middle interception |
Include meta when authentication or expiry applies |
Clients can display expiry timers or handle refresh logic |
Avoid embedding file data inline | Reduces payload size and memory footprint |
Prefer download over generic link names |
Keeps semantics consistent across endpoints |
✅ Summary
File links under _links
make your API:
- Predictable → consistent structure for downloads
- Secure → signed URLs and metadata guidance
- Portable → clients can consume them without custom conventions
Perfect — here’s the next addition, 8.6 Inline Media References (Thumbnails, Avatars, etc.), written in the same tone, format and Markdown structure as your doc.
It directly complements 8.5 File Link Example, keeping _links
consistent across data and media use cases.
Not every file link is a downloadable document — many APIs include media previews such as user avatars, product thumbnails or video stills.
JsonDispatch keeps this consistent by defining them under _links.media
or _links.image
, so clients can handle them predictably.
{
"status": "success",
"message": "User profile fetched successfully",
"data": {
"id": 99,
"name": "A. B. M. Mahmudul Hasan",
"role": "admin"
},
"_links": {
"self": "https://api.example.com/users/99",
"avatar": "https://cdn.example.com/avatars/99-thumb.jpg"
},
"_properties": {
"data": {
"type": "object",
"name": "user"
}
}
}
✅ Ideal for lightweight references such as user photos, brand icons or thumbnail images.
{
"_links": {
"self": "https://api.example.com/products/2001",
"image": {
"small": "https://cdn.example.com/products/2001/small.jpg",
"medium": "https://cdn.example.com/products/2001/medium.jpg",
"large": "https://cdn.example.com/products/2001/large.jpg"
}
}
}
Clients can choose which variant to use — perfect for responsive frontends or apps that cache different image sizes.
When returning multiple images, videos or icons, each media entry can include metadata for display and accessibility:
{
"_links": {
"thumbnail": {
"href": "https://cdn.example.com/products/2001/thumb.webp",
"meta": {
"width": 120,
"height": 120,
"type": "image/webp",
"alt": "Product preview image"
}
},
"video_preview": {
"href": "https://cdn.example.com/products/2001/preview.mp4",
"meta": {
"duration": 12,
"type": "video/mp4",
"poster": "https://cdn.example.com/products/2001/thumb.jpg"
}
}
}
}
✅ This allows advanced clients (e.g. web dashboards, mobile apps) to render previews, use proper MIME types and preload efficiently.
Guideline | Reason |
---|---|
Use separate keys for image vs. video (e.g. avatar , thumbnail , video_preview ) |
Keeps structure clear and predictable |
Include meta.type for MIME hints (image/png , video/mp4 ) |
Helps browsers and SDKs preload correctly |
Prefer WebP or AVIF for image efficiency | Saves bandwidth for clients |
Use _links — not _references — for any URL-based assets |
Keeps semantics clean (navigation vs lookup) |
✅ Summary
Inline media links under _links
let APIs describe:
- Where assets are located (
href
) - What they are (
meta.type
,meta.alt
) - How to use them (variants, resolutions, roles)
They make your API visual-friendly, cache-efficient and self-documenting — no guesswork or extra endpoints needed.
JsonDispatch is designed for long-lived APIs that evolve gracefully without breaking existing clients. The guiding principle is simple:
⚙️ Evolve forward, never break back.
When making changes to your API responses:
-
Never remove a field. Old clients may still depend on it.
-
Never change the type of an existing field. A string should never become an object and an object should never become an array.
-
Only add new fields (preferably optional ones with sensible defaults).
-
Deprecate before removing. Mark old fields with
_properties.deprecation
and provide documentation for migration.
{
"_properties": {
"legacyTitle": {
"type": "string",
"name": "legacy-title",
"deprecation": "https://api.example.com/docs/v2/articles#title"
}
}
}
This approach ensures:
- Older clients keep working as expected.
- Newer clients can progressively adopt new structures.
When a breaking change becomes necessary:
-
Increment the major version in the response’s
Content-Type
.Content-Type: application/vnd.infocyph.jd.v2+json X-Api-Version: 2.0.0
-
Maintain the old major version (v1) in production for a transition period. Deprecate it gradually using communication and version headers.
-
Publish updated documentation for each major version side-by-side.
-
Avoid “soft breaks” (changing existing semantics without version bumps). Always be explicit.
Step | Action | Example |
---|---|---|
1 | Add field | Add _properties.template |
2 | Mark old field deprecated | _properties.oldField.deprecation |
3 | Announce upcoming version | Changelog + docs |
4 | Introduce new major version | v2 media type |
5 | Sunset old version | Remove after clients migrate |
JsonDispatch provides structure — but your team’s discipline keeps it reliable. These best practices ensure consistency, observability and developer happiness.
- The server generates a unique
X-Request-Id
for each response. - Include it in all logs, traces and monitoring dashboards.
- When an issue occurs, that single ID traces the request end-to-end.
✅ Treat it as your primary debugging handle.
Deprecation in JsonDispatch isn’t just about marking things as “old.” It’s about communicating intent clearly — to humans and machines — so clients can migrate smoothly without breaking.
Below is the recommended lifecycle for any field, structure or endpoint change.
Phase | Description | Duration / Expectation | Implementation Pattern | Example |
---|---|---|---|---|
1. Active (Default) | Field is stable and supported. | Ongoing | Regular use; documented and visible. | "title": "Hello World" |
2. Announce Deprecation | You’ve introduced a replacement field or version. | 1 release cycle minimum before enforcement | Mark in _properties.deprecation with URL + replacement reference. |
"legacyTitle": { "deprecation": "https://api.example.com/docs/v2/articles#title" } |
3. Dual Support Phase | Both old and new fields coexist; clients update gradually. | 1–2 release cycles | Continue returning both values; prefer new one in docs and examples. | Old → legacyTitle , New → title |
4. Soft Removal (Warn Only) | Old field still exists but returns a notice or null . |
1 cycle max | Include _properties.legacyTitle.deprecation + log warnings in responses. |
"legacyTitle": null + metadata link |
5. Hard Removal | Field is no longer returned; major version bump required. | Next major release | Remove from payload; update JSON Schema and docs. | Removed in v2 |
{
"status": "success",
"message": "Article listed with deprecated field notice",
"data": {
"id": 42,
"legacyTitle": "Hello JD (legacy)",
"title": "Hello JsonDispatch"
},
"_properties": {
"legacyTitle": {
"type": "string",
"deprecation": "https://api.example.com/docs/v2/articles#title"
},
"title": {
"type": "string",
"name": "title"
}
}
}
✅ The _properties.legacyTitle.deprecation
URL gives developers a clear migration path — and automated tools can detect deprecation without guessing.
Rule | Why |
---|---|
Never remove without prior deprecation notice | Prevents client crashes |
Always provide documentation link | Enables auto-migration and transparency |
Keep dual support phase predictable | Lets clients plan migration windows |
Announce removals in release notes | Keeps API consumers informed |
Bundle hard removals with major version bumps | Aligns with Semantic Versioning |
Clients are not required to send vendor-specific Accept
headers.
Your server defines the media type in responses:
Header | Example | Description |
---|---|---|
Content-Type | application/vnd.infocyph.jd.v1+json |
Defines the JsonDispatch version in use |
X-Api-Version | 1.3.2 |
Full semantic version of implementation |
If a client requests plain JSON (Accept: application/json
), return the default stable major version — but still
include the correct headers for transparency.
HTTP/1.1 200 OK
Content-Type: application/vnd.infocyph.jd.v1+json
X-Api-Version: 1.3.2
X-Request-Id: b3e9e7c4-9b7b-44c3-a8f3-8c39e612d882
Even the most elegant API format is useless if it leaks data or opens up attack surfaces. JsonDispatch defines how you format responses — but your platform must enforce how they’re delivered securely.
Below are recommended security headers, CORS patterns and exposure rules that align with JsonDispatch’s design.
Header | Example | Purpose |
---|---|---|
Content-Security-Policy |
default-src 'none'; frame-ancestors 'none'; |
Prevents browsers from embedding or executing API responses in iframes or cross-site contexts. |
Strict-Transport-Security |
max-age=31536000; includeSubDomains; preload |
Enforces HTTPS and prevents downgrade attacks. |
X-Content-Type-Options |
nosniff |
Blocks MIME-type sniffing, ensuring application/json stays JSON. |
X-Frame-Options |
DENY |
Prevents clickjacking or UI redress attacks. |
Referrer-Policy |
no-referrer |
Avoids leaking private API URLs in browser referrers. |
Cache-Control |
no-store, no-cache, must-revalidate |
Prevents sensitive responses (e.g., tokens, user info) from being cached. |
Permissions-Policy |
geolocation=(), microphone=(), camera=() |
Locks down API-origin capabilities when served via browsers. |
Tip: You can centralize these headers at reverse proxy level (e.g., Nginx, Caddy or API Gateway) — they don’t need to live inside your application code.
If your API is accessed from browsers, define explicit and minimal CORS policies.
Recommended Example
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Request-Id, X-Correlation-Id, Authorization
Access-Control-Expose-Headers: X-Request-Id, X-Api-Version, X-Correlation-Id
Access-Control-Max-Age: 600
✅ Key points:
- Always whitelist origins, never use
*
for authenticated APIs. - Expose
X-Request-Id
,X-Api-Version
andX-Correlation-Id
so browser-based clients can log and correlate them. - Cache preflight (
OPTIONS
) responses to reduce latency.
-
Never echo back raw credentials, tokens or stack traces in
error
responses. Use safecode
andmessage
fields instead. -
Return consistent HTTP status codes (
401
,403
) for unauthorized access — don’t over-share. -
When using JWT or HMAC signatures, ensure timestamps and expirations are validated server-side.
-
Log
X-Request-Id
with auth context, but never include sensitive tokens in the same line — to prevent log correlation attacks.
{
"status": "fail",
"message": "Authentication required",
"data": [
{
"status": 401,
"source": "auth-service",
"title": "Invalid or missing token",
"detail": "Please provide a valid Authorization header."
}
]
}
This tells the user exactly what’s wrong — without exposing internals.
✅ Task | Why it matters |
---|---|
HTTPS enforced (Strict-Transport-Security ) |
Prevents MITM attacks |
No open Access-Control-Allow-Origin: * |
Blocks cross-site token leakage |
nosniff and DENY headers applied |
Stops MIME confusion & framing |
Only expose safe headers (X-* ) |
Prevents internal header leakage |
Minimal, consistent error structure | Avoids sensitive info exposure |
They’re not fluff — they make your responses self-describing.
_properties
→ guides clients about structure and pagination._references
→ eliminates enum lookups or duplicate API calls.
Example:
{
"_references": {
"status": {
"A": "Active",
"I": "Inactive"
}
},
"_properties": {
"data": {
"type": "array",
"name": "users",
"count": 50,
"page": 1
}
}
}
JsonDispatch isn’t just a response format — it’s the foundation of your API observability contract. When used consistently, it becomes the single source of truth for debugging, monitoring, auditing and compliance.
Every service should emit structured logs with consistent keys — machine-parseable and human-readable.
Field | Source | Purpose |
---|---|---|
timestamp |
system | When the event occurred |
level |
system | Severity (INFO , WARN , ERROR ) |
service |
config | Service name (checkout-api , risk-worker ) |
route |
request | HTTP method + path |
status |
response | HTTP status code |
duration_ms |
response | Request duration |
x_request_id |
header | Per-request trace ID |
x_correlation_id |
header | Workflow-level trace ID |
user_id |
context | Acting user or system ID |
remote_ip |
request | Client IP (sanitized if needed) |
Example log line
[2025-10-06T10:22:51Z] INFO service=checkout-api
route=POST /checkout duration=284ms status=201
request_id=019fb440-4e83-4b1b-bef9-44a80771f181
correlation_id=order-2025-10-05-777 user_id=U9912
✅ Tip: Always start each log entry with request_id
for fast filtering in log pipelines.
Each inbound call generates a unique X-Request-Id
, while X-Correlation-Id
persists across the entire workflow.
Service | X-Correlation-Id |
X-Request-Id |
Role |
---|---|---|---|
checkout-api |
order-2025-10-05-777 |
019f… |
Entry point |
payment-api |
Propagated | 020a… |
Downstream call |
shipping-worker |
Propagated | 021b… |
Background task |
order-2025-10-05-777
├── [checkout-api] 019f…
├── [payment-api] 020a…
└── [shipping-job] 021b…
Search by correlation_id
to see the entire transaction chain.
Attach JsonDispatch IDs to your tracing spans for full observability.
Example span attributes
{
"trace_id": "00f067aa0ba902b7",
"request_id": "019fb440-4e83-4b1b-bef9-44a80771f181",
"correlation_id": "order-2025-10-05-777",
"service.name": "checkout-api",
"http.status_code": 201,
"duration_ms": 284
}
✅ Best Practice: Mirror these IDs in logs, traces and metrics — enabling one-click navigation from error to trace to log line.
Expose /health
and /ready
endpoints using the JsonDispatch envelope.
This ensures monitoring tools receive a consistent, parsable format.
Example
GET /health
Accept: application/json
HTTP/1.1 200 OK
Content-Type: application/json
X-Api-Version: 1.4.0
X-Request-Id: 44f0d7e2-c9b2-4a61-9b2a-1cf41b922021
{
"status": "success",
"message": "Service healthy",
"data": {
"uptime": "4d 06h 22m",
"load": 0.21,
"dependencies": {
"database": "ok",
"redis": "ok",
"queue": "delayed(1)"
}
},
"_properties": {
"data": { "type": "object", "name": "health" }
}
}
Return status:error
with 503
if any dependency fails.
Expose lightweight /metrics
or /stats
endpoints for quantitative insights.
{
"status": "success",
"message": "Metrics snapshot",
"data": {
"requests_per_minute": 1240,
"average_latency_ms": 182,
"active_users": 341,
"error_rate_percent": 0.6
},
"_properties": {
"data": {
"type": "object",
"name": "metrics",
"template": "https://spec.infocyph.com/jsondispatch/metrics.schema.json"
}
}
}
This allows Grafana, Prometheus exporters or custom dashboards to consume metrics in the same JSON format as all other responses.
JsonDispatch headers and IDs also power audit trails and forensic analysis. Integrate them into your audit subsystem.
Audit Log Entry Example
{
"timestamp": "2025-10-06T11:44:10Z",
"action": "USER_DELETE",
"actor_id": "admin-42",
"target_id": "user-983",
"result": "success",
"request_id": "bafc98e0-33a1-41e9-90e4-0234cf19a311",
"correlation_id": "session-2384",
"ip": "203.0.113.12"
}
Security Best Practices
- 🔒 Mask sensitive fields (PII, credentials, tokens) before logging.
- 🧩 Validate
_links
URLs to prevent malicious injection. - 🧮 Use UUIDs or hashes instead of numeric IDs in responses.
- 🕵️ Correlate admin actions via
X-Request-Id
in audit reports. - 🧱 Rotate and retain logs per compliance policy (GDPR, PCI-DSS, ISO 27001).
- 📈 Trigger alerts when error rates exceed thresholds, grouped by
service
andcorrelation_id
.
Category | Key Practice | Why It Matters |
---|---|---|
Tracing | Generate X-Request-Id for every response |
Root-cause visibility |
Correlation | Reuse X-Correlation-Id across related calls |
End-to-end transaction tracing |
Logging | Emit structured JSON logs | Machine-readable monitoring |
Security | Mask sensitive info before logging | Data protection & compliance |
Monitoring | Return JsonDispatch envelopes for /health & /metrics |
Uniform automation |
Audit | Store IDs with every admin or financial action | Forensic traceability |
Alerts | Integrate with APM / log aggregator | Real-time anomaly detection |
✅ Key Takeaway
JsonDispatch is not just a response contract — it’s the observability spine of your platform. When tracing, metrics, logs and audits all share the same IDs and envelope style, your API ecosystem becomes self-diagnosing, self-auditing and self-healing.
The Appendix provides reference materials, reserved conventions and developer tools for teams implementing or integrating JsonDispatch.
It’s meant as a quick lookup section — not something you memorize, but something you check when building middleware, validation tools or test clients.
These headers are core to JsonDispatch and reserved for server responses. They should never be redefined for other purposes.
Header | Direction | Required | Description |
---|---|---|---|
Content-Type |
Response | ✅ | Defines the JsonDispatch response format (e.g. application/vnd.infocyph.jd.v1+json ) |
X-Api-Version |
Response | ✅ | Full semantic version of the server’s JsonDispatch implementation |
X-Request-Id |
Response | ✅ | Unique identifier generated by the server for this specific request/response |
X-Correlation-Id |
Response | ⚪ | Optional; groups multiple related requests under one workflow |
traceparent / tracestate |
Response | ⚪ | Optional; W3C Trace Context headers if distributed tracing is enabled |
⚠️ Clients should not sendX-Request-Id
orX-Api-Version
— these are server-assigned identifiers for traceability and audit purposes.
Content-Type: application/vnd.infocyph.jd.v1+json
X-Api-Version: 1.3.1
X-Request-Id: aabbccdd-1122-3344-5566-77889900aabb
X-Correlation-Id: order-2025-09-30-777
If you also use W3C Trace Context:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
tracestate: infocyph=api-2025
JsonDispatch reserves certain top-level fields in the response body. Avoid reusing them for other meanings.
Reserved Key | Purpose |
---|---|
status |
Overall outcome (success , fail , error ) |
message |
Human-readable message |
data |
Primary content (object, array or list of errors) |
code |
Business-level error code (only in error responses) |
_references |
Lookup tables for IDs → labels |
_properties |
Metadata describing structure or pagination |
_links |
Navigational or relational links |
If you need more top-level fields, group them under a meta
object or use prefixed names (e.g. app_status
).
JsonDispatch is language-agnostic. Here’s what a typical implementation might include:
Middleware responsibilities
- Attach
Content-Type
andX-Api-Version
. - Generate
X-Request-Id
for each request. - Optionally include
X-Correlation-Id
if workflow-aware. - Enrich logs and tracing context automatically.
Response builders
-
Provide helper methods like:
JsonDispatch::success($data, $message)
JsonDispatch::fail($errors, $message)
JsonDispatch::error($code, $errors, $message)
-
Automatically append
_links
,_properties
and_references
if provided.
Logging integration
- Every log entry should carry
X-Request-Id
. - Distributed logs can also use
X-Correlation-Id
for multi-service correlation.
A lightweight JSON Schema to validate response envelopes. This helps test suites and monitoring agents verify that your API always returns valid JsonDispatch shapes.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://spec.infocyph.com/jsondispatch/v1/envelope.schema.json",
"title": "JsonDispatch v1 Envelope",
"type": "object",
"additionalProperties": false,
"properties": {
"status": {
"type": "string",
"enum": [
"success",
"fail",
"error"
]
},
"message": {
"type": "string"
},
"code": {
"type": "string"
},
"data": {},
"_references": {
"type": "object"
},
"_properties": {
"type": "object"
},
"_links": {
"type": "object"
}
},
"required": [
"status"
]
}
This schema validates:
status
must always exist.- Reserved fields must use correct types.
- No unexpected top-level keys.
Basic request (client → server):
curl -H 'Accept: application/json' \
https://api.example.com/articles/42
Server response (JsonDispatch-compliant):
HTTP/1.1 200 OK
Content-Type: application/vnd.infocyph.jd.v1+json
X-Api-Version: 1.3.1
X-Request-Id: 60c1bbca-b1c8-49d0-b3ea-fe41d23290bd
Body:
{
"status": "success",
"message": "Article fetched successfully",
"data": {
"id": 42,
"title": "JsonDispatch in Action"
}
}
Request with correlation (for multi-step workflows):
curl -H 'Accept: application/json' \
-H 'X-Correlation-Id: order-2025-10-05-xyz' \
https://api.example.com/checkout
Server response:
HTTP/1.1 201 Created
Content-Type: application/vnd.infocyph.jd.v1+json
X-Api-Version: 1.3.1
X-Request-Id: 8d4e2a1b-c821-4b97-8430-44c7b9651d79
X-Correlation-Id: order-2025-10-05-xyz
Body:
{
"status": "success",
"message": "Checkout initiated successfully",
"data": {
"order_id": "ORD-2391A",
"state": "processing"
}
}
-
The server is the only authority for JsonDispatch headers. Clients may provide
X-Correlation-Id
(optional), but neverX-Request-Id
. -
Content-Type
defines the envelope version (application/vnd.infocyph.jd.v1+json
). -
Always include
X-Api-Version
in responses — even for errors. -
JsonDispatch responses remain valid JSON even for plain
application/json
clients.