Methods
In the Centia.io API, instead of sending raw SQL every time you want to run a statement, you can wrap your SQL statements inside JSON-RPC methods. This means you define a named method which holds your SQL query along with optional instructions on how to interpret and format the data types.
Why do this?
- Reusability: Once a method is created, clients just call the method by name.
- Consistency: You define type hints to specify the data types of the query results and formats for how the data should appear (e.g., date formats).
- Encapsulation: The SQL code is stored on the server side, reducing the chance of SQL injection and improving security.
- Easier client code: Clients don’t need to embed or parse SQL, they just call methods.
JSON-RPC is a stateless, lightweight remote procedure call (RPC) protocol that uses JSON for data interchange. Defined by the JSON-RPC 2.0 specification, it enables clients to invoke methods on a server by sending JSON-formatted requests and receiving JSON-formatted responses, with support for batch calls, notifications, and standardized error handling.
How it works
- You create a JSON-RPC method by posting a method definition to the
methodsAPI. - You can include:
type_hints: indicating expected types for query results.type_formats: specifying formats for those types (like date formatting).output_format: specifying output format for the result (like json, csv, ndjson). See SQL → Output formats.srs: specifying output geospatial reference system for PostGIS geometry. Must be specified as an EPSG code. Defaults to 4326.
- When calling the method later, clients only send the method name and parameters (if any).
Example
Suppose you have a SQL query that fetches the current timestamp:
select now() as date
You can wrap it as a JSON-RPC method named with a date formatting hint: getDate
POST https://api.centia.io/api/v4/methods
Content-Type: application/json
Accept: application/json; charset=utf-8
Authorization: Bearer abc123
{
"q": "select now() as date",
"method": "getDate",
"type_formats": {
"date": "D M d Y"
}
}
- Here
"method": "getDate"tells the server to save this SQL as a callable method. "type_formats"specifies that thedatecolumn should be formatted asD M d Y(e.g., "Thu May 08 2025").
After this is done, a client can simply call:
{
"jsonrpc": "2.0",
"method": "getDate",
"id": 1
}
And receive properly typed and formatted results without sending the SQL each time..
{
"jsonrpc": "2.0",
"result": {
"schema": {
"date": {
"type": "timestamptz",
"array": false
}
},
"data": [
{
"date": "Thu May 08 2025"
}
]
},
"id": 1
}
The result property is similar to what the SQL API returns.
The REST API is used like this:
POST https://api.centia.io/api/v4/call
Content-Type: application/json
Accept: application/json; charset=utf-8
Authorization: Bearer abc123
{
"jsonrpc": "2.0",
"method": "getDate",
"id": 1
}
And the websocket API is used like this:
const socket = new WebSocket("wss://event.centia.io?token=abc123");
socket.onopen = () => {
socket.send(JSON.stringify({
"jsonrpc": "2.0",
"method": "getDate",
"id": 1
}))
}
Sending parameters
When creating a method, the statement is not executed, and therefore you don't have to provide any parameters.
So if you want to create a method that gives you a formatted date plus a given number of days like this SQL request:
{
"q": "select date(:date) + :days::int as result",
"params": {
"date": "09-07-10",
"days": 0
},
"type_hints": {
"date": "timestamptz",
"days": "int"
},
"type_formats": {
"result": "l jS F Y",
"date": "d-m-y"
}
}
You create the method without params:
{
"method": "getDatePlusDays",
"q": "select date(:date) + :days::int as result",
"type_hints": {
"date": "timestamptz",
"days": "int"
},
"type_formats": {
"result": "l jS F Y",
"date": "d-m-y"
}
}
And when call the the method with params:
{
"jsonrpc": "2.0",
"method": "getDatePlusDays",
"params": {
"date": "09-07-10",
"days": 10
},
"id": 1
}
Notifications
JSON-RPC supports notifications, a special type of request sent without an id member. Notifications instruct the server to perform the method but indicate that no response is expected.
A notification payload might look like:
{
"jsonrpc": "2.0",
"method": "notifyEvent",
"params": { "event": "user_signup", "userId": 123 }
}
Since the id field is omitted, the server must not send any response. Clients should handle notifications as one-way messages.
In a REST context, notifications will result in a 204 No Content response. In a websocket context, notifications will not result in a response.
Execute multiple methods
You can run multiple methods like this:
[
{
"jsonrpc": "2.0",
"method": "withDrawFromAccount",
"params": { "name": "joe", "amount": 100 }
},
{
"jsonrpc": "2.0",
"method": "depositToAccount",
"params": { "name": "peter", "amount": 100 }
}
]
This will run both withDrawFromAccount and depositToAccount in same transaction block,
meaning both transactions must succeed or nothing will happen.
If you are going to call the same transactional method for e.g., inserting multiple records, it's most efficient to call the method once with multiple parameters instead:
{
"jsonrpc": "2.0",
"method": "notifyEvent",
"params": [
{ "event": "user_signup", "userId": 123 },
{ "event": "user_signup", "userId": 124 },
{ "event": "user_signup", "userId": 125 },
{ "event": "user_signup", "userId": 126 },
{ "event": "user_signup", "userId": 127 },
{ "event": "user_signup", "userId": 128 }
]
}
Error Handling
In the JSON-RPC 2.0 specification, errors are communicated using a standard structure in the response.
If an error occurs during the processing of a request,
the error object will be present in the response instead of the result object.
Error Response Format
An error response has the following structure:
{ "jsonrpc": "2.0", "error": { "code": , "message": " ", "data": }, "id": <id from request | null> }
Error Object Fields
-
code: A numeric code that indicates the type of error. JSON-RPC defines these codes:
-32600: Invalid Request (The JSON sent is not a valid Request object.)-32601: Method Not Found (The method does not exist or is unavailable.)-32602: Invalid Params (Invalid or missing method parameters.)-32603: Internal Error (Internal server error occurred while processing the request.)
-
message: A short description of the error. This is intended for developers and should not change based on runtime context.
-
data: Additional information about the error, which could include server-specific debug information or additional details to help the client understand what went wrong.
-
id: Mirrors the
idfrom the request ornullwhen the id is not present in the request (e.g., for notifications).
Example Error Scenarios and Responses
Invalid Request
This error occurs when the JSON payload does not conform to the JSON-RPC 2.0 specification.
Request:
{ "jsonrpc": "2.0", "invalid_property": "invalid value" }
Response:
{ "jsonrpc": "2.0", "error": { "code": -32600, "message": "Invalid Request" }, "id": null }
Method Not Found
This error occurs when an unknown or unsupported method is invoked.
Request:
{ "jsonrpc": "2.0", "method": "unknownMethod", "id": 10 }
Response:
{ "jsonrpc": "2.0", "error": { "code": -32601, "message": "Method not found" }, "id": 10 }
Invalid Parameters
This error occurs when the parameters provided in the request are invalid.
Request:
{ "jsonrpc": "2.0", "method": "getDate", "params": { "invalid": "param" }, "id": 2 }
Response:
{ "jsonrpc": "2.0", "error": { "code": -32602, "message": "Invalid params" }, "id": 2 }
Internal Server Error
This error occurs when an unexpected error happens on the server while processing the request.
Response:
{ "jsonrpc": "2.0", "error": { "code": -32603, "message": "Internal error" }, "id": 3 }
Client-Side Handling
Clients should handle error responses gracefully by checking for the presence of the error object and using the code and message fields to determine actions or inform the user.
Get methods
- HTTP
- CLI
GET https://api.centia.io/api/v4/methods HTTP/1.1
Accept: application/json
Authorization: Bearer abc123
GET https://api.centia.io/api/v4/methods/getDate HTTP/1.1
Accept: application/json
Authorization: Bearer abc123
centia rpc get
centia rpc get "getDate"
Update methods
- HTTP
- CLI
PATCH https://api.centia.io/api/v4/methods/getDate HTTP/1.1
Content-Type: application/json
Authorization: Bearer abc123
{
"q": "select now()::date as date",
"type_formats": {
"date": "Y-m-d"
}
}
centia rpc update "getDate"
Delete methods
- HTTP
- CLI
DELETE https://api.centia.io/api/v4/methods/getDate HTTP/1.1
Authorization: Bearer abc123
DELETE https://api.centia.io/api/v4/methods/getDate,getDatePlusDays HTTP/1.1
Authorization: Bearer abc123
centia rpc drop "getDate"
Using the SDK
The SDK calls JSON-RPC methods through the Rpc class:
- Class:
new Rpc() - Method:
call(request: RpcRequest): Promise<RpcResponse> - Endpoint:
POST https://api.centia.io/api/v4/call
Types (simplified):
RpcRequesthasjsonrpc: "2.0",method, optionalparams, optionalidRpcResponsehasjsonrpc: "2.0",id, andresultwith{ schema, data }
import { Rpc } from "@centia-io/sdk";
const rpc = new Rpc();
const payload = { a: 1, b: "hello" };
const res = await rpc.call({
jsonrpc: "2.0",
method: "typeTest",
params: payload,
id: 1
});
console.log(res.result.schema);
console.log(res.result.data); // array of rows
Typing the rows:
import type { PgTypes } from "@centia-io/sdk";
interface Row extends PgTypes.DataRow {
a: number;
b: string;
}
const res = await rpc.call({ jsonrpc: "2.0", method: "typeTest", params: payload }) as PgTypes.RpcResponse<Row>;
createApi
A tiny helper that builds a Proxy around Rpc so you can call api.someMethod(params) directly, with TypeScript autocompletion and type-checking based on your own interface.
Under the hood, each property access becomes a JSON-RPC call with the property name as the method. The helper returns result.data (array of rows) from the RPC response.
import { createApi } from "@centia-io/sdk";
import type { PgTypes, RowOfApiMethod } from "@centia-io/sdk";
// Define the shape of your RPC methods and return types
// Don't write this by hand. Get the interfaces from /api/v4/interfaces
interface MyApi {
getBandById(params: {
band_id: number;
}): Promise<Array<{
name: PgTypes.Varchar;
subgenre: PgTypes.Varchar;
albums: PgTypes.PgArray<PgTypes.Varchar>;
}>>;
}
// Create the API proxy
const api = createApi<MyApi>();
// If you need the type of the result, use the helper RowOfApiMethod
type Band = RowOfApiMethod<MyApi, "getBandById">
// Call the method
const bands: Band[] = await api.getBandById({
band_id: 1
});
console.log(bands); // typed row array
createApi<T>()relies on naming conventions: the property name is the JSON-RPCmethodname.- Each call returns
result.datafrom the RPC response (array of rows).
JSON-RPC TypeScript interfaces
Instead of defining the interfaces yourself as shown above, you can use the API /api/v4/interfaces helper to get the interfaces for all JSON-RPC methods.
- Define your JSON-RPC method. Input and output types are inferred from an actual request/response.
Consider this method definition:
{
"q": "select :x as unknown",
"method": "getX"
}
The type of x can not be inferred from the definition. So requesting /api/v4/interfaces will return:
export interface Api {
getX(params: Record<string, unknown>): Promise<Record<string, unknown>>;
}
- You will need to 'cast' the field
xto the correct type. This is not necessary for real columns in tables, but for the example above, you can cast the field toint:
{
"q": "select :x::int as my_int",
"method": "getX"
}
- Dry run the request to set the types. Types are only inferred for dry run calls. This will cache the types on the server:
POST https://api.centia.io/api/v4/call/dry
Content-Type: application/json
Accept: application/json; charset=utf-8
Authorization: Bearer abc123
{
"jsonrpc": "2.0",
"method": "getX",
"params": { "x": 1 },
"id": 1
}
And /api/v4/interfaces will now return a typed interface:
export interface Api {
getX(params: { x: number; }): Promise<{ my_int: number; }[]>;
}
This is ready for import and use in your application:
export interface Api {
getX(params: { x: number; }): Promise<{ my_int: number; }[]>
}
import { createApi } from "@centia-io/sdk"
import type { Api } from "./api"
const api = createApi<Api>()
const res = await api.getX({ x: 1 })
console.log(res)