Mirajv1.0
FR

24. API REST : MIRAJ sur HTTP et HTTPS

Le point d'accès REST de miraj-server ouvre les bases à toute application qui parle HTTP : application web ou mobile, script, outil d'automatisation, autre service. Il ne demande ni pilote ni bibliothèque cliente : des requêtes HTTP et du JSON.

Il sert trois familles d'accès, ouvertes séparément sur le serveur et pour chaque client :

FamilleCheminsUsage
Endpointschemins que vous définissez (GET /clients/{id})Des API écrites en SQL (CREATE ENDPOINT) : l'application n'appelle que ce que vous avez prévu, avec des paramètres typés
Tables/api/{base}/{table}[/{clé}]Lire et écrire les lignes d'une table (filtres, tri, pagination)
SQLPOST /api/sqlSQL libre, pour un outil interne de confiance
  Application (web, mobile, script)
         │   HTTP ou HTTPS : GET https://hôte:7009/clients/42
         │   Authorization: Bearer mja_…   (ou Basic compte:mot de passe)
         ▼
  ┌─────────────────────────────── miraj-server ───────────────────────────────┐
  │  port 7007 : protocole réseau de MIRAJ                                     │
  │  port 7009 : point d'accès REST (--rest ON)                                │
  │     famille ouverte sur le serveur ? ─► client authentifié ?               │
  │     famille permise au jeton ? ─► droits SQL du compte (table, colonne,    │
  │     EXECUTE sur l'endpoint) ─► instruction SQL ─► verrous, déclencheurs,   │
  │     clés étrangères, journal, transactions, réplication                    │
  └────────────────────────────────────────────────────────────────────────────┘

Chaque requête ouvre une session MIRAJ sous le compte du client, exécute une instruction SQL ordinaire, puis ferme la session : rien n'est jamais écrit directement dans les fichiers de données, et les droits SQL du compte s'appliquent toujours, jusqu'à la colonne (chapitre 15). Chaque requête prend une place de max_connections le temps de son exécution.

24.1 Éditions concernées#

Le point d'accès REST existe dans toutes les éditions, Express comprise.

24.2 Activation et configuration#

24.2.1 Variables#

Comme le port principal : valeur par défaut, puis miraj_config.xml, puis ligne de commande (la ligne de commande l'emporte, voir 16.2). Désactivé par défaut. Les variables ON|OFF acceptent aussi true, false, 1, 0.

Variable (miraj_config.xml)ArgumentDéfautRôle
rest--rest ON|OFFOFFOuvre ou non le point d'accès
rest_port--rest-port <port>7009Port TCP (0 à 65535), distinct de port et de mcp_port ; 0 : choisi par le système, affiché au démarrage
rest_bind--rest-bind <adresse>127.0.0.1Adresse d'écoute ; hors de la boucle locale (127.0.0.1, localhost, ::1), TLS obligatoire
rest_endpoints--rest-endpoints ON|OFFONFamille endpoints ouverte sur le serveur
rest_tables--rest-tables ON|OFFOFFFamille tables ouverte sur le serveur
rest_sql--rest-sql ON|OFFOFFFamille SQL ouverte sur le serveur
rest_basic--rest-basic ON|OFFONConnexion par compte et mot de passe (Authorization: Basic) acceptée
rest_basic_access--rest-basic-access <familles>ENDPOINTSFamilles permises aux connexions par mot de passe : ALL, NONE, ou ENDPOINTS, TABLES, SQL séparés par des virgules
rest_idle_timeout--rest-idle-timeout <s>60Connexion HTTP persistante fermée après ce délai sans requête (1 à 31 536 000 secondes)
rest_statement_timeout--rest-statement-timeout <s>30Durée maximale d'une instruction (0 à 31 536 000 secondes, fraction permise ; 0 : sans limite)
rest_max_rows--rest-max-rows <n>1000Lignes rendues au plus par une réponse (1 à 1 000 000)
rest_cors_origins--rest-cors-origins <origines>videOrigines admises par CORS : vide (aucune), *, ou https://app.exemple.com,https://… (chaque origine commence par http:// ou https://)

Une famille fermée sur le serveur n'existe pas pour les clients : ses chemins répondent 404, même à un jeton ACCESS ALL.

24.2.2 Exemples#

<miraj>
    <rest>ON</rest>
    <rest_bind>0.0.0.0</rest_bind>
    <tls_cert>C:\miraj\cert.pem</tls_cert>
    <tls_key>C:\miraj\key.pem</tls_key>
    <rest_cors_origins>https://app.exemple.com</rest_cors_origins>
</miraj>
miraj-server.exe --root D:\miraj\data --log --rest ON
miraj-server.exe --root D:\miraj\data --log --rest ON --rest-tables ON --rest-sql ON --rest-port 8443 --rest-bind 0.0.0.0 --tls-cert cert.pem --tls-key key.pem

Démarrez toujours le serveur avec --log : chaque requête est alors inscrite, avec le jeton ou le compte, le statut et la durée ; une erreur SQL est inscrite en plus avec son code et l'endpoint appelé :

REST GET /clients/42 -> 200, jeton mobile, compte app@%, 0 ms, depuis 127.0.0.1
REST GET /noms -> 200, compte lecteur@%, 0 ms, depuis 127.0.0.1
[509] REST ! 1143 (42000): SELECT command denied to user 'lecteur'@'localhost' for column 'ville' in table 'clients' -- ENDPOINT gestion.villes
REST GET /villes -> 403, compte lecteur@%, 0 ms, depuis 127.0.0.1

Les erreurs et les échecs d'authentification sont aussi inscrits dans <root>\miraj\server.log.

24.2.3 Démarrage et erreurs de configuration#

SituationMessage ou effet
Point d'accès ouvertMIRAJ <version> <édition> - point d'accès REST sur 127.0.0.1:7009, accès ENDPOINTS. (, TLS avec un certificat ; accès liste les familles ouvertes, par exemple ENDPOINTS,TABLES,SQL)
rest = OFF et une variable rest_* donnéeNote : variables rest_* ignorées, le point d'accès REST est désactivé (rest = OFF ; --rest ON pour l'ouvrir). Serveur démarré, port REST fermé
rest_port égal à portPoint d'accès REST : rest_port (7007) est aussi le port principal ; choisissez un autre port (--rest-port) ; serveur arrêté.
rest_port égal à mcp_portPoint d'accès REST : rest_port (7008) est aussi le port du point d'accès MCP ; choisissez un autre port (--rest-port) ; serveur arrêté.
rest_bind hors de la boucle locale sans certificatPoint d'accès REST : écoute sur 0.0.0.0 hors de la boucle locale sans TLS refusée ; donnez un certificat (--tls-cert, --tls-key) ou gardez rest_bind sur 127.0.0.1 ; serveur arrêté.
Port déjà prispoint d'accès REST : impossible d'écouter sur 127.0.0.1:7009 : … ; serveur arrêté.
Valeur hors bornesValeur invalide pour rest_max_rows : 0 (de 1 à 1000000), Valeur invalide pour rest_basic_access : … (ALL, NONE, ou ENDPOINTS, TABLES, SQL séparés par des virgules), --rest-tables : « peut-être » inconnu (ON, OFF)…

Dans tous les cas « serveur arrêté », le code de sortie est 2. Le certificat est le même que celui du port principal (tls_cert, tls_key) ; avec un certificat, toutes les connexions du point d'accès sont chiffrées (HTTPS).

24.3 Authentification#

Chaque requête (sauf GET /api/health) présente l'une des deux formes :

FormeEn-têteQuand l'utiliser
Jeton d'APIAuthorization: Bearer mja_…Applications et services : révocable, limité à des familles, des endpoints et des bases
Compte et mot de passeAuthorization: Basic base64(compte:mot de passe)Outils d'administration, scripts ponctuels. Accepté en HTTPS, ou en HTTP depuis la boucle locale seulement ; familles limitées par rest_basic_access

Le compte d'une connexion par mot de passe est choisi comme sur le port principal, d'après l'adresse du client : depuis la machine du serveur, 'app'@'localhost' l'emporte sur 'app'@'%'.

Refus (statut 401, en-têtes WWW-Authenticate: Bearer realm="miraj" et, si rest_basic = ON, WWW-Authenticate: Basic realm="miraj", charset="UTF-8") :

CasMessage
Aucun en-tête AuthorizationUnauthorized: an API token (Authorization: Bearer mja_...) or Basic credentials is required
Jeton inconnu, expiré, révoqué, ou compte qui n'accepte pas l'hôte du clientUnauthorized: unknown, expired or revoked API token, or account not allowed
Compte ou mot de passe fauxUnauthorized: wrong account or password
Basic avec rest_basic = OFFUnauthorized: Basic authentication is disabled (rest_basic = OFF); use an API token
Basic en HTTP clair hors de la boucle localeUnauthorized: Basic authentication requires HTTPS

Les échecs d'authentification sont comptés par adresse, comme sur le port principal : retard croissant, puis blocage de l'adresse à max_connect_errors (FLUSH HOSTS la débloque ; 403 tant qu'elle est bloquée). Le journal les signale (SÉCURITÉ : 4 échecs d'authentification REST consécutifs depuis 127.0.0.1).

24.3.1 Jetons d'API#

CREATE API TOKEN nom [FOR compte]
    [ACCESS { ALL | famille [, famille …] }]     -- défaut : ACCESS ENDPOINTS
    [DATABASES (base, …)]
    [EXPIRE { NEVER | INTERVAL n DAY }];
--  famille := ENDPOINTS [([base.]nom, …)] | TABLES | SQL

ALTER API TOKEN nom ACCESS …;        -- familles remplacées, secret inchangé, dès la requête suivante
DROP API TOKEN [IF EXISTS] nom;
SHOW API TOKENS;
  • nom : identifiant, identifiant entre accents graves ou chaîne ; unique sur le serveur, jetons MCP compris (erreur 9035 Token '…' already exists).
  • FOR compte : sans FOR, le jeton appartient au compte de la session.
  • Les clauses ACCESS, DATABASES et EXPIRE s'écrivent dans n'importe quel ordre, chacune une fois ; EXPIRE INTERVAL n DAY demande n ≥ 1.
  • ENDPOINTS (nom, …) : un nom sans base désigne un endpoint de la base courante (erreur 1046 No database selected sans base courante).
  • DATABASES (…) : les autres bases n'existent plus pour les requêtes du jeton, même pour un compte qui y a des droits.
  • DROP API TOKEN et ALTER API TOKEN d'un jeton inconnu : erreur 9036 Unknown token '…' (avertissement 9036 avec IF EXISTS).

CREATE API TOKEN rend le secret du jeton (mja_ suivi de 24 caractères) une seule fois : seule son empreinte est gardée, dans le coffre des comptes (répliqué dans un cluster). Exemples :

-- Application mobile : seulement les API prévues pour elle
CREATE API TOKEN mobile FOR 'app'@'%' ACCESS ENDPOINTS;
-- Partenaire : deux endpoints précis, 90 jours
CREATE API TOKEN partenaire FOR 'app'@'%' ACCESS ENDPOINTS (gestion.fiche_client, gestion.liste_clients) EXPIRE INTERVAL 90 DAY;
-- Outil interne : tables et SQL libre sur une base
CREATE API TOKEN outil FOR 'dev'@'%' ACCESS TABLES, SQL DATABASES (gestion);
+--------+------------------------------+-----------+-----------+---------+
| name   | token                        | account   | access    | expires |
+--------+------------------------------+-----------+-----------+---------+
| mobile | mja_3e3ZwjRCqjj47aCnxpFmUWPy | 'app'@'%' | ENDPOINTS | NULL    |
+--------+------------------------------+-----------+-----------+---------+
SHOW API TOKENS;
+------------+------+------+-----------------------------------------------------------------+-----------+---------------------+---------------------+
| Name       | User | Host | Access                                                          | Databases | Created             | Expires             |
+------------+------+------+-----------------------------------------------------------------+-----------+---------------------+---------------------+
| mobile     | app  | %    | ENDPOINTS                                                       | NULL      | 2026-10-19 02:28:09 | NULL                |
| outil      | dev  | %    | TABLES, SQL                                                     | gestion   | 2026-10-19 02:29:20 | NULL                |
| partenaire | app  | %    | ENDPOINTS (`gestion`.`fiche_client`, `gestion`.`liste_clients`) | NULL      | 2026-10-19 02:29:23 | 2027-01-17 02:29:23 |
+------------+------+------+-----------------------------------------------------------------+-----------+---------------------+---------------------+

Dates en UTC, triées par nom. Le secret n'est jamais montré. Comme pour les jetons MCP (20.4.2), un compte qui a CREATE USER voit tous les jetons, les autres seulement les leurs.

Un jeton ne donne aucun droit : il restreint ceux du compte. Sa famille permise, puis les privilèges SQL du compte (EXECUTE sur l'endpoint, SELECT sur la table ou la colonne…) sont contrôlés tous les deux. Une session ouverte par jeton ne gère jamais de jetons (9034 token management is not allowed at MCP access level ADMIN). Un secret MCP (mjt_…) n'ouvre jamais le point d'accès REST, ni un secret mja_… le point d'accès MCP.

Créer un jeton pour son propre compte est libre ; pour un autre compte, il faut CREATE USER. DROP USER supprime les jetons du compte ; un compte verrouillé voit ses jetons refusés (401).

24.3.2 Ordre des contrôles#

ÉtapeRefus
Famille ouverte sur le serveur (rest_endpoints, rest_tables, rest_sql)404
Client authentifié (jeton valide, non expiré ; mot de passe juste ; compte non verrouillé)401
Famille permise au jeton (ACCESS) ou aux connexions par mot de passe (rest_basic_access)403, erreur 9047
Endpoint permis au jeton (ACCESS ENDPOINTS (…))403, erreur 9047
Droits SQL du compte403 (1044, 1142, 1143, 1370…)

Messages de l'erreur 9047, tels que rendus :

{"error":{"code":9047,"sqlstate":"HY000","message":"REST access to SQL is not allowed for API token 'mobile'"}}
{"error":{"code":9047,"sqlstate":"HY000","message":"REST access to tables is not allowed for API token 'mobile'"}}
{"error":{"code":9047,"sqlstate":"HY000","message":"REST access to endpoints is not allowed for API token 'outil'"}}
{"error":{"code":9047,"sqlstate":"HY000","message":"REST access to endpoint gestion.nouveau_client is not allowed for API token 'partenaire'"}}
{"error":{"code":9047,"sqlstate":"HY000","message":"REST access to SQL is not allowed for password authentication"}}

24.4 Endpoints : des API écrites en SQL#

Un endpoint associe une méthode et un chemin HTTP à un corps SQL (une instruction, ou un bloc BEGIN … END comme une procédure). C'est la façon recommandée d'ouvrir des données : l'application n'a accès qu'aux opérations prévues, avec des paramètres typés, et peut ne détenir que EXECUTE sur ses endpoints.

CREATE [OR REPLACE] [DEFINER = compte] ENDPOINT [IF NOT EXISTS] [base.]nom
    { GET | POST | PUT | PATCH | DELETE } '/chemin/{param}/…'
    [PARAMS (nom type [DEFAULT valeur], …)]
    [RETURNS { ROWS | ONE | NONE | AFFECTED }]
    [COMMENT 'texte'] [SQL SECURITY { DEFINER | INVOKER }]
    AS instruction | BEGIN … END;

DROP ENDPOINT [IF EXISTS] [base.]nom;
SHOW ENDPOINTS [FROM base] [LIKE 'motif'];
SHOW CREATE ENDPOINT [base.]nom;
  • OR REPLACE remplace un endpoint du même nom ; sans lui, un nom déjà pris donne l'erreur 1304 (ENDPOINT fiche_client already exists), sauf avec IF NOT EXISTS (rien n'est fait). OR REPLACE et IF NOT EXISTS ensemble : erreur 1221.
  • Un endpoint ne se crée pas dans le corps d'une routine.
  • DROP ENDPOINT d'un endpoint inconnu : erreur 1305 (ENDPOINT gestion.nope does not exist) ; rien avec IF EXISTS.
  • Deux paramètres de même nom dans PARAMS : erreur 1330 (Duplicate parameter: a).

24.4.1 Exemple complet#

USE gestion;

-- Fiche d'un client : une ligne, 404 s'il n'existe pas
CREATE ENDPOINT fiche_client GET '/clients/{id}' PARAMS (id INT) RETURNS ONE
AS SELECT id, nom, ville FROM clients WHERE id = :id;

-- Liste filtrée, paramètres facultatifs
CREATE ENDPOINT liste_clients GET '/clients' PARAMS (ville VARCHAR(40) DEFAULT NULL, limite INT DEFAULT 50)
AS SELECT id, nom, ville FROM clients WHERE :ville IS NULL OR ville = :ville ORDER BY nom LIMIT :limite;

-- Création : 201 et lignes insérées
CREATE ENDPOINT nouveau_client POST '/clients' PARAMS (nom VARCHAR(80), ville VARCHAR(40)) RETURNS AFFECTED
AS INSERT INTO clients (nom, ville) VALUES (:nom, :ville);

-- Traitement en plusieurs instructions (avec miraj-cli, entre DELIMITER // et DELIMITER ;)
CREATE ENDPOINT solder_facture POST '/factures/{id}/solder' PARAMS (id INT) RETURNS ONE
AS BEGIN
    DECLARE reste DECIMAL(12,2);
    SELECT montant - paye INTO reste FROM factures WHERE id = :id FOR UPDATE;
    UPDATE factures SET paye = montant WHERE id = :id;
    INSERT INTO reglements (facture, montant) VALUES (:id, reste);
    SELECT :id AS facture, reste AS regle;
END;

-- L'application n'a que le droit d'appeler ces endpoints
CREATE USER 'app'@'%' IDENTIFIED BY '…';
GRANT EXECUTE ON ENDPOINT gestion.fiche_client TO 'app'@'%';
GRANT EXECUTE ON ENDPOINT gestion.liste_clients TO 'app'@'%';
GRANT EXECUTE ON ENDPOINT gestion.nouveau_client TO 'app'@'%';
GRANT EXECUTE ON ENDPOINT gestion.solder_facture TO 'app'@'%';
CREATE API TOKEN mobile FOR 'app'@'%';

Appels avec curl (la table clients contient Ali et Karim à Oran, Samia à Alger ; la facture 7 vaut 120,50, rien n'est encore payé) :

curl http://127.0.0.1:7009/clients/42 -H "Authorization: Bearer mja_3e3ZwjRCqjj47aCnxpFmUWPy"
→ 200 {"id":42,"nom":"Ali","ville":"Oran"}

curl "http://127.0.0.1:7009/clients?ville=Oran&limite=10" -H "Authorization: Bearer mja_…"
→ 200 [{"id":42,"nom":"Ali","ville":"Oran"},{"id":12,"nom":"Karim","ville":"Oran"}]

curl -X POST http://127.0.0.1:7009/clients -H "Authorization: Bearer mja_…" \
     -H "Content-Type: application/json" -d '{"nom":"Lina","ville":"Alger"}'
→ 201 {"affected_rows":1,"last_insert_id":43}

curl -X POST http://127.0.0.1:7009/factures/7/solder -H "Authorization: Bearer mja_…"
→ 200 {"facture":7,"regle":120.50}

curl http://127.0.0.1:7009/clients/999 -H "Authorization: Bearer mja_…"
→ 404 {"error":{"code":null,"sqlstate":null,"message":"Not Found: no row"}}

Sans GRANT EXECUTE sur solder_facture, le dernier appel aurait rendu :

→ 403 {"error":{"code":1370,"sqlstate":"42000","message":"execute command denied to user 'app'@'localhost' for routine 'gestion.solder_facture'"}}

24.4.2 Chemin et méthode#

  • Le chemin commence par / et compte au plus 255 caractères ; ses segments sont fixes (lettres, chiffres, -, _, ., ~) ou des paramètres {nom} (lettre ou _, puis lettres, chiffres, _), chacun une fois. Les chemins sous /api et /mcp sont réservés. Erreur 9045 sinon :
ERREUR 9045 (HY000) : Invalid endpoint path 'clients': the path must start with '/'
ERREUR 9045 (HY000) : Invalid endpoint path '/api/x': paths under /api and /mcp are reserved
ERREUR 9045 (HY000) : Invalid endpoint path '/a b': invalid character in segment 'a b'
ERREUR 9045 (HY000) : Invalid endpoint path '/x/{id}/{id}': parameter 'id' appears twice
  • Deux endpoints ne peuvent pas répondre à la même méthode sur un chemin de même forme (GET /clients/{id} et GET /clients/{code}), toutes bases confondues : erreur 9044.
ERREUR 9044 (HY000) : Endpoint path GET /clients/{code} is already used by endpoint gestion.fiche_client
  • Un segment fixe l'emporte sur un paramètre : avec GET '/clients/nouveaux' et GET '/clients/{id}', GET /clients/nouveaux appelle le premier, GET /clients/12 le second.
  • Chemin connu appelé avec une autre méthode : 405 avec l'en-tête Allow (DELETE /clients/42 → 405, Allow: GET). Chemin inconnu : 404 (Not Found).

24.4.3 Paramètres#

  • Déclarés dans PARAMS (nom type [DEFAULT valeur]) ; un paramètre du chemin non déclaré est un VARCHAR(255).
  • Le corps lit un paramètre par :nom, jamais par son nom nu : WHERE id = :id compare la colonne id au paramètre id.
  • Valeurs prises, par nom, dans le chemin ({id}), la requête (?ville=Oran) et, pour POST, PUT et PATCH, les clés d'un corps JSON objet. Celles du chemin et de la requête sont des chaînes converties au type déclaré ; celles du corps gardent leur type JSON (null, nombre, booléen, chaîne ; octets {"hex": "…"} ou {"base64": "…"} ; géométrie {"wkt": "POINT(1 2)"} ou {"wkt": "…", "srid": n}).
  • Erreurs, toutes en 400 :
CasRéponse
Paramètre sans valeur ni DEFAULT{"error":{"code":9046,"sqlstate":"HY000","message":"Endpoint parameter 'ville': missing value"}}
Valeur pour un paramètre inconnu (?zz=1, clé du corps){"error":{"code":9046,"sqlstate":"HY000","message":"Endpoint parameter 'zz': unknown parameter"}}
Même nom donné deux fois (chemin et requête){"error":{"code":null,"sqlstate":null,"message":"Bad Request: parameter 'id' is given twice"}}
Corps qui n'est pas un objet JSON{"error":{"code":null,"sqlstate":null,"message":"Bad Request: the body must be a JSON object"}}
Valeur qui ne se convertit pas (GET /clients/abc){"error":{"code":1366,"sqlstate":"22007","message":"Incorrect integer value: 'abc' for column 'id' at row 1"}}

Exemple de valeurs typées dans le corps :

CREATE ENDPOINT echo_types PUT '/echo' PARAMS (b VARBINARY(8), n DECIMAL(10,2), d DATE, ok BOOLEAN, g GEOMETRY)
RETURNS ONE AS SELECT HEX(:b) AS hex_b, :n AS n, :d AS d, :ok AS ok, ST_AsText(:g) AS g, :g AS geo;
curl -X PUT http://127.0.0.1:7009/echo -u root:… -H "Content-Type: application/json" \
     -d '{"b":{"hex":"00ff"},"n":12.5,"d":"2026-09-25","ok":true,"g":{"wkt":"POINT(1 2)"}}'
→ 200 {"hex_b":"00FF","n":12.50,"d":"2026-09-25","ok":1,"g":"POINT(1 2)","geo":{"wkt": "POINT(1 2)", "srid": 0}}

24.4.4 Réponse (RETURNS)#

RETURNSRéponse
ROWS (défaut)200, tableau d'objets du dernier résultat du corps ([] sans résultat) ; au-delà de rest_max_rows, lignes tronquées et en-tête X-Miraj-Truncated: true
ONE200, objet de la première ligne du dernier résultat ; 404 sans ligne (Not Found: no row)
NONE204 sans corps
AFFECTED{"affected_rows": n, "last_insert_id": id} ; 201 pour POST, 200 sinon

Les valeurs sont écrites en JSON comme par le point d'accès MCP (20.9.1) : décimaux exacts, entiers au-delà de 2^53 en chaîne, BOOLEAN en 1 ou 0, dates et heures en texte SQL, octets en chaîne hexadécimale "0x…", géométries en objet {"wkt": "POINT(1 2)", "srid": 0}.

24.4.5 Droits#

  • Créer un endpoint (ou le remplacer par CREATE OR REPLACE) : CREATE ROUTINE sur la base ; le supprimer : ALTER ROUTINE (1044 sinon). Le créateur ne reçoit aucun droit sur l'endpoint qu'il crée.
  • L'appeler : EXECUTE sur l'endpoint (GRANT EXECUTE ON ENDPOINT base.nom TO compte) ou sur sa base (GRANT EXECUTE ON base.* …, qui ouvre tous ses endpoints) ; sinon 403, erreur 1370.
  • SQL SECURITY DEFINER (défaut) : le corps s'exécute avec les droits de son définisseur — l'appelant n'a besoin d'aucun droit sur les tables. SQL SECURITY INVOKER : avec ceux de l'appelant, colonnes comprises. Pour un compte qui ne lit que id et nom de clients :
CREATE ENDPOINT villes GET '/villes' SQL SECURITY INVOKER AS SELECT DISTINCT ville FROM clients;
→ 403 {"error":{"code":1143,"sqlstate":"42000","message":"SELECT command denied to user 'lecteur'@'localhost' for column 'ville' in table 'clients'"}}
  • SHOW GRANTS écrit les droits par endpoint, et information_schema.ENDPOINT_PRIVILEGES les liste :
SHOW GRANTS FOR 'app'@'%';
+-------------------------------------------------------------------+
| Grants for app@%                                                  |
+-------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `app`@`%`                                   |
| GRANT EXECUTE ON ENDPOINT `gestion`.`fiche_client` TO `app`@`%`   |
| GRANT EXECUTE ON ENDPOINT `gestion`.`liste_clients` TO `app`@`%`  |
| GRANT EXECUTE ON ENDPOINT `gestion`.`nouveau_client` TO `app`@`%` |
| GRANT EXECUTE ON ENDPOINT `gestion`.`solder_facture` TO `app`@`%` |
+-------------------------------------------------------------------+
SELECT * FROM information_schema.ENDPOINT_PRIVILEGES;
+-----------+------------------+-----------------+----------------+----------------+--------------+
| GRANTEE   | ENDPOINT_CATALOG | ENDPOINT_SCHEMA | ENDPOINT_NAME  | PRIVILEGE_TYPE | IS_GRANTABLE |
+-----------+------------------+-----------------+----------------+----------------+--------------+
| 'app'@'%' | def              | gestion         | fiche_client   | EXECUTE        | NO           |
| 'app'@'%' | def              | gestion         | liste_clients  | EXECUTE        | NO           |
| 'app'@'%' | def              | gestion         | nouveau_client | EXECUTE        | NO           |
| 'app'@'%' | def              | gestion         | solder_facture | EXECUTE        | NO           |
+-----------+------------------+-----------------+----------------+----------------+--------------+

24.4.6 Consulter les endpoints#

SHOW ENDPOINTS FROM gestion;
+---------+----------------+--------+-----------------------+----------+---------------------------------------------------------+----------------+---------------+---------------------+---------------------+---------+
| Db      | Name           | Method | Path                  | Returns  | Parameters                                              | Definer        | Security_type | Modified            | Created             | Comment |
+---------+----------------+--------+-----------------------+----------+---------------------------------------------------------+----------------+---------------+---------------------+---------------------+---------+
| gestion | fiche_client   | GET    | /clients/{id}         | ONE      | id int(11)                                              | root@localhost | DEFINER       | 2026-10-19 03:28:03 | 2026-10-19 03:28:03 |         |
| gestion | liste_clients  | GET    | /clients              | ROWS     | ville varchar(40) (optional), limite int(11) (optional) | root@localhost | DEFINER       | 2026-10-19 03:28:03 | 2026-10-19 03:28:03 |         |
| gestion | nouveau_client | POST   | /clients              | AFFECTED | nom varchar(80), ville varchar(40)                      | root@localhost | DEFINER       | 2026-10-19 03:28:03 | 2026-10-19 03:28:03 |         |
| gestion | solder_facture | POST   | /factures/{id}/solder | ONE      | id int(11)                                              | root@localhost | DEFINER       | 2026-10-19 03:28:09 | 2026-10-19 03:28:09 |         |
+---------+----------------+--------+-----------------------+----------+---------------------------------------------------------+----------------+---------------+---------------------+---------------------+---------+

Parameters marque (optional) un paramètre qui a une valeur par défaut. SHOW CREATE ENDPOINT rend les colonnes Endpoint, sql_mode, Create Endpoint, character_set_client, collation_connection et Database Collation ; le texte de Create Endpoint reprend la définition avec son définisseur et sa clause RETURNS :

CREATE DEFINER=`root`@`localhost` ENDPOINT `liste_clients` GET '/clients' PARAMS (ville VARCHAR(40) DEFAULT NULL, limite INT DEFAULT 50) RETURNS ROWS
AS SELECT id, nom, ville FROM clients WHERE :ville IS NULL OR ville = :ville ORDER BY nom LIMIT :limite

information_schema.ENDPOINTS en donne une ligne par endpoint, avec les colonnes ENDPOINT_CATALOG, ENDPOINT_SCHEMA, ENDPOINT_NAME, METHOD, PATH, RETURNS, PARAMETERS, ENDPOINT_DEFINITION (le corps), SECURITY_TYPE, DEFINER, CREATED, LAST_ALTERED, SQL_MODE et ENDPOINT_COMMENT.

Les endpoints ne sont ni des procédures ni des fonctions : CALL fiche_client(42) donne l'erreur 1305 (PROCEDURE gestion.fiche_client does not exist) et information_schema.ROUTINES ne les montre pas. Ils sont sauvegardés avec la base (BACKUP, miraj-dump) et répliqués dans un cluster comme les procédures.

24.5 Tables : /api/{base}/{table}#

Famille ouverte par rest_tables = ON et le jeton (ACCESS TABLES). Chaque requête devient une instruction SQL paramétrée (identifiants cités, valeurs liées) sous les droits du compte.

RequêteEffet
GET /api/{base}/{table}Lignes (100 par défaut, au plus rest_max_rows) ; 200 et un tableau d'objets
GET /api/{base}/{table}/{clé}La ligne de clé primaire clé (404 sans elle) ; clé de plusieurs colonnes : valeurs séparées par des virgules, dans l'ordre des colonnes de la table
POST /api/{base}/{table}Insère un objet JSON, ou un tableau non vide d'objets aux mêmes clés ; 201 et {"affected_rows", "last_insert_id"}
PATCH /api/{base}/{table}/{clé}Modifie les colonnes du corps ; 200 et {"affected_rows", "last_insert_id"}
PATCH /api/{base}/{table}?filtresModifie les lignes filtrées (un filtre au moins) ; 200
DELETE /api/{base}/{table}/{clé}Supprime la ligne ; 204, 404 sans elle
DELETE /api/{base}/{table}?filtresSupprime les lignes filtrées (un filtre au moins) ; 200 et {"affected_rows", "last_insert_id"}

Paramètres de lecture : select=a,b ; order=a.desc,b (.asc facultatif) ; limit ; offset ; filtres colonne[.op]=valeur, op parmi eq (défaut), ne, gt, ge, lt, le, like, in (valeurs séparées par des virgules), null (true ou false). Plusieurs filtres se combinent par AND. Exemples, sur la table clients de 24.4.1 :

GET /api/gestion/clients?ville=Oran&nom.like=A%25&order=nom&limit=20
→ 200 [{"id":42,"nom":"Ali","ville":"Oran"}]

GET /api/gestion/clients?select=id,nom&order=id.desc&limit=2&offset=1
→ 200 [{"id":42,"nom":"Ali"},{"id":12,"nom":"Karim"}]

GET /api/gestion/clients?id.in=7,12&ville.null=false
→ 200 [{"id":7,"nom":"Samia","ville":"Alger"},{"id":12,"nom":"Karim","ville":"Oran"}]

POST /api/gestion/clients   {"nom":"Nadia","ville":"Blida"}
→ 201 {"affected_rows":1,"last_insert_id":44}

POST /api/gestion/clients   [{"nom":"Omar","ville":"Oran"},{"nom":"Rym","ville":null}]
→ 201 {"affected_rows":2,"last_insert_id":45}

PATCH /api/gestion/clients/44   {"ville":"Tlemcen"}
→ 200 {"affected_rows":1,"last_insert_id":0}

DELETE /api/gestion/clients/46
→ 204

Sans select, la réponse contient les colonnes que le compte peut lire : toutes s'il a SELECT sur la table, sinon seulement celles qui lui sont accordées. Demander, filtrer, trier ou écrire une colonne refusée : 403, erreur 1143. Pour un compte qui n'a que SELECT (id, nom) sur clients :

GET /api/gestion/clients?limit=2
→ 200 [{"id":42,"nom":"Ali"},{"id":7,"nom":"Samia"}]

GET /api/gestion/clients?ville=Oran
→ 403 {"error":{"code":1143,"sqlstate":"42000","message":"SELECT command denied to user 'lecteur'@'localhost' for column 'ville' in table 'clients'"}}

Refus propres à cette famille :

CasStatut et message
PATCH ou DELETE sans clé ni filtre400 Bad Request: PATCH needs a key or at least one filter (DELETE needs …)
Opérateur inconnu (nom.zz=1)400 Bad Request: unknown filter operator 'zz' (eq, ne, gt, ge, lt, le, like, in, null)
limit ou offset qui n'est pas un entier positif ou nul400 Bad Request: 'limit' must be a non-negative integer
Colonne inconnue dans un filtre400, erreur 1054
Clé de plusieurs colonnes mal découpée400 Bad Request: the key has 2 column(s), separated by commas
Table sans clé primaire, appelée par clé404 Not Found: unknown table, or a table without primary key
Chemin d'une autre forme (/api/gestion/clients/1/2)404 Not Found: /api/{database}/{table}[/{key}] expected
POST sur une clé405 Method Not Allowed: POST to /api/{database}/{table}, Allow: GET, PATCH, DELETE
Autre méthode (PUT)405, Allow: GET, POST, PATCH, DELETE (ou GET, PATCH, DELETE avec une clé)
Doublon de clé409, erreur 1062

24.6 SQL libre : POST /api/sql#

Famille ouverte par rest_sql = ON et le jeton (ACCESS SQL).

{ "sql": "SELECT id, nom FROM gestion.clients WHERE ville = :ville", "params": { "ville": "Oran" }, "max_rows": 100 }

params : tableau pour les ?, objet pour les :nom ; valeurs comme les paramètres d'execute_sql du point d'accès MCP (20.9.2). max_rows (entier positif) est ramené à rest_max_rows. Réponse :

{"results":[{"columns":[{"name":"id","type":"int(11)"},{"name":"nom","type":"varchar(80)"}],"rows":[[42,"Ali"],[12,"Karim"]],"row_count":2,"truncated":false,"affected_rows":null,"last_insert_id":null}],"warnings":[]}

Plusieurs instructions séparées par ; donnent plusieurs résultats ; une instruction qui écrit donne un résultat sans colonnes avec affected_rows et last_insert_id :

{"sql": "SELECT id, nom FROM gestion.clients WHERE id > ? ORDER BY id; UPDATE gestion.clients SET ville = ville WHERE id = 7", "params": [10]}
{"results":[{"columns":[{"name":"id","type":"int(11)"},{"name":"nom","type":"varchar(80)"}],"rows":[[12,"Karim"],[42,"Ali"],[43,"Lina"]],"row_count":3,"truncated":false,"affected_rows":null,"last_insert_id":null},{"columns":[],"rows":[],"row_count":0,"truncated":false,"affected_rows":1,"last_insert_id":0}],"warnings":[]}

En erreur, la réponse porte error et les résultats des instructions déjà exécutées, avec le statut de l'erreur (24.8) :

{"error":{"code":1142,"sqlstate":"42000","message":"SELECT command denied to user 'dev'@'localhost' for table `ventes`.`article`"},"results":[{"columns":[{"name":"un","type":"bigint"}],"rows":[[1]],"row_count":1,"truncated":false,"affected_rows":null,"last_insert_id":null}],"warnings":[]}

Chaque requête est une session neuve : une transaction ne s'étend pas d'une requête à l'autre (utilisez un bloc d'instructions, ou un endpoint). Refus propres à ce chemin : sql absent (400 Bad Request: 'sql' (string) is required), params d'un autre type (400), méthode autre que POST (405 Method Not Allowed: use POST).

24.7 Autres chemins#

CheminRôle
GET /api/health{"status":"ok","version":"1.0.0","edition":"Entreprise"}, sans authentification (sonde de disponibilité) ; autre méthode : 405
OPTIONS …204 avec Allow: GET, POST, PUT, PATCH, DELETE, OPTIONS ; pour une origine de rest_cors_origins, en plus Access-Control-Allow-Methods, Access-Control-Allow-Headers: Authorization, Content-Type et Access-Control-Max-Age: 600

Une réponse à une requête dont l'en-tête Origin est admis par rest_cors_origins porte Access-Control-Allow-Origin (l'origine, ou *) et Vary: Origin. Toute réponse porte Cache-Control: no-store.

24.8 Erreurs#

Corps : {"error": {"code": 1143, "sqlstate": "42000", "message": "…"}} (code et sqlstate à null pour une erreur HTTP sans code SQL).

StatutCas
400JSON invalide (Bad Request: invalid JSON body (…)), paramètre manquant ou inconnu (9046), SQL invalide (1064), colonne inconnue (1054), valeur hors type (1366…), autre erreur de SQLSTATE 22 ou 42
401Authentification absente ou refusée ; Basic en HTTP clair hors boucle locale (24.3)
403Adresse bloquée, famille ou endpoint non permis (9047), droits SQL (1044, 1142, 1143, 1370), gestion de jetons par jeton (9034)
404Famille fermée sur le serveur, chemin inconnu, ligne absente (ONE, clé), base ou table inconnue (1049, 1146)
405Méthode non servie sur ce chemin (Allow)
409Doublon de clé (1062), clé étrangère (1451, 1452), conflit de verrou (1205, 1213), autre erreur de SQLSTATE 23
415Corps envoyé avec un Content-Type autre que application/json (Unsupported Media Type: application/json expected)
500Autre erreur du moteur
503max_connections atteint (1040)
504Délai de l'instruction dépassé (1969) ou instruction interrompue (1317)

24.9 Bonnes pratiques#

  • Préférez les endpoints : l'application ne détient que EXECUTE sur ses API, le SQL reste sur le serveur, et un changement de schéma ne touche que le corps des endpoints.
  • Un compte par application, un jeton par déploiement (ACCESS ENDPOINTS (…) au plus juste, EXPIRE), révoqué par DROP API TOKEN.
  • Gardez rest_tables et rest_sql fermés en production, ou réservés à des jetons d'outils internes limités à une base.
  • Hors de la boucle locale, le point d'accès exige TLS ; placez-le derrière un pare-feu et limitez rest_cors_origins aux origines de vos applications.

Voir aussi#