sondahub / JSON-RPC & XML-RPC
JSON-RPC and XML-RPC test server
JSON-RPC 2.0 and XML-RPC over every mock API here: five methods per collection, by position or by name, batches and notifications, the spec’s error codes, an OpenRPC document, the introspection methods and multicall — and helpers to make a client time out or fail on demand. Writes are validated like a real server’s and are there when you read them back. Free, no key.
Connect
- JSON-RPC 2.0
- POST https://api.sondahub.com/jsonrpc
- OpenRPC
- GET https://api.sondahub.com/jsonrpc
- XML-RPC
- POST https://api.sondahub.com/xmlrpc
- Methods
- {api}.{verb}{Thing} — store.getProduct
- Auth
- none
| API | Methods | Count |
|---|---|---|
| Store | store.listCategories, store.getCategory… | 45 |
| Fleet | fleet.listSites, fleet.getSite… | 25 |
| Bank | bank.listCustomers, bank.getCustomer… | 30 |
| Social | social.listUsers, social.getUser… | 25 |
| Helpdesk | helpdesk.listTeams, helpdesk.getTeam… | 25 |
| Flights | flights.listAirports, flights.getAirport… | 20 |
| Identity | identity.listUsers, identity.getUser… | 15 |
Over the same data and the same rules as the REST API. Besides them: system.echo (answers what it was given), system.time, system.sleep (up to ten seconds a request — for testing a client’s timeout) and system.fail (answers the error you ask for — for testing how a client handles one).
Try JSON-RPC
curl https://api.sondahub.com/jsonrpc \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"store.getProduct","params":[1]}'
curl https://api.sondahub.com/jsonrpc \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"store.listProducts","params":{"limit":3,"sort":"-price","filter":{"price_lt":30}}}'
curl https://api.sondahub.com/jsonrpc \
-H "Content-Type: application/json" \
-d '[
{"jsonrpc":"2.0","id":"a","method":"store.createCategory","params":{"category":{"name":"Garden","slug":"garden"}}},
{"jsonrpc":"2.0","id":"b","method":"store.getCategory","params":[9]},
{"jsonrpc":"2.0","method":"system.echo","params":["no id, no answer"]}
]'
curl https://api.sondahub.com/jsonrpc \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":3,"method":"store.createProduct","params":{"product":{"name":"Teapot","price":-1}}}'
curl https://api.sondahub.com/jsonrpc \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":4,"method":"store.getUnicorn","params":[1]}'
curl https://api.sondahub.com/jsonrpc \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":5,"method":"system.fail","params":{"code":-32050,"message":"Upstream timed out","data":{"retry":true}}}'
Error codes
JSON-RPC answers HTTP 200 with an error object; XML-RPC a fault with the same codes (the interoperability set’s, for the first five). For the spec’s own codes the message is the spec’s words and the specifics are in data; for the others the message says what happened.
| Code | Meaning |
|---|---|
| -32700 | Parse error — the body is not JSON (or not well-formed XML) |
| -32600 | Invalid Request — not a JSON-RPC 2.0 call: no "jsonrpc": "2.0", a method that isn’t a string, params that aren’t an array or object, an empty batch |
| -32601 | Method not found |
| -32602 | Invalid params — a wrong argument, or a record the rules refuse (data.details lists each field) |
| -32603 | Internal error |
| -32001 | Not found — no record with that id |
| -32002 | Conflict — already exists, sold out, already following… |
| -32003 | Refused by the rules — an impossible status change |
| -32029 | Rate limited |
| -32000 | Any other server error |
XML-RPC
The same methods, positional, with the spec’s types — int/i4, boolean, string, double, dateTime.iso8601, base64, struct, array — and the two common extensions: nil for an empty field and i8 for big integers. The introspection methods and system.multicall are there, as are system.getCapabilities and the test helpers.
curl https://api.sondahub.com/xmlrpc \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0"?>
<methodCall>
<methodName>store.getProduct</methodName>
<params>
<param><value><int>1</int></value></param>
</params>
</methodCall>'
curl https://api.sondahub.com/xmlrpc \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0"?>
<methodCall>
<methodName>store.getProduct</methodName>
<params>
<param><value><int>999999</int></value></param>
</params>
</methodCall>'
curl https://api.sondahub.com/xmlrpc \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0"?>
<methodCall>
<methodName>system.methodSignature</methodName>
<params>
<param><value><string>store.updateProduct</string></value></param>
</params>
</methodCall>'
curl https://api.sondahub.com/xmlrpc \
-H "Content-Type: text/xml" \
-d '<?xml version="1.0"?>
<methodCall>
<methodName>system.multicall</methodName>
<params>
<param><value><array><data>
<value><struct><member><name>methodName</name><value><string>store.getCategory</string></value></member><member><name>params</name><value><array><data><value><int>1</int></value></data></array></value></member></struct></value>
<value><struct><member><name>methodName</name><value><string>system.time</string></value></member><member><name>params</name><value><array><data></data></array></value></member></struct></value>
</data></array></value></param>
</params>
</methodCall>'
From code
Python (xmlrpc.client, in the standard library)
import xmlrpc.client
class Session(xmlrpc.client.SafeTransport):
"""Carries the session (your writes) from each answer to the next call."""
token = None
def send_headers(self, connection, headers):
if self.token:
headers.append(('X-Sondahub-Session', self.token))
super().send_headers(connection, headers)
def parse_response(self, response):
self.token = response.getheader('X-Sondahub-Session') or self.token
return super().parse_response(response)
hub = xmlrpc.client.ServerProxy('https://api.sondahub.com/xmlrpc', transport=Session(), allow_none=True)
print(hub.store.getProduct(1)['name'])
print(hub.store.listProducts({'limit': 5, 'filter': {'price_lt': 30}})['total'])
made = hub.store.createCategory({'name': 'Garden', 'slug': 'garden'})
print(hub.store.getCategory(made['id'])['name']) # Garden: the session carried it
print(hub.system.methodSignature('store.updateProduct')) # [['struct', 'int', 'struct']]
JavaScript (fetch)
// JSON-RPC needs no library: one fetch, and the session carried by hand
let session = null
async function call(method, params) {
const res = await fetch('https://api.sondahub.com/jsonrpc', {
method: 'POST',
headers: { 'Content-Type': 'application/json', ...(session ? { 'X-Sondahub-Session': session } : {}) },
body: JSON.stringify({ jsonrpc: '2.0', id: crypto.randomUUID(), method, params }),
})
session = res.headers.get('X-Sondahub-Session') ?? session
const answer = await res.json()
if (answer.error) throw Object.assign(new Error(answer.error.message), answer.error)
return answer.result
}
const made = await call('store.createCategory', { category: { name: 'Garden', slug: 'garden' } })
console.log((await call('store.getCategory', [made.id])).name)
What it answers
Questions
How are the methods named?
{api}.{verb}{Thing}: store.listProducts, store.getProduct, bank.createTransfer, helpdesk.updateTicket, flights.deleteBooking — five per collection, the same on JSON-RPC and XML-RPC. system.listMethods names all of them.
By position or by name?
Either, on JSON-RPC: [1] or {"id": 1}; an update is [id, {fields}] or {"id": …, "product": {fields}}; a list takes {"page", "limit", "sort", "q", "filter"} or those in that order. XML-RPC is positional, as the spec is; a list takes one options struct, or the same values in order.
Do batches and notifications work?
Yes. A batch runs its calls in order (so it can create something and read it back) and answers an array, leaving out the notifications; a batch of nothing but notifications, or a single notification, is answered 204 with no body. Up to 100 calls per batch, and up to 100 in an XML-RPC system.multicall; one request sleeps ten seconds at most in all, and once its answer passes 8 MB the calls left are answered with an error instead of run.
Do writes stick?
Yes, for you: creates, updates and deletes are validated and run through the same rules as REST, and the answer’s X-Sondahub-Session header carries them. Send it back and the next call sees your change; nothing is stored on the server. How sessions work.
Is there a schema to import?
The OpenRPC document at https://api.sondahub.com/jsonrpc (a GET; also the rpc.discover method): every method with its parameters, result schema, errors and an example. XML-RPC describes itself through system.methodHelp and system.methodSignature.