Skip to content

Mesh Protocol Specification

An intra-service RPC protocol with per-function versioning

Protocol Version: 0.1.0 (Draft)


Mesh is a JSON-based RPC protocol designed for internal microservice communication. It prioritizes:

  • Per-function versioning — Evolve functions independently, not monolithic API versions
  • Built-in observability — Tracing context propagates through call chains
  • Explicit retry semantics — Idempotency and deadlines are first-class
  • Extensibility — Clean extension mechanism for optional features
  • Simplicity — Easy to implement in any language

Get a Mesh endpoint running in 5 minutes:

{
"function": "users.get",
"version": "1",
"description": "Retrieve a user by ID",
"arguments": {
"type": "object",
"properties": {
"id": { "type": "integer" }
},
"required": ["id"]
}
}
{
"protocol": { "name": "mesh", "version": "0.1.0" },
"id": "req_001",
"call": {
"function": "users.get",
"version": "1",
"arguments": { "id": 42 }
}
}
{
"protocol": { "name": "mesh", "version": "0.1.0" },
"id": "req_001",
"result": {
"id": 42,
"name": "Jane Doe",
"email": "jane@example.com"
}
}
{
"protocol": { "name": "mesh", "version": "0.1.0" },
"id": "req_001",
"result": null,
"errors": [{
"code": "NOT_FOUND",
"message": "User not found",
"retryable": false
}]
}
{
"protocol": { "name": "mesh", "version": "0.1.0" },
"id": "req_001",
"call": {
"function": "users.get",
"version": "1",
"arguments": { "id": 42 }
},
"context": {
"trace_id": "tr_abc123",
"caller": "api-gateway"
}
}

See Protocol for complete field definitions.


DocumentDescription
ProtocolRequest and response structure, field definitions
Document StructureTop-level structure of requests and responses
ErrorsError handling, error codes, multiple errors, source pointers
VersioningProtocol versioning and per-function versioning
DocumentDescription
Resource ObjectsStandard structure for domain entities (type/id/attributes)
RelationshipsIncluding and querying related resources
Sparse FieldsetsRequest only the fields you need
FilteringQuery filter syntax and operators
SortingSort criteria for collection queries
PaginationStandard patterns for paginated list operations
DocumentDescription
ContextContext propagation for tracing and metadata
Rate LimitingRate limit communication and handling
DocumentDescription
ExtensionsExtension mechanism for optional capabilities
AsyncAsynchronous operation patterns
DeadlineRequest timeouts to prevent cascading failures
DocumentDescription
TransportHTTP and message queue bindings
DocumentDescription
System FunctionsReserved namespaces and built-in functions
DescriptionMachine-readable API description (OpenRPC-style)

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in these documents are to be interpreted as described in RFC 2119.


{
"protocol": { "name": "mesh", "version": "0.1.0" },
"id": "req_xyz789",
"call": {
"function": "orders.create",
"version": "2",
"arguments": {
"customer_id": 42,
"items": [
{ "sku": "WIDGET-01", "quantity": 2 }
]
}
},
"context": {
"trace_id": "tr_8f3a2b1c",
"span_id": "sp_4d5e6f",
"caller": "checkout-service"
},
"extensions": [
{
"urn": "urn:mesh:ext:deadline",
"options": { "value": 5, "unit": "second" }
}
]
}
{
"protocol": { "name": "mesh", "version": "0.1.0" },
"id": "req_xyz789",
"result": {
"order_id": 12345,
"status": "pending"
},
"extensions": [
{
"urn": "urn:mesh:ext:tracing",
"data": {
"trace_id": "tr_8f3a2b1c",
"span_id": "span_server_001",
"duration": { "value": 127, "unit": "millisecond" }
}
}
],
"meta": {
"node": "orders-api-2"
}
}
{
"protocol": { "name": "mesh", "version": "0.1.0" },
"id": "req_xyz789",
"result": null,
"errors": [
{
"code": "INVALID_ARGUMENTS",
"message": "Customer not found",
"retryable": false,
"source": {
"pointer": "/call/arguments/customer_id"
}
}
],
"extensions": [
{
"urn": "urn:mesh:ext:tracing",
"data": {
"trace_id": "tr_8f3a2b1c",
"span_id": "span_server_002",
"duration": { "value": 23, "unit": "millisecond" }
}
}
],
"meta": {
"node": "orders-api-1"
}
}

Mesh is transport-agnostic. Common transports:

  • HTTP — POST to service endpoint, JSON body
  • Message Queue — Async processing via RabbitMQ, Redis, etc.
  • Unix Socket — Local inter-process communication

See Transport for detailed bindings.


Use this checklist when implementing a Mesh client or server:

  • Parse and validate protocol field (reject unknown versions)
  • Echo id field in all responses
  • Route requests by call.function and call.version
  • Validate call.arguments against function schema
  • Return error object for single errors, errors array for multiple
  • Include code, message, and retryable in all errors
  • Support source.pointer for validation errors (JSON Pointer format)
  • Implement standard error codes (NOT_FOUND, INVALID_ARGUMENTS, etc.)
  • Handle deadline extension — return DEADLINE_EXCEEDED if exceeded
  • Propagate context.trace_id and context.span_id to downstream calls
  • Include meta.duration in responses
  • Generate unique id for each request
  • Use deadline extension for all production calls
  • Propagate trace context from incoming requests
  • Handle both error and errors response formats
  • Implement retry logic based on error.retryable
  • Use exponential backoff for retryable errors
  • Respect Retry-After hints in rate limit errors
  • Extensions: Declare in extensions array with urn and options objects
  • Async: Implement mesh.operation.status polling
  • Idempotency: Use idempotency extension for safe retries
  • Discovery: Implement mesh.functions for introspection
  • Health: Implement mesh.health with component checks

Migrating from JSON-RPC 2.0 to Mesh:

JSON-RPCMeshNotes
jsonrpc: "2.0"protocol: "mesh/0.1"Version identifier
methodcall.functionFunction name
call.versionNew: Per-function versioning
params (array/object)call.argumentsAlways object
resultresultSame semantics
error.code (integer)errors[].code (string)String codes: NOT_FOUND, INVALID_ARGUMENTS
error.messageerrors[].messageSame semantics
error.dataerrors[].detailsRenamed
errors[].retryableNew: Explicit retry hint
errors[].sourceNew: JSON Pointer to error location
contextNew: Trace propagation
extensionsNew: Optional capabilities (deadline, tracing, etc.)
metaNew: Response metadata
  1. No notifications — All requests require responses. Use async extension for fire-and-forget with tracking.

  2. No batch requests — Make concurrent HTTP/2 requests instead. See FAQ for rationale.

  3. Per-function versioning — Version each function independently instead of the entire API.

  4. String error codes — Use descriptive SCREAMING_SNAKE_CASE codes instead of integers.

  5. Built-in observability — Trace context propagates automatically through context field.

JSON-RPC 2.0:

{
"jsonrpc": "2.0",
"id": 1,
"method": "users.get",
"params": { "id": 42 }
}

Mesh:

{
"protocol": { "name": "mesh", "version": "0.1.0" },
"id": "req_001",
"call": {
"function": "users.get",
"version": "1",
"arguments": { "id": 42 }
}
}

Choose the right pagination strategy for your use case:

StrategyBest ForTrade-offs
OffsetAdmin UIs, simple lists, “jump to page”Inconsistent on writes, slow for large offsets
CursorInfinite scroll, real-time feedsNo random access, opaque tokens
KeysetLarge datasets, time-seriesRequires stable sort column, no count
Need "jump to page N"?
└─ Yes → Offset pagination
└─ No → Dataset > 10K rows?
└─ Yes → Keyset pagination
└─ No → Real-time feed?
└─ Yes → Cursor pagination
└─ No → Cursor or Offset (preference)

Offset — Page 3 of 10 items:

{ "page": { "offset": 20, "limit": 10 } }

Cursor — After specific position:

{ "page": { "after": "eyJpZCI6MTAwfQ==", "limit": 10 } }

Keyset — After specific value:

{ "page": { "after": { "created_at": "2024-01-15T10:30:00Z", "id": 500 }, "limit": 10 } }

See Pagination for complete specification.


Mesh does not specify authentication — it is a transport concern. Common patterns:

  • HTTP: Bearer tokens in Authorization header
  • Message Queues: Credentials in connection/channel configuration
  • mTLS: Certificate-based service identity

Use the context field to propagate identity for authorization decisions:

{
"context": {
"caller": "checkout-service",
"user_id": "usr_123",
"roles": ["admin", "billing"]
}
}

Servers SHOULD validate authorization before processing and return FORBIDDEN for unauthorized requests.

  • Always validate call.arguments against your schema
  • Use source.pointer to indicate exact location of invalid input
  • Sanitize all user input before use in queries or commands
  • Limit request body size at transport layer (recommended: 1 MB)
  • Implement rate limits per caller/function
  • Return RATE_LIMITED with retry_after in details
  • Use context.caller to identify requesting service
  • See Rate Limiting for response format
  • Never include secrets in call.arguments for logging safety
  • Use reference IDs instead of raw credentials
  • Redact sensitive fields in error details

  • Initial specification
  • Core protocol, errors, versioning
  • Document structure and resource objects (JSON:API-style)
  • Filtering with operators (equals, in, between, like, etc.)
  • Sorting and sparse fieldsets
  • Relationship inclusion and nested queries
  • Context propagation
  • Rate limiting with proactive visibility (IETF draft headers)
  • Pagination patterns (offset, cursor, keyset)
  • Extension mechanism
  • Async patterns
  • Transport bindings (HTTP, message queues)
  • System functions and reserved namespaces
  • Health protocol (mesh.health) with component-level checks
  • Discovery specification (OpenRPC-style introspection)