JSON-RPC

6 min de leitura

JSON-RPC

POST /{prefix}/{database}/jsonrpc chama uma função do PostgreSQL e retorna o resultado como uma resposta JSON-RPC 2.0. Esta página é a referência; para um primeiro exemplo funcional veja Início rápido.

1. Requisição

POST /db/my_database/jsonrpc
Authorization: Basic ...            (or Bearer <jwt / api token>)
Content-Type: application/json

{"jsonrpc": "2.0", "method": "api.hello_world", "params": {"name": "Alice"}, "id": 1}
CampoDescrição
methodObrigatório. schema.function, no máximo 256 caracteres. Identificadores entre aspas ("my schema"."my fn") são permitidos. O schema é obrigatório: hello_world sozinho é rejeitado com 400. capabilities é um método integrado (seção 5).
paramsQualquer valor JSON, passado sem alterações à função como seu argumento jsonb. Se ausente ou null, vira {}. Objetos são a convenção; arrays e escalares também funcionam.
idQualquer valor JSON; é copiado para a resposta. Se ausente, a resposta contém "id": null (não há notificações do tipo dispare e esqueça).
jsonrpcPor convenção "2.0"; não é validado na entrada, a resposta sempre traz "2.0".
idempotencyKeyExtensão opcional (seção 7). No máximo 256 caracteres.

Requisições em lote (um array JSON de chamadas) não são suportadas e retornam 400 — envie uma chamada por requisição HTTP.

2. Resposta

HTTP 200  {"jsonrpc":"2.0","result":{"message":"Hello, Alice!"},"id":1}

HTTP 404  {"jsonrpc":"2.0","error":{"code":-32601,"message":"Function does not exist"},"id":1}

Diferentemente do JSON-RPC puro sobre um socket, os erros também definem um status HTTP significativo, para que clientes e proxies possam reagir sem analisar o corpo. O error.code é 0 a menos que um código JSON-RPC específico se aplique (-32601 função desconhecida, -32001 permissão negada, -32000 requisição duplicada). A lista completa está na página Códigos de Erro.

3. Escrevendo uma função

Um método é uma função do PostgreSQL que recebe exatamente um argumento do tipo jsonb e retorna json ou jsonb. Funções com outras assinaturas não são listadas por capabilities nem podem ser chamadas.

CREATE OR REPLACE FUNCTION api.find_user(payload jsonb DEFAULT '{}'::jsonb)
RETURNS json LANGUAGE sql STABLE AS $$
    SELECT row_to_json(u) FROM (
        SELECT id, name FROM app.users WHERE id = (payload->>'id')::int
    ) u;
$$;

GRANT USAGE ON SCHEMA api TO web_user;
GRANT EXECUTE ON FUNCTION api.find_user(jsonb) TO web_user;
  • Valor de retorno. O resultado JSON é enviado como result. Um NULL do SQL (como acima, quando nenhuma linha corresponde) vira "result": null. A função deve retornar JSON válido: um tipo de retorno text falha com 500 a menos que o texto seja JSON — use to_json('hi'::text) para um resultado do tipo string.
  • Erros dentro da função. RAISE EXCEPTION ou qualquer erro SQL aborta a chamada com HTTP 500 e a mensagem genérica Function call failed; a mensagem real do PostgreSQL só é escrita no log do servidor, então detalhes do schema não vazam. Informe os erros de negócio esperados (não encontrado, validação) no valor de retorno, por exemplo {"ok": false, "reason": "..."}.
  • Transações. Cada chamada é executada em uma transação que é confirmada quando a função retorna e revertida em caso de qualquer erro.
  • Permissões. A chamada é executada como o papel (role) PostgreSQL autenticado, então GRANT, REVOKE e Row-Level Security decidem o que quem chama pode fazer. Um papel precisa de USAGE no schema e EXECUTE na função. O PostgreSQL concede EXECUTE em novas funções a todos por padrão, então revogue isso (ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC) e mantenha as funções expostas em um schema dedicado — veja Segurança e Autenticação.

4. Descrevendo uma função para clientes e agentes de IA

A primeira linha do comentário da função é a sua descrição; um objeto JSON após --- PARAMS --- descreve os parâmetros (properties do JSON Schema). Ambos aparecem em capabilities, na especificação OpenAPI e no MCP tools/list:

COMMENT ON FUNCTION api.find_user(jsonb) IS 'Looks up a user by id.
--- PARAMS ---
{"id": {"type": "integer", "description": "User id"}}';

Um comentário com JSON malformado após o marcador é ignorado (a função recorre a uma descrição genérica de parâmetros) e nunca quebra a descoberta.

5. Métodos integrados

capabilities

Lista as funções que o papel autenticado pode chamar (EXECUTE e USAGE do schema); schemas do sistema e funções de extensões ficam ocultos. Cada entrada tem method, description, parameters, http_method, endpoint e kind (rpc, ou file para funções de arquivo).

{"jsonrpc":"2.0","method":"capabilities","id":1}

Fazer login para obter um JWT não é um método JSON-RPC: use POST /{prefix}/{database}/token com credenciais HTTP Basic (seção 6). O antigo método get_jwt, que recebia a senha em params, foi removido e responde 404 / -32601 com uma indicação do novo endpoint.

6. Autenticação

Toda requisição precisa de um cabeçalho Authorization; uma requisição sem ele é rejeitada com 401 antes de abrir qualquer conexão com o banco de dados.

CabeçalhoO que acontece
Basic <base64(user:password)>Um pool de conexões autenticado como esse usuário do PostgreSQL. O mais simples; sem tokens. Envie somente via HTTPS.
Bearer <jwt>Token de POST /{prefix}/{database}/token (ou do seu próprio provedor de identidade). O PgArachne troca para o papel do token com SET LOCAL ROLE; o papel de serviço precisa de GRANT user TO pgarachne.
Bearer <api token>Token de longa duração criado com pgarachne.add_api_token(...), vinculado a um papel. Bom para serviços.

Obtendo um JWT: POST /{prefix}/{database}/token

Troca um login e senha do PostgreSQL, enviados com autenticação HTTP Basic, por um JWT. As credenciais trafegam no cabeçalho Authorization — nunca em um corpo JSON — e o endpoint tem sua própria URL, de modo que um proxy reverso pode aplicar limites mais rígidos aos logins sem ler corpos de requisição. Requer JWT_SECRET; caso contrário retorna 404. Cada tentativa conta para o limite de taxa de login e a resposta nunca pode ser armazenada em cache.

curl -X POST http://localhost:8080/db/my_database/token -u web_user:password
→ {"token":"eyJhbGciOi...","token_type":"Bearer","expires_in":28800}

curl http://localhost:8080/db/my_database/jsonrpc -H "Authorization: Bearer eyJhbGciOi..." \
  -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"capabilities","id":1}'

Tentativas falhas têm limite de taxa (HTTP 429). Todos os detalhes, a duração dos tokens e um provedor de identidade externo estão na página Segurança e Autenticação.

7. Idempotência

Adicione "idempotencyKey": "order-42" para tornar uma chamada segura para nova tentativa. A primeira chamada com uma chave é executada normalmente; uma chave repetida é rejeitada antes de a função executar, com HTTP 409 e -32000 «This request has already been processed». A chave é armazenada na mesma transação da chamada, então uma chamada que falhou (e foi revertida) pode ser repetida com a mesma chave. As chaves têm namespace por papel.

  • Com autenticação por JWT ou token de API funciona sem configuração adicional.
  • Com autenticação Basic o próprio usuário grava a chave, então ele precisa de USAGE no schema pgarachne e INSERT em pgarachne.requests (caso contrário a chamada falha com 500 Idempotency check failed).
  • Chaves antigas não são removidas automaticamente — agende pgarachne.cleanup_idempotency_keys() (veja Limpeza de Chaves de Idempotência).

8. Limites

  • Corpo da requisição: MAX_REQUEST_BYTES (padrão 2 MiB); caso contrário, 413.
  • method e idempotencyKey: 256 caracteres cada.
  • Falhas de autenticação: LOGIN_RATE_LIMIT / LOGIN_RATE_LIMIT_PER_IP por LOGIN_RATE_WINDOW (veja Configuração).

9. Chamando

curl -X POST http://localhost:8080/db/my_database/jsonrpc \
  -u web_user:password -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"api.find_user","params":{"id":1},"id":1}'

A partir de uma página web use fetch() com o mesmo cabeçalho e corpo; uma página completa para copiar e colar (incluindo SSE e download de arquivos) está em Início rápido.