JSON-RPC is a stateless, lightweight remote procedure call protocol that encodes a call to a named method, its parameters and its result or error as JSON objects. Among APIs, it represents the procedure-oriented style: a client asks a server to run a specific function, rather than manipulating resources as a REST API does.
The current version, JSON-RPC 2.0, is a short specification that defines only the message format and leaves the transport open. In practice it runs over HTTP, WebSocket, TCP sockets and standard input and output, which explains its wide use in blockchain node interfaces, editor tooling such as the Language Server Protocol and the Model Context Protocol for AI tools.
How JSON-RPC messages work
A JSON-RPC exchange consists of a request object sent by the client and, in most cases, a response object returned by the server. Every message carries the member "jsonrpc": "2.0" so that both sides know which version of the protocol applies.
A request contains the method name, optional params given either as an array (by position) or as an object (by name), and an id that can be a string or a number. The server answers with the same id plus either a result member or an error member, never both. The id is what lets a client match responses to requests when several calls are in flight over one connection.
| Member | Appears in | Purpose |
|---|---|---|
| jsonrpc | Request and response | Must be exactly the string 2.0 |
| method | Request | Name of the procedure to invoke |
| params | Request (optional) | Arguments as an array or an object |
| id | Request and response | Correlates a response with its request |
| result | Successful response | Return value of the method |
| error | Failed response | Object with code, message and optional data |
A request without an id is a notification. The server must not reply to it, which makes notifications suitable for fire-and-forget events such as progress updates or log messages, but it also means the client never learns whether the call failed.
Batches, notifications and error codes
JSON-RPC 2.0 allows a client to send an array of requests as a batch. The server may process them in any order and in parallel, and it returns an array of responses that the client reassembles by id. Notifications inside a batch produce no entries, and a batch consisting only of notifications produces no response at all.
Errors use integer codes with fixed meanings for the protocol layer. The range from -32768 to -32000 is reserved for the specification and implementations, while applications should define their own codes outside that block.
| Code | Name | Meaning |
|---|---|---|
| -32700 | Parse error | The server received invalid JSON |
| -32600 | Invalid request | The JSON is not a valid request object |
| -32601 | Method not found | The method does not exist or is unavailable |
| -32602 | Invalid params | The parameters are wrong for the method |
| -32603 | Internal error | A server-side failure occurred |
| -32000 to -32099 | Server error | Reserved for implementation-defined errors |
JSON-RPC compared with other API styles
JSON-RPC sits at the simple end of the procedure-call spectrum. It shares the call-a-function model of gRPC but uses text instead of a binary schema, and it differs from a REST API by putting the operation in the message body rather than in the URL and HTTP method.
| Aspect | JSON-RPC | REST API | gRPC | GraphQL |
|---|---|---|---|---|
| Core model | Method calls | Resources and HTTP verbs | Method calls | Typed query language |
| Encoding | JSON text | Usually JSON | Protocol Buffers binary | JSON |
| Transport | Any | HTTP | HTTP/2 | Usually HTTP |
| Schema | None built in | Optional, often OpenAPI | Mandatory .proto files | Mandatory schema |
| HTTP caching | Not usable by default | Native with GET | Not usable | Limited |
| Bidirectional calls | Yes over sockets | No | Yes with streaming | Via subscriptions |
Because JSON-RPC typically sends every call as an HTTP POST to a single API endpoint, it gives up the caching, status-code semantics and resource-oriented URLs that make REST friendly to proxies and browsers. In return it offers a uniform envelope, transport independence and symmetric communication in which either peer can call the other.
When to choose JSON-RPC
JSON-RPC fits best where the interface is naturally a set of actions rather than a set of resources, and where both sides want a minimal, language-neutral contract.
| Situation | Fit | Reason |
|---|---|---|
| Tool and editor protocols over stdio | Strong | Bidirectional calls with no HTTP needed |
| Blockchain node interfaces | Strong | Many distinct commands with small payloads |
| Internal command APIs | Good | Simple to implement in any language |
| Public web API with heavy reads | Weak | Loses HTTP caching and resource semantics |
| High-throughput service mesh | Weak | Text encoding is slower than binary gRPC |
Teams that adopt JSON-RPC usually add what the protocol omits: a machine-readable method catalog, authentication at the transport layer, request size limits and versioning rules. Without these, the flexibility of free-form params quickly turns into undocumented and fragile contracts.
Limitations and common mistakes
- Returning HTTP errors instead of error objects: clients expect a JSON-RPC error with a code; mixing HTTP 500 responses for application failures breaks generic client libraries.
- Reusing ids for concurrent requests: duplicate ids make responses ambiguous on a shared connection.
- Using notifications for important actions: the caller receives no confirmation and no error.
- Ignoring batch limits: unbounded batches let one client consume large amounts of server time.
- Mixing 1.0 and 2.0 semantics: the older version used null fields and lacked named params, so the version member must be checked.
Security is outside the specification. Authentication, authorization per method and rate limiting must be added by the transport or the application, and exposing a node or tool interface with powerful methods on an open port is a frequent and serious misconfiguration.
The idea in brief
JSON-RPC is a simple, agreed way for one program to ask another program to do something. The request is a short note written in JSON that says which task to run, which values to use and a reference number. The answer comes back with the same number and either the result or an explanation of what went wrong.
It works much like ordering by a numbered ticket at a counter. You hand over a ticket that says what you want, and when your number is called, you get either your order or a reason it is unavailable. The ticket format stays the same whether the order goes by phone, by messenger or across the counter.
A practical scenario
A team builds a code editor plugin that offers autocompletion, error highlighting and renaming for an internal programming language. The editor and the language engine run as two separate processes connected through standard input and output, with no web server involved.
They use JSON-RPC 2.0 for every interaction. When the user types, the editor sends a request with the method name for completion, the file path and the cursor position, plus an id such as 57. The engine replies with id 57 and a list of suggestions, typically within tens of milliseconds. When the engine finishes analyzing a file, it sends a notification with diagnostics, and the editor draws the red underlines without replying.
Because the message format is fixed, the same engine later works with a second editor after only a small adapter is written. The outcome is one language engine serving several editors, with errors reported through standard codes such as -32602 when the editor passes an invalid position.