so jsonrpc is nothing more than a json schema saying that please follow this structure, but you just do http+json body anyway
JSON-RPC is a small protocol for expressing remote procedure calls as JSON. A client names an operation, supplies its arguments, and optionally assigns an identifier that links the eventual response to the request.
Its essence is:
Call this named operation with these arguments,
then tell me either its result or its error.A minimal request looks like this:
{
"jsonrpc": "2.0",
"id": 17,
"method": "subtract",
"params": {
"minuend": 42,
"subtrahend": 23
}
}The corresponding response is:
{
"jsonrpc": "2.0",
"id": 17,
"result": 19
}JSON-RPC does not define what subtract means, which methods exist, or the
schema of their parameters and results. An application protocol built on
JSON-RPC defines those details. JSON-RPC supplies only the common message
envelope and call semantics.
Is It Just HTTP with a JSON Body? #
In many deployments, JSON-RPC is carried in an ordinary HTTP request:
POST /rpc HTTP/1.1
Host: calculator.example.com
Content-Type: application/json
{"jsonrpc":"2.0","id":17,"method":"subtract","params":{"minuend":42,"subtrahend":23}}So at the transport level, yes: this is HTTP with a JSON body. But that description omits the convention that makes the body interoperable.
HTTP defines:
- how a request reaches a server;
- methods such as
GETandPOST; - URLs, headers, authentication, caching, and status codes; and
- how bytes are transferred.
JSON defines:
- how values such as objects, arrays, strings, and numbers are encoded.
JSON-RPC defines:
- which remote operation is requested through
method; - how arguments are supplied through
params; - how concurrent calls are correlated through
id; - whether a message is a request, response, notification, or error; and
- the standard shape of successful and failed responses.
The layers therefore have different responsibilities:
Application methods: subtract, initialize, SendMessage, tools/call
JSON-RPC: method, params, id, result, error
Transport: HTTP, WebSocket, stdio, or another byte channel
Serialization: JSONJSON-RPC itself is transport-independent. HTTP is common, but a protocol can carry the same messages over WebSocket, a persistent stream, standard input and output, or another transport.
The Four Message Shapes #
Request #
A request asks the receiver to invoke a method and return a response:
{
"jsonrpc": "2.0",
"id": "request-123",
"method": "user.get",
"params": {
"userId": 42
}
}The required jsonrpc value identifies JSON-RPC 2.0. method is a string.
params, when present, is an object for named arguments or an array for
positional arguments. id is chosen by the caller.
Successful Response #
A successful response repeats the request ID and contains result:
{
"jsonrpc": "2.0",
"id": "request-123",
"result": {
"userId": 42,
"name": "Ada"
}
}The caller uses id, not arrival order, to associate this response with its
request. This matters when several requests are in flight concurrently.
Error Response #
A failed call contains error instead of result:
{
"jsonrpc": "2.0",
"id": "request-123",
"error": {
"code": -32602,
"message": "Invalid params",
"data": {
"field": "userId"
}
}
}Every error has a numeric code and textual message; data is optional.
JSON-RPC reserves a range of codes for protocol-level failures:
| Code | Meaning |
|---|---|
-32700 | Parse error |
-32600 | Invalid request |
-32601 | Method not found |
-32602 | Invalid parameters |
-32603 | Internal error |
Applications may define additional codes for domain failures. This creates a machine-readable error contract instead of making every client infer failure from arbitrary text.
Notification #
A notification is a request without an id:
{
"jsonrpc": "2.0",
"method": "cache.invalidate",
"params": {
"key": "user:42"
}
}The receiver does not send a response, including when processing fails. A notification therefore means “send and do not wait for a protocol-level reply,” not “guaranteed delivery.” Delivery, retries, acknowledgements, and durability depend on the transport and application protocol.
JSON-RPC Is a Message Model, Not a Complete API #
JSON-RPC deliberately leaves many questions unanswered:
- Which methods are available?
- What parameters and result types does each method use?
- Is a method safe, idempotent, or state-changing?
- How are versions negotiated?
- How are clients authenticated and authorized?
- How are large files transferred?
- How are long-running operations represented?
- Are notifications ordered or durable?
- How are streaming results delivered?
A higher-level protocol must answer these questions. For example, the Model Context Protocol uses JSON-RPC messages but adds initialization, capability negotiation, tools, resources, prompts, and lifecycle rules. The Agent2Agent Protocol’s JSON-RPC binding maps A2A operations and data objects onto JSON-RPC calls. In both cases, JSON-RPC is plumbing rather than the entire contract.
This distinction is important:
JSON-RPC tells peers how to represent a call.
The application protocol tells peers what the call means.JSON-RPC Compared with REST #
REST and JSON-RPC can both use HTTP and JSON, but they organize an API differently.
A REST-style API emphasizes resources and HTTP semantics:
POST /orders
GET /orders/42
DELETE /orders/42A JSON-RPC API usually exposes one endpoint and places the operation in the body:
POST /rpc
{"jsonrpc":"2.0","id":1,"method":"order.create","params":{...}}POST /rpc
{"jsonrpc":"2.0","id":2,"method":"order.get","params":{"orderId":42}}The practical difference is not “HTTP versus no HTTP.” It is where the application semantics live:
| Concern | REST style | JSON-RPC style |
|---|---|---|
| Operation | HTTP method plus URL | method field |
| Target | Resource URL | Usually one RPC endpoint |
| Arguments | Path, query, headers, body | params |
| Success | HTTP status plus representation | result |
| Failure | HTTP status plus API-specific body | Standard error envelope |
| Correlation | Usually one response per HTTP request | Explicit id |
Neither style is universally superior. REST fits APIs naturally modeled as resources and benefits directly from HTTP caching, conditional requests, and generic intermediaries. JSON-RPC fits command-oriented interfaces, protocol methods, bidirectional connections, and systems where every message should share one compact envelope.
An HTTP JSON-RPC implementation still needs a documented mapping between HTTP and JSON-RPC failures. For example, malformed HTTP, failed authentication, and an unavailable server are transport-level failures, while an unknown RPC method is a JSON-RPC error. Collapsing both layers into one vague error model makes clients unreliable.
Batching #
JSON-RPC 2.0 allows a client to send an array of requests and notifications:
[
{"jsonrpc":"2.0","id":1,"method":"user.get","params":{"userId":42}},
{"jsonrpc":"2.0","id":2,"method":"user.get","params":{"userId":43}},
{"jsonrpc":"2.0","method":"audit.touch","params":{"scope":"users"}}
]The server returns responses for the two requests but not for the notification. Response order need not match request order, so IDs remain essential.
Batching reduces transport overhead, but it is not automatically a database transaction. The specification does not require calls to execute in order, atomically, or even sequentially. An application requiring those guarantees must define them explicitly.
What JSON-RPC Does Not Guarantee #
The presence of an id does not provide exactly-once execution. Consider:
Client sends payment.charge(id = 9).
Server charges the card.
Connection fails before the response arrives.
Client does not know whether the call ran.Blindly retrying may execute the operation twice. Robust APIs need application-level idempotency keys, deduplication, or a way to query operation status. The JSON-RPC request ID correlates messages; it is not necessarily a business-operation identifier.
JSON-RPC also does not provide:
- encryption;
- authentication or authorization;
- service discovery;
- schemas or code generation;
- retries and backoff;
- cancellation;
- flow control;
- observability context;
- streaming semantics; or
- compatibility guarantees between API versions.
Those features come from the transport, an application protocol, supporting standards, or implementation conventions.
When JSON-RPC Is a Good Fit #
JSON-RPC is useful when:
- the interface is naturally a set of named commands;
- clients and servers need a small language-neutral envelope;
- calls may share a persistent or bidirectional transport;
- explicit request correlation is useful;
- notifications are needed;
- a higher-level protocol already defines methods and data models; or
- the uniformity of one endpoint is more useful than resource-oriented HTTP semantics.
It is less compelling when:
- the API is naturally a collection of cacheable resources;
- standard HTTP behavior is central to the design;
- browser and CDN integration should work with minimal custom logic;
- rich schemas, generated clients, and service governance are required but no additional specification mechanism is planned; or
- a mature domain-specific protocol already solves the problem.
The Essential Mental Model #
JSON-RPC is not an alternative network stack and not a complete distributed system. It is a thin, standardized vocabulary for remote calls:
Request: id + method + params
Success: id + result
Failure: id + error
Notification: method + params, with no id and no responseWhen used over HTTP, it really is JSON in an HTTP body. Its value lies in the shared meaning of that body: both sides agree how to identify a call, route it to a named operation, correlate its response, and represent its failure. The remaining behavior belongs to the application protocol built on top.
Comments
Read or comment on GitHub (sign-in required to comment)