sondahub

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
APIMethodsCount
Storestore.listCategories, store.getCategory…45
Fleetfleet.listSites, fleet.getSite…25
Bankbank.listCustomers, bank.getCustomer…30
Socialsocial.listUsers, social.getUser…25
Helpdeskhelpdesk.listTeams, helpdesk.getTeam…25
Flightsflights.listAirports, flights.getAirport…20
Identityidentity.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

The examples on this page share one session: run them in order and each sees what the one before it wrote.
By position
curl https://api.sondahub.com/jsonrpc \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"store.getProduct","params":[1]}'
By name, with a filter
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}}}'
A batch: create, then read it back, and a notification
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"]}
]'
Invalid params: every field the rules refuse
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}}}'
Method not found
curl https://api.sondahub.com/jsonrpc \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":4,"method":"store.getUnicorn","params":[1]}'
An error on demand
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.

CodeMeaning
-32700Parse error — the body is not JSON (or not well-formed XML)
-32600Invalid 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
-32601Method not found
-32602Invalid params — a wrong argument, or a record the rules refuse (data.details lists each field)
-32603Internal error
-32001Not found — no record with that id
-32002Conflict — already exists, sold out, already following…
-32003Refused by the rules — an impossible status change
-32029Rate limited
-32000Any 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.

store.getProduct(1)
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>'
A fault
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>'
system.methodSignature
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>'
system.multicall: two calls, one answer
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

POST/jsonrpcJSON-RPC 2.0: one call or a batch; notifications answered 204.
GET/jsonrpcThe OpenRPC document (also rpc.discover).
POST/xmlrpcXML-RPC: a methodCall, faults with the interoperability codes, system.multicall.

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.