**JARPC 1.0 Draft** JARPC, pronounced **jarpsee**, stands for **JSON Application RPC**. JARPC is a standard JSON RPC protocol optimized for web applications. It uses HTTP paths as procedure names, JSON request bodies for parameters, and a consistent response envelope for application results. It aims to be wider and more practical than existing standards, improving developer experience and code readability. **Design Goals** - Keep request and response bodies flat and predictable. - Map naturally to HTTP routes and backend files. - Work well with JavaScript `fetch()`. - Work well with plain HTML forms and `FormData`. - Avoid nested form conventions, dot-path parsing, and heavy serializers. - Standardize common application concerns such as validation errors and pagination. - Remain mechanically adaptable to JSON-RPC and MCP. **Endpoint Model** The HTTP path identifies the procedure. ```http POST /users/create Content-Type: application/json ``` Request: ```json { "id": "r-1001", "params": { "email": "ada@example.com", "password": "secret" } } ``` There is no body-level `method`. The route path is the method. The request method is usually POST, not GET. **Request Format** ```ts type JARPCRequest
= {
id?: string | number;
params?: P;
};
```
Fields:
- `id`: optional request correlation identifier.
- `params`: endpoint input parameters.
If `id` is present, the response must echo it exactly. If `id` is omitted, the response should omit it.
For form-oriented endpoints, `params` should usually be a flat object whose keys match HTML input `name` attributes.
```js
var params = Object.fromEntries(new FormData(form));
```
JARPC does not define dot-notation or bracket-notation expansion for nested form values. Field names are opaque strings.
**Success Response**
A successful response has `ok: true`.
```json
{
"ok": true,
"id": "r-1001",
"data": {
"userId": 42
}
}
```
Success with no returned data:
```json
{
"ok": true,
"id": "r-1001"
}
```
Fields:
- `ok`: required, always `true` for success.
- `id`: echoed only when supplied by the request.
- `data`: primary successful return value.
- `pagination`: standardized pagination metadata, when applicable.
- `details`: endpoint-specific supplemental information, when documented.
**Error Response**
An error response has `ok: false`.
```json
{
"ok": false,
"id": "r-1001",
"message": "Unauthorized",
"code": "UNAUTHORIZED"
}
```
Fields:
- `ok`: required, always `false` for application failure.
- `id`: echoed only when supplied by the request.
- `message`: human-readable error message.
- `code`: optional stable machine-readable error code. It may be a string or
number.
- `fields`: standardized field/input validation errors.
- `details`: endpoint-specific supplemental information, when documented.
**Validation Errors**
Field validation errors must use `fields`.
```json
{
"ok": false,
"id": "r-1001",
"message": "Validation failed",
"code": "VALIDATION_ERROR",
"fields": {
"email": ["Must be a valid email"],
"password": ["Must be at least 12 characters"]
}
}
```
`fields` is a flat object whose keys are field names and whose values are arrays of validation messages.
Field names are opaque strings and should normally match HTML input `name` attributes.
```html
```
Example:
```json
{
"fields": {
"company_name": ["Required"],
"items_0_name": ["Required"]
}
}
```
JARPC does not define nested field syntax. It does not require clients to parse dot notation, bracket notation, or object paths.
Field errors must not be placed in `details`.
**Flat Form Principle**
JARPC favors flat form flows.
For web application forms, deeply nested form payloads often require custom serializers, path parsers, deep merging, and fragile error mapping. JARPC avoids prescribing those mechanisms.
A simple form can be serialized with:
```js
var params = Object.fromEntries(new FormData(form));
```
And field errors can be mapped back with:
```js
for (var [name, messages] of Object.entries(result.fields || {})) {
var input = form.elements.namedItem(name);
showFieldError(input, messages);
}
```
Applications should prefer flat field names and, when useful, multi-step flows over deeply nested form payloads.
**Pagination**
Paginated responses must use `pagination`.
Page-based pagination:
```json
{
"ok": true,
"data": [
{
"id": 1,
"name": "Ada"
}
],
"pagination": {
"page": 1,
"pageSize": 20,
"total": 134,
"hasMore": true
}
}
```
Cursor-based pagination:
```json
{
"ok": true,
"data": [
{
"id": 1,
"name": "Ada"
}
],
"pagination": {
"nextCursor": "eyJpZCI6MX0=",
"previousCursor": null,
"hasMore": true
}
}
```
Pagination metadata must not be placed in `details`.
Suggested type:
```ts
type Pagination =
| {
page: number;
pageSize: number;
total?: number;
hasMore?: boolean;
}
| {
nextCursor?: string | null;
previousCursor?: string | null;
hasMore: boolean;
};
```
**Details**
`details` is an endpoint-specific supplemental field.
```json
{
"ok": false,
"message": "Import failed",
"code": "IMPORT_FAILED",
"details": {
"row": 17,
"column": "email",
"rawValue": "not-an-email"
}
}
```
Rules:
- `details` has no global meaning.
- Each endpoint that returns `details` must document its structure and meaning.
- Do not use `details` for standardized concepts such as `fields` or `pagination`.
**HTTP Status Codes**
JARPC separates transport status from application status.
Application results are represented by the JSON envelope:
```json
{
"ok": false,
"message": "User not found",
"code": "USER_NOT_FOUND"
}
```
This may be returned with HTTP `200`, because the endpoint executed successfully and returned an application-level result.
Recommended HTTP status usage:
- `200`: endpoint executed and returned a JARPC result, whether `ok` is true or false.
- `404`: endpoint/path does not exist.
- `500`: server crash or unexpected infrastructure failure.
- `429`: request throttled before normal endpoint execution.
- `301` / `302`: HTTP-level redirect.
- `401` / `403`: HTTP-level auth only, such as Basic Auth, proxy auth, or middleware rejection.
Application authentication and authorization errors should normally use HTTP `200` with `ok: false`:
```json
{
"ok": false,
"message": "Please sign in",
"code": "UNAUTHENTICATED"
}
```
```json
{
"ok": false,
"message": "You do not have permission to do that",
"code": "UNAUTHORIZED"
}
```
**Client Usage**
```js
var response = await fetch("/users/create", {
method: "POST",
headers: {
"content-type": "application/json"
},
body: JSON.stringify({
id: "r-1001",
params: Object.fromEntries(new FormData(form))
})
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
var result = await response.json();
if (!result.ok) {
displayErrors(result);
return;
}
render(result.data);
```
There are two levels of success:
```js
response.ok // HTTP/transport success
result.ok // application success
```
**Type Definition**
```ts
type JARPCId = string | number;
type FieldErrors = Record = {
id?: JARPCId;
params?: P;
};
type JARPCSuccess