Service & API ============= This section describes the deployed translation service: the Docker pipeline, the service pipeline schema, and the public API contract. Service pipeline ---------------- The Docker deployment is the reproducible runtime for the translation service. It is defined in ``demo_n_api/docker-compose.yml`` and runs three cooperating services: * ``app`` builds ``demo_n_api/app/Dockerfile`` and runs the Flask application on port ``8080`` inside the container. * ``db`` runs MySQL 5.7, creates the ``ans`` database from ``demo_n_api/db/init.sql``, and stores user accounts, expert suggestions, and terminology rows. * ``caddy`` terminates HTTPS, validates client certificates for API routes, and reverse-proxies valid requests to ``app:8080``. .. figure:: images/service_pipeline.png :alt: Docker translation service pipeline Pipeline schema ^^^^^^^^^^^^^^^ The diagram presents the runtime decision flow for a translation request: #. An English term enters through the web app or API. #. The caller selects options such as multiple translations, scoring, LLM refinement, and postprocessing rules. #. The selected translation model produces the first machine translation. In the current service this is the NLLB model checkpoint configured by ``NLLB_MODEL_EN_FR`` or by the request-level ``model_id``. #. If LLM refinement is disabled, the model output becomes the final translation after scoring and terminology highlighting. #. If LLM refinement is enabled, the source term, initial translation, and refinement instruction are sent to the configured LLM provider. #. The LLM returns a suggested final translation and an explanation. The service includes this information in the response instead of silently replacing the model output. The grey Retrieval-Augmented Generation area shows the optional LLM/RAG-style step. It receives structured context from the API request and the initial NLLB translation. The "Focus on models" box shows the core model-serving part of the pipeline: the web service remains stable while the deployed checkpoint can be changed through configuration. Runtime inputs ^^^^^^^^^^^^^^ The application container mounts the runtime inputs needed for reproduction: * model files under ``/app/models``; * environment configuration from ``demo_n_api/.env``; * database initialization files from ``demo_n_api/db``; * optional credential files from ``demo_n_api/credentials``; * mTLS certificates for Caddy from ``demo_n_api/mtls``. Startup sequence ^^^^^^^^^^^^^^^^ #. Docker Compose builds the Flask image from ``demo_n_api/app/Dockerfile``. #. The image installs ``demo_n_api/app/requirements.txt`` and starts ``flask run --host=0.0.0.0 --port=8080``. #. MySQL starts first and executes ``demo_n_api/db/init.sql``. #. The Flask app loads environment values from ``demo_n_api/.env`` and initializes the global ``Translator`` object. #. The translator loads the default NLLB model path configured by ``NLLB_MODEL_EN_FR`` and ``NLLB_MODEL_FR_EN``. #. On the first terminology lookup, the app ensures that the configured terminology table exists and can load rows from ``TERMINOLOGY_CSV_PATH``. #. Caddy exposes the public HTTPS endpoint, adds mTLS identity headers for requests with a trusted client certificate, and forwards accepted API calls to the Flask app. Translation request flow ^^^^^^^^^^^^^^^^^^^^^^^^ The main API path is ``/translate``. A request can provide ``terms`` as JSON or upload a text file where each non-empty line is one term. The processing order is: #. Authenticate the caller using ``api_key``, ``mtls``, or ``api_key_or_mtls``. #. Normalize request options: ``direction``, ``model_id``, ``multiple``, ``similarityMetric``, ``llm``, ``llmProvider``, ``llmModel``, and ``returnFile``. #. Parse terms from JSON, form data, query parameters, or uploaded file lines. #. Reject terms that exceed ``MAX_TERM_WORDS``. #. Run Presidio sensitive-data detection. Sensitive batches return HTTP 422 and are not translated. #. Translate each accepted term with NLLB using the selected direction and model. #. Compute token-level confidence and expose it as ``trust_score`` and ``mean_conf_prob``. #. Find terminology matches in the generated translation and add ``highlights``. #. Adjust the trust score with terminology coverage when matches are available. #. If ``multiple=true``, generate up to ten candidate translations. #. If ``similarityMetric=true``, compute semantic similarity between the source term and the generated translation. #. If ``llm=true``, call the configured LLM provider for suggested refinement and explanation. #. Return JSON with ``count`` and ``items``, or a downloadable text file when ``returnFile=true``. Reproducing the service ^^^^^^^^^^^^^^^^^^^^^^^ For local reproduction, publish the Flask app port in the ``app`` service: .. code-block:: yaml ports: - "8080:8080" Start the app and database: .. code-block:: powershell cd demo_n_api docker compose up --build app db Then test a local request: .. code-block:: powershell curl.exe -X POST http://localhost:8080/translate ` -H "Content-Type: application/json" ` -H "X-API-Key: replace-with-local-development-key" ` -d "{\"terms\":[\"heart failure\"],\"direction\":\"en_fr\"}" For a public HTTPS/mTLS deployment, keep Caddy enabled, provide ``demo_n_api/mtls/ca.crt``, set a real ``APP_HOST`` and ``ACME_EMAIL``, and set ``API_AUTH_MODE=mtls`` or ``API_AUTH_MODE=api_key_or_mtls``. Translate endpoint ------------------ Base endpoints: * Local development: ``http://localhost:8080/translate`` * Public HTTPS deployment: ``https://smt-traducteur.esante.gouv.fr/translate`` Request fields: * ``terms``: list of terms to translate. For a single term, still send a one-item list. * ``direction``: translation direction. Supported values are ``en_fr`` and ``fr_en``. * ``model_id``: optional model checkpoint path or Hugging Face identifier. * ``multiple``: when ``true``, return candidate translations. * ``similarityMetric``: when ``true``, include semantic similarity fields. * ``llm``: when ``true``, request LLM-based refinement and explanation. * ``llmProvider``: provider name when ``llm`` is enabled, for example ``gemini`` or ``openai``. * ``llmModel``: optional provider-specific model override. * ``returnFile``: when file input is used, return output as a file payload. Example call with mTLS ^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: bash curl -v 'https://smt-traducteur.esante.gouv.fr/translate' \ --header 'Content-Type: application/json' \ --data-raw '{ "terms": ["heart failure", "respiratory distress"], "llm": false, "direction": "en_fr", "model_id": "/app/models/checkpoint-16539", "similarityMetric": true, "multiple": true, "returnFile": false, "llmProvider": "", "llmModel": "" }' \ --cert ./mTLS/client.crt \ --key ./mTLS/client.key \ --cacert ./mTLS/ca.crt The JSON responses shown below (through the end of this page) were captured by running the corresponding calls against a live instance of this service — they are real output, not illustrative samples. Each call is shown in the public HTTPS/mTLS form documented above; verification was done against the Flask app with the same authenticated identity Caddy forwards for a trusted mTLS connection, so the response bodies are identical to what a real mTLS client receives. Minimal single-term example ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Request: .. code-block:: bash curl -s 'https://smt-traducteur.esante.gouv.fr/translate' \ --header 'Content-Type: application/json' \ --data-raw '{ "terms": ["heart failure"], "direction": "en_fr" }' \ --cert ./mTLS/client.crt \ --key ./mTLS/client.key \ --cacert ./mTLS/ca.crt Output (``HTTP 200``): .. code-block:: json { "count": 1, "items": [ { "input": "heart failure", "index": 0, "output": "insuffisance cardiaque", "direction": "en_fr", "model_id": "/app/models/checkpoint-16539", "trust_score": 1.0, "mean_conf_prob": 0.995455, "highlights": [ { "start": 0, "end": 22, "translation": "insuffisance cardiaque", "trustScore": 0.993407, "matches": [ { "source": "https://smt.esante.gouv.fr/explorer-les-concepts/terminologie-cim-10/?concept=http%3A%2F%2Fdata.esante.gouv.fr%2Fatih%2Fcim10%2FI50&lang=fr&treeViewType=simple", "terminology": "CIM 10 PMSI", "conceptUri": "http://data.esante.gouv.fr/atih/cim10/I50", "id": "I50", "theme": "I30-I52 Autres formes de cardiopathies", "nearTerms": [] }, { "source": "https://smt.esante.gouv.fr/explorer-les-concepts/terminologie-cim11-mms/?concept=http%3A%2F%2Fid.who.int%2Ficd%2Frelease%2F11%2Fmms%2F1458683894&lang=fr&treeViewType=simple", "terminology": "CIM-11 MMS", "conceptUri": "http://id.who.int/icd/release/11/mms/1458683894", "id": "BlockL1-BD1", "theme": "block", "nearTerms": [] }, { "source": "https://smt.esante.gouv.fr/explorer-les-concepts/terminologie-ncit/?concept=http%3A%2F%2Fncicb.nci.nih.gov%2Fxml%2Fowl%2FEVS%2FThesaurus.owl%23C50577&lang=fr&treeViewType=simple", "terminology": "NCIt", "conceptUri": "http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl#C50577", "id": "C50577", "theme": "C35741 trouble non-néoplasique cardiaque", "nearTerms": [] }, { "source": "https://smt.esante.gouv.fr/explorer-les-concepts/terminologie-snomed-ct-fr/?concept=http%3A%2F%2Fsnomed.info%2Fid%2F84114007&lang=fr&treeViewType=simple", "terminology": "SNOMED CT", "conceptUri": "http://snomed.info/id/84114007", "id": "84114007", "theme": "", "nearTerms": [] } ] } ] } ] } Multiple terms in one request ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Send several terms in the same ``terms`` array; each is scored and highlighted independently and returned in the same order in ``items``: .. code-block:: bash curl -s 'https://smt-traducteur.esante.gouv.fr/translate' \ --header 'Content-Type: application/json' \ --data-raw '{ "terms": ["heart failure", "asthma", "diabetes mellitus"], "direction": "en_fr" }' \ --cert ./mTLS/client.crt \ --key ./mTLS/client.key \ --cacert ./mTLS/ca.crt Output (``HTTP 200``, abridged — each item has the same shape as above): .. code-block:: json { "count": 3, "items": [ {"input": "heart failure", "index": 0, "output": "insuffisance cardiaque", "trust_score": 1.0}, {"input": "asthma", "index": 1, "output": "asthme", "trust_score": 1.0}, {"input": "diabetes mellitus", "index": 2, "output": "diabète sucré", "trust_score": 1.0} ] } Candidate translations (``multiple=true``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: bash curl -s 'https://smt-traducteur.esante.gouv.fr/translate' \ --header 'Content-Type: application/json' \ --data-raw '{ "terms": ["diabetes mellitus"], "direction": "en_fr", "multiple": true }' \ --cert ./mTLS/client.crt \ --key ./mTLS/client.key \ --cacert ./mTLS/ca.crt Output (``HTTP 200``) — each item gains a ``top_translations`` list of up to ten scored candidates, ordered best first: .. code-block:: json { "count": 1, "items": [ { "input": "diabetes mellitus", "output": "diabète sucré", "trust_score": 1.0, "mean_conf_prob": 0.993861, "top_translations": [ {"translation": "diabète sucré", "score": 1.0}, {"translation": "sucré diabétique", "score": 0.699226}, {"translation": "diabète sucrée", "score": 0.698371}, {"translation": "sucre sucré", "score": 0.64033}, {"translation": "diabetes mellitus", "score": 0.594149}, {"translation": "diabètes sucrés", "score": 0.579666}, {"translation": "diabétose sucrée", "score": 0.563762}, {"translation": "type diabétique", "score": 0.548722} ] } ] } Semantic similarity (``similarityMetric=true``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: bash curl -s 'https://smt-traducteur.esante.gouv.fr/translate' \ --header 'Content-Type: application/json' \ --data-raw '{ "terms": ["heart failure"], "direction": "en_fr", "similarityMetric": true }' \ --cert ./mTLS/client.crt \ --key ./mTLS/client.key \ --cacert ./mTLS/ca.crt Output (``HTTP 200``) — adds ``semantic_similarity_score`` (cross-lingual similarity between the source term and the translation) and ``semantic_similarity_model``: .. code-block:: json { "count": 1, "items": [ { "input": "heart failure", "output": "insuffisance cardiaque", "trust_score": 1.0, "semantic_similarity_score": 0.8298, "semantic_similarity_model": "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2" } ] } LLM refinement (``llm=true``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: bash curl -s 'https://smt-traducteur.esante.gouv.fr/translate' \ --header 'Content-Type: application/json' \ --data-raw '{ "terms": ["heart failure"], "direction": "en_fr", "llm": true, "llmProvider": "gemini" }' \ --cert ./mTLS/client.crt \ --key ./mTLS/client.key \ --cacert ./mTLS/ca.crt Output (``HTTP 200``) — adds ``llm_provider`` and an ``llm`` object with the provider's suggested translation and its reasoning. The model output itself (``output``) is left unchanged; the LLM suggestion is additive: .. code-block:: json { "count": 1, "items": [ { "input": "heart failure", "output": "insuffisance cardiaque", "trust_score": 1.0, "llm_provider": "gemini", "llm": { "suggested": "insuffisance cardiaque", "reasoning": "The current translation 'insuffisance cardiaque' is the standard and most accurate medical term for 'heart failure' in French. It is widely recognized and used in medical contexts." } } ] } Reverse direction (``fr_en``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: bash curl -s 'https://smt-traducteur.esante.gouv.fr/translate' \ --header 'Content-Type: application/json' \ --data-raw '{ "terms": ["insuffisance cardiaque"], "direction": "fr_en" }' \ --cert ./mTLS/client.crt \ --key ./mTLS/client.key \ --cacert ./mTLS/ca.crt Output (``HTTP 200``): .. code-block:: json { "count": 1, "items": [ { "input": "insuffisance cardiaque", "direction": "fr_en", "output": "heart failure", "trust_score": 0.827668, "mean_conf_prob": 0.908154, "highlights": [] } ] } File upload ^^^^^^^^^^^ Send a plain-text file (one term per line) as multipart form data in the ``file`` field, alongside ``direction`` as a form field. Without ``returnFile``, the response is the same JSON batch shape as the JSON calls above, one item per line: .. code-block:: bash printf 'heart failure\nasthma\ndiabetes mellitus\n' > terms.txt curl -s 'https://smt-traducteur.esante.gouv.fr/translate' \ -F "file=@terms.txt" \ -F "direction=en_fr" \ --cert ./mTLS/client.crt \ --key ./mTLS/client.key \ --cacert ./mTLS/ca.crt Output (``HTTP 200``, abridged to the same three items as the multi-term example above): .. code-block:: json {"count": 3, "items": ["...same item shape as the JSON examples above..."]} Add ``-F "returnFile=true"`` to receive a downloadable results file instead of JSON. For a ``.txt`` upload the response is ``Content-Type: text/plain`` with one translation per line, in the same order as the input file: .. code-block:: bash curl -s 'https://smt-traducteur.esante.gouv.fr/translate' \ -F "file=@terms.txt" \ -F "direction=en_fr" \ -F "returnFile=true" \ -OJ \ --cert ./mTLS/client.crt \ --key ./mTLS/client.key \ --cacert ./mTLS/ca.crt Response headers and body actually returned for the three-line file above: .. code-block:: text HTTP/1.1 200 OK Content-Disposition: attachment; filename=terms_translated.txt Content-Type: text/plain; charset=utf-8 insuffisance cardiaque asthme diabète sucré nearTerms resolved to a code ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Each match's ``nearTerms`` covers the synonyms of *that matched concept* (its own ``french_synonyms``), always rendered as ``"code label"`` rather than bare text. Each synonym resolves in one of two ways: * If the synonym text is itself a registered concept's own French label somewhere in that terminology, it is shown as that concept's own ``"code label"`` — which may be a *different* code than the match it came from: .. code-block:: bash curl -s 'https://smt-traducteur.esante.gouv.fr/translate' \ --header 'Content-Type: application/json' \ --data-raw '{"terms": ["effects of thirst"], "direction": "en_fr"}' \ --cert ./mTLS/client.crt --key ./mTLS/client.key --cacert ./mTLS/ca.crt Output (``HTTP 200``): the CIM-10 match ``T73.1`` and the CIM-11 MMS match ``NF07.1`` both have a synonym meaning "water deprivation", which is itself a registered code in each terminology (``X54`` and ``XE3WS`` respectively) — so ``nearTerms`` resolves each to that other code: .. code-block:: json { "count": 1, "items": [ { "input": "effects of thirst", "output": "effets de la soif", "highlights": [ { "translation": "effets de la soif", "matches": [ { "id": "T73.1", "terminology": "CIM 10 PMSI", "theme": "T73 Effets d'autres privations", "nearTerms": ["X54 Privation d'eau"] }, { "id": "NF07.1", "terminology": "CIM-11 MMS", "theme": "", "nearTerms": ["XE3WS Privation d'eau"] } ] } ] } ] } * Otherwise — the common case, since most synonyms are free text with no code of their own anywhere in the terminology — it falls back to the code of the concept the synonym belongs to: .. code-block:: bash curl -s 'https://smt-traducteur.esante.gouv.fr/translate' \ --header 'Content-Type: application/json' \ --data-raw '{"terms": ["breast"], "direction": "en_fr"}' \ --cert ./mTLS/client.crt --key ./mTLS/client.key --cacert ./mTLS/ca.crt Output (``HTTP 200``, abridged): NCIt's ``C12971`` ("Sein") has the synonym "Seins", which is not itself a registered NCIt concept, so it falls back to ``C12971``'s own code: .. code-block:: json { "count": 1, "items": [ { "input": "breast", "output": "sein", "highlights": [ { "translation": "sein", "matches": [ { "id": "C12971", "terminology": "NCIt", "theme": "C13018 Organe", "nearTerms": ["C12971 Seins"] } ] } ] } ] } Response shape (JSON) ^^^^^^^^^^^^^^^^^^^^^ The endpoint returns a batch response with one item per accepted term. When the translation matches entries of the terminology database, each matched span is returned in ``highlights`` together with its terminology concepts. This is the same shape shown in the examples above, repeated here as the full reference: .. code-block:: json { "count": 1, "items": [ { "input": "heart failure", "index": 0, "output": "insuffisance cardiaque", "direction": "en_fr", "model_id": "/app/models/checkpoint-16539", "trust_score": 1.0, "mean_conf_prob": 0.995455, "highlights": [ { "start": 0, "end": 22, "translation": "insuffisance cardiaque", "trustScore": 0.993407, "matches": [ { "source": "https://smt.esante.gouv.fr/explorer-les-concepts/terminologie-cim-10/?concept=http%3A%2F%2Fdata.esante.gouv.fr%2Fatih%2Fcim10%2FI50&lang=fr&treeViewType=simple", "terminology": "CIM 10 PMSI", "conceptUri": "http://data.esante.gouv.fr/atih/cim10/I50", "id": "I50", "theme": "I30-I52 Autres formes de cardiopathies", "nearTerms": [] }, { "source": "https://smt.esante.gouv.fr/explorer-les-concepts/terminologie-snomed-ct-fr/?concept=http%3A%2F%2Fsnomed.info%2Fid%2F84114007&lang=fr&treeViewType=simple", "terminology": "SNOMED CT", "conceptUri": "http://snomed.info/id/84114007", "id": "84114007", "theme": "", "nearTerms": [] } ] } ] } ] } Item fields: * ``input`` / ``index``: the submitted term and its position in the request. * ``output``: the translation. * ``trust_score``: combined confidence and terminology-coverage score (0-1). * ``mean_conf_prob``: mean token confidence reported by the model. * ``error``: only present when this term failed; the other terms of the batch are still translated and returned. Highlight fields (one entry per matched span of the translation): * ``start`` / ``end`` / ``translation``: character span and matched text. * ``trustScore``: confidence for this span. * ``matches``: terminology concepts matching the span, with: * ``source``: link to the concept page in the SMT concept explorer. * ``terminology``: terminology name (for example ``CIM 10 PMSI``, ``SNOMED CT``, ``NCIt``, ``LOINC``). * ``conceptUri``: URI of the concept. * ``id``: concept code in its terminology. * ``theme``: parent category, as ``code label`` when the label is known (for example ``I30-I52 Autres formes de cardiopathies``), the bare code when no label resolves, or ``""`` when the terminology has no category data at all (always empty for SNOMED and ICF). * ``nearTerms``: French synonyms of this concept, each resolved to ``"code label"`` (see `nearTerms resolved to a code`_ above) — never bare text. Empty when the concept has no synonym data at all. Most terminologies have sparse or no synonym data — SNOMED, LOINC, ATC, EMDN, ORDO, ICF and STANDARD_TERMS rarely populate this field. ``.xlsx`` workbooks (one term per row, first non-empty cell) are also accepted in the ``file`` field. With ``returnFile=true`` an ``.xlsx`` upload returns a ``.xlsx`` results file with the columns ``term``, ``translation``, ``trust score``, ``terminology matches``, ``theme``, ``near terms``, ``SMT links`` and ``error`` — terms that fail (including sensitive-data terms) keep their row with the error message while the rest of the file is translated. Status codes ^^^^^^^^^^^^ * ``200``: translation succeeded. * ``400``: invalid request, missing terms or file, unsupported upload format, empty uploaded file, or term length above ``MAX_TERM_WORDS``. * ``401``: missing or invalid API key or mTLS identity. * ``422``: sensitive data detected by Presidio in a JSON batch; blocked items are reported in ``violations``. File requests with ``returnFile=true`` return the results file with per-term errors instead. Real responses for each error case: **401 — no API key / no mTLS identity presented:** .. code-block:: bash curl -s 'https://smt-traducteur.esante.gouv.fr/translate' \ --header 'Content-Type: application/json' \ --data-raw '{"terms": ["heart failure"], "direction": "en_fr"}' .. code-block:: json {"error": "Unauthorized. Provide a valid mTLS client certificate."} ``HTTP 401`` **400 — unsupported direction value:** .. code-block:: bash curl -s 'https://smt-traducteur.esante.gouv.fr/translate' \ --header 'Content-Type: application/json' \ --data-raw '{"terms": ["heart failure"], "direction": "de_en"}' \ --cert ./mTLS/client.crt --key ./mTLS/client.key --cacert ./mTLS/ca.crt .. code-block:: json {"error": "Invalid direction. Supported values: en_fr, fr_en."} ``HTTP 400`` **422 — sensitive data detected by Presidio:** .. code-block:: bash curl -s 'https://smt-traducteur.esante.gouv.fr/translate' \ --header 'Content-Type: application/json' \ --data-raw '{ "terms": ["My name is John Smith and my email is john.smith@example.com"], "direction": "en_fr" }' \ --cert ./mTLS/client.crt --key ./mTLS/client.key --cacert ./mTLS/ca.crt .. code-block:: json { "count": 1, "error": "Unprocessed Entity: term includes sensitive data", "message": "term includes sensitive data", "items": [ { "index": 0, "input": "My name is John Smith and my email is john.smith@example.com", "output": "Unprocessed Entity: term includes sensitive data", "message": "term includes sensitive data", "entities": ["EMAIL_ADDRESS", "PERSON"] } ], "violations": [ { "index": 0, "term": "My name is John Smith and my email is john.smith@example.com", "entities": ["EMAIL_ADDRESS", "PERSON"] } ] } ``HTTP 422`` Health endpoint ^^^^^^^^^^^^^^^ ``GET /health`` reports service and database status without authentication. .. code-block:: bash curl -s 'https://smt-traducteur.esante.gouv.fr/health' Output when healthy (``HTTP 200``): .. code-block:: json { "app": "ok", "database": "ok", "database_bootstrapped": true, "terminology_rows": 493055 } When the database is unreachable, the endpoint returns ``HTTP 503`` with a ``database_error`` message instead (illustrative example, based on an actual outage previously seen on this deployment when the ``ans`` database was missing): .. code-block:: json { "app": "ok", "database_bootstrapped": false, "database": "error", "database_error": "(1049, \"Unknown database 'ans'\")" } ``HTTP 503`` Suggestion endpoints -------------------- The service also exposes endpoints used by expert review workflows. Both endpoints use the same authentication layer as ``/translate`` (``api_key``, ``mtls``, or ``api_key_or_mtls`` depending on ``API_AUTH_MODE``), and both also accept an authenticated browser session, in which case ``user`` defaults to the logged-in username instead of ``"api_key_user"``. ``POST /store`` ^^^^^^^^^^^^^^^^ Stores an expert-provided suggestion for a source term and its machine translation. Required JSON fields: ``text`` (source term), ``trans`` (the machine translation being reviewed), ``sugg`` (the expert's suggested translation). Optional: ``user`` (defaults to the authenticated identity). .. code-block:: bash curl -s 'https://smt-traducteur.esante.gouv.fr/store' \ --header 'Content-Type: application/json' \ --data-raw '{ "text": "heart failure", "trans": "insuffisance cardiaque", "sugg": "défaillance cardiaque" }' \ --cert ./mTLS/client.crt --key ./mTLS/client.key --cacert ./mTLS/ca.crt Output (``HTTP 200``, plain text body): .. code-block:: text Done!! ``POST /get_suggestion`` ^^^^^^^^^^^^^^^^^^^^^^^^^ Returns the most recently stored suggestion for a source term (by insertion order, most recent first), or an empty string when none was ever stored. Required JSON field: ``text``. .. code-block:: bash curl -s 'https://smt-traducteur.esante.gouv.fr/get_suggestion' \ --header 'Content-Type: application/json' \ --data-raw '{"text": "heart failure"}' \ --cert ./mTLS/client.crt --key ./mTLS/client.key --cacert ./mTLS/ca.crt Output when a suggestion exists (``HTTP 200``): .. code-block:: json {"result": "défaillance cardiaque"} Output when no suggestion was ever stored for that term (``HTTP 200``): .. code-block:: json {"result": ""}