JSON-RPC
JSON-RPC
POST /{prefix}/{database}/jsonrpc appelle une fonction PostgreSQL et renvoie son résultat sous forme de réponse
JSON-RPC 2.0. Cette page est la
référence ; pour un premier exemple fonctionnel, voir le Démarrage rapide.
1. Requête
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}| Champ | Description |
|---|---|
method | Obligatoire. schema.function, 256 caractères au maximum. Les identifiants entre guillemets ("my schema"."my fn") sont autorisés. Le schéma est obligatoire : hello_world seul est rejeté avec 400. capabilities est une méthode intégrée (section 5). |
params | Toute valeur JSON, transmise telle quelle à la fonction comme argument jsonb. Une valeur absente ou null devient {}. Les objets sont la convention ; les tableaux et les scalaires fonctionnent aussi. |
id | Toute valeur JSON ; elle est recopiée dans la réponse. Si elle est absente, la réponse contient "id": null (il n’existe pas de notifications « envoyer et oublier»). |
jsonrpc | Par convention "2.0" ; il n’est pas validé en entrée, la réponse contient toujours "2.0". |
idempotencyKey | Extension facultative (section 7). 256 caractères au maximum. |
Les requêtes par lot (un tableau JSON d’appels) ne sont pas prises en charge et renvoient 400 — envoyez un appel par requête HTTP.
2. Réponse
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}Contrairement à JSON-RPC sur une simple socket, les erreurs définissent aussi un statut HTTP significatif, ce qui permet aux clients et aux proxys de réagir
sans analyser le corps. Le error.code vaut 0 sauf si un code JSON-RPC spécifique s’applique
(-32601 fonction inconnue, -32001 permission refusée, -32000 requête en double).
La liste complète se trouve sur la page Codes d’erreur.
3. Écrire une fonction
Une méthode est une fonction PostgreSQL qui prend exactement un argument de type jsonb
et renvoie json ou jsonb. Les fonctions avec une autre signature ne sont ni listées par
capabilities ni appelables.
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;- Valeur de retour. Le résultat JSON est envoyé comme
result. UnNULLSQL (comme ci-dessus, quand aucune ligne ne correspond) devient"result": null. La fonction doit renvoyer du JSON valide : un type de retourtextéchoue avec 500 sauf si le texte est par hasard du JSON — utilisezto_json('hi'::text)pour un résultat de type chaîne. - Erreurs dans la fonction.
RAISE EXCEPTIONou toute erreur SQL interrompt l’appel avec HTTP 500 et le message génériqueFunction call failed; le vrai message PostgreSQL n’est écrit que dans le journal du serveur, de sorte que les détails du schéma ne fuient pas. Signalez plutôt les erreurs métier attendues (introuvable, validation) dans la valeur de retour, par exemple{"ok": false, "reason": "..."}. - Transactions. Chaque appel s’exécute dans une transaction validée au retour de la fonction et annulée à la moindre erreur.
- Permissions. L’appel s’exécute avec le rôle PostgreSQL authentifié, donc
GRANT,REVOKEet la Row-Level Security décident de ce que l’appelant peut faire. Un rôle a besoin deUSAGEsur le schéma et deEXECUTEsur la fonction. PostgreSQL accorde par défautEXECUTEsur les nouvelles fonctions à tout le monde ; révoquez donc ce droit (ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC) et gardez les fonctions exposées dans un schéma dédié — voir Sécurité & Authentification.
4. Décrire une fonction pour les clients et les agents IA
La première ligne du commentaire de la fonction est sa description ; un objet JSON après --- PARAMS --- décrit les paramètres
(properties de JSON Schema). Les deux apparaissent dans capabilities, la spécification OpenAPI et MCP tools/list :
COMMENT ON FUNCTION api.find_user(jsonb) IS 'Looks up a user by id.
--- PARAMS ---
{"id": {"type": "integer", "description": "User id"}}';Un commentaire dont le JSON après le marqueur est malformé est ignoré (la fonction retombe sur une description générique des paramètres) et ne casse jamais la découverte.
5. Méthodes intégrées
capabilities
Liste les fonctions que le rôle authentifié peut appeler (EXECUTE et USAGE sur le schéma) ; les schémas système et les fonctions d’extension sont masqués.
Chaque entrée comporte method, description, parameters, http_method, endpoint et
kind (rpc, ou file pour les fonctions de fichier).
{"jsonrpc":"2.0","method":"capabilities","id":1}La connexion pour obtenir un JWT n’est pas une méthode JSON-RPC : utilisez POST /{prefix}/{database}/token avec des identifiants HTTP Basic (section 6).
L’ancienne méthode get_jwt, qui recevait le mot de passe dans params, a été supprimée et répond 404 / -32601 avec un renvoi vers le nouvel endpoint.
6. Authentification
Chaque requête nécessite un en-tête Authorization ; une requête sans cet en-tête est rejetée avec 401 avant qu’aucune connexion à la base de données ne soit ouverte.
| En-tête | Ce qui se passe |
|---|---|
Basic <base64(user:password)> | Un pool de connexions authentifié comme cet utilisateur PostgreSQL. Le plus simple ; pas de jetons. À envoyer uniquement en HTTPS. |
Bearer <jwt> | Jeton issu de POST /{prefix}/{database}/token (ou de votre fournisseur d’identité). PgArachne bascule vers le rôle du jeton avec SET LOCAL ROLE ; le rôle de service a besoin de GRANT user TO pgarachne. |
Bearer <api token> | Jeton longue durée créé avec pgarachne.add_api_token(...), lié à un rôle. Adapté aux services. |
Obtenir un JWT : POST /{prefix}/{database}/token
Échange un identifiant et un mot de passe PostgreSQL, envoyés par authentification HTTP Basic, contre un JWT. Les identifiants voyagent dans l’en-tête Authorization
— jamais dans un corps JSON — et l’endpoint a sa propre URL, de sorte qu’un reverse proxy peut appliquer des limites plus strictes aux connexions sans lire les corps de requête.
Il nécessite JWT_SECRET ; sinon il renvoie 404. Chaque tentative compte dans la limite de débit de connexion et la réponse n’est jamais mise en 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}'Les tentatives échouées sont limitées en débit (HTTP 429). Tous les détails, les durées de vie des jetons et un fournisseur d’identité externe sont décrits sur la page Sécurité & Authentification.
7. Idempotence
Ajoutez "idempotencyKey": "order-42" pour rendre un appel sûr à rejouer. Le premier appel avec une clé s’exécute normalement ; une clé répétée est rejetée
avant l’exécution de la fonction avec HTTP 409 et -32000 « This request has already been processed». La clé est enregistrée dans la même
transaction que l’appel, donc un appel qui a échoué (et a été annulé) peut être rejoué avec la même clé. Les clés sont isolées par rôle.
- Avec l’authentification par JWT ou jeton API, cela fonctionne immédiatement.
- Avec l’authentification Basic, c’est l’utilisateur lui-même qui écrit la clé ; il a donc besoin de
USAGEsur le schémapgarachneet deINSERTsurpgarachne.requests(sinon l’appel échoue avec 500Idempotency check failed). - Les anciennes clés ne sont pas supprimées automatiquement — planifiez
pgarachne.cleanup_idempotency_keys()(voir Nettoyage des clés d’idempotence).
8. Limites
- Corps de requête :
MAX_REQUEST_BYTES(2 Mio par défaut), sinon 413. methodetidempotencyKey: 256 caractères chacun.- Échecs d’authentification :
LOGIN_RATE_LIMIT/LOGIN_RATE_LIMIT_PER_IPparLOGIN_RATE_WINDOW(voir Configuration).
9. L’appeler
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}'Depuis une page web, utilisez fetch() avec le même en-tête et le même corps ; une page complète à copier-coller (y compris SSE et téléchargement de fichiers) se trouve dans le
Démarrage rapide.