diff --git a/app/forms/main/config_apo.py b/app/forms/main/config_apo.py
index adac92c0eee63c9b430fa58f91a43b8f8c75df84..87e6d9fa65cbc53095f0920f8f90300807c4c8e4 100644
--- a/app/forms/main/config_apo.py
+++ b/app/forms/main/config_apo.py
@@ -59,6 +59,7 @@ def _build_code_field(code):
 
 class CodesDecisionsForm(FlaskForm):
     "Formulaire code décisions Apogée"
+
     ABAN = _build_code_field("ABAN")
     ABL = _build_code_field("ABL")
     ADC = _build_code_field("ADC")
@@ -92,5 +93,35 @@ class CodesDecisionsForm(FlaskForm):
             validators.DataRequired("format requis"),
         ],
     )
+    APO_DECIMAL_SEP = StringField(
+        label="Séparateur décimal",
+        description="Séparateur décimal pour les notes exportées (par défaut <tt>,</tt>)",
+        validators=[
+            validators.Length(
+                max=SHORT_STR_LEN,
+                message=f"Le séparateur ne doit pas dépasser {SHORT_STR_LEN} caractères",
+            ),
+        ],
+    )
+    APO_INPUT_ENCODING = StringField(
+        label="",
+        description="encodage des fichiers Apogee lus (par défaut <tt>ISO-8859-1</tt>)",
+        validators=[
+            validators.Length(
+                max=SHORT_STR_LEN,
+                message=f"Le code ne doit pas dépasser {SHORT_STR_LEN} caractères",
+            ),
+        ],
+    )
+    APO_OUTPUT_ENCODING = StringField(
+        label="",
+        description="encodage des fichiers Apogee générés (par défaut <tt>ISO-8859-1</tt>)",
+        validators=[
+            validators.Length(
+                max=SHORT_STR_LEN,
+                message=f"Le code ne doit pas dépasser {SHORT_STR_LEN} caractères",
+            ),
+        ],
+    )
     submit = SubmitField("Valider")
     cancel = SubmitField("Annuler", render_kw={"formnovalidate": True})
diff --git a/app/models/config.py b/app/models/config.py
index 613c3ccd06fc031082b5e16844ca3b716b81e0f6..fad76a36eae18609c9633f143557b008418cd871 100644
--- a/app/models/config.py
+++ b/app/models/config.py
@@ -1,7 +1,6 @@
 # -*- coding: UTF-8 -*
 
-"""Model : site config  WORK IN PROGRESS #WIP
-"""
+"""Model : site config  WORK IN PROGRESS #WIP"""
 
 import json
 import re
@@ -57,7 +56,11 @@ CODES_SCODOC_TO_APO = {
     PAS1NCI: "PAS1NCI",
     RAT: "ATT",
     RED: "RED",
+    # Paramètres de config (non des codes)
     "NOTES_FMT": "%3.2f",
+    "APO_DECIMAL_SEP": ",",
+    "APO_INPUT_ENCODING": "ISO-8859-1",
+    "APO_OUTPUT_ENCODING": "ISO-8859-1",
 }
 
 
@@ -282,7 +285,8 @@ class ScoDocSiteConfig(models.ScoDocModel):
 
     @classmethod
     def disable_passerelle(cls, disabled: bool = True) -> bool:
-        """Désactive (ou active) les fonctions liées à la présence d'une passerelle. True si changement."""
+        """Désactive (ou active) les fonctions liées à la présence d'une passerelle.
+        True si changement."""
         return cls.set("disable_passerelle", "on" if disabled else "")
 
     @classmethod
@@ -486,7 +490,8 @@ class PersonalizedLink:
         self.url = str(url or "")
         self.with_args = bool(with_args)
 
-    def get_url(self, params: dict = {}) -> str:
+    def get_url(self, params: dict = None) -> str:
+        params = params or {}
         if not self.with_args:
             return self.url
         query_string = urllib.parse.urlencode(params)
diff --git a/app/scodoc/sco_apogee_compare.py b/app/scodoc/sco_apogee_compare.py
index c7b34d9067df0f7f9b8250b223f0c9a608222b43..f830942aab4f3d505927dd510bd27d928200c02e 100644
--- a/app/scodoc/sco_apogee_compare.py
+++ b/app/scodoc/sco_apogee_compare.py
@@ -47,6 +47,7 @@ from flask import g, render_template, url_for
 
 from app import log
 from app.scodoc import sco_apogee_csv, sco_apogee_reader
+from app.scodoc.sco_apogee_reader import ApoIOConfig
 from app.scodoc.sco_apogee_csv import ApoData
 from app.scodoc.gen_tables import GenTable
 from app.scodoc.sco_exceptions import ScoValueError
@@ -55,6 +56,7 @@ from app.scodoc import sco_preferences
 
 def apo_compare_csv(file_a, file_b, autodetect=True):
     """Page comparing 2 Apogee CSV files"""
+    cfg = ApoIOConfig()
     try:
         apo_data_a = _load_apo_data(file_a, autodetect=autodetect)
         apo_data_b = _load_apo_data(file_b, autodetect=autodetect)
@@ -73,7 +75,7 @@ def apo_compare_csv(file_a, file_b, autodetect=True):
             raise ScoValueError(
                 f"""
             Erreur: l'encodage de l'un des fichiers est incorrect.
-            Vérifiez qu'il est bien en {sco_apogee_reader.APO_INPUT_ENCODING}
+            Vérifiez qu'il est bien en {cfg.input_encoding}.
             """,
                 dest_url=dest_url,
             ) from exc
@@ -85,16 +87,16 @@ def apo_compare_csv(file_a, file_b, autodetect=True):
     )
 
 
-def _load_apo_data(csvfile, autodetect=True):
+def _load_apo_data(csvfile, autodetect=True, cfg: ApoIOConfig = None):
     "Read data from request variable and build ApoData"
     data_b = csvfile.read()
     if autodetect:
-        data_b, message = sco_apogee_reader.fix_data_encoding(data_b)
+        data_b, message = sco_apogee_reader.fix_data_encoding(data_b, cfg=cfg)
         if message:
             log(f"apo_compare_csv: {message}")
         if not data_b:
             raise ScoValueError("fichier vide ? (apo_compare_csv: no data)")
-    data = data_b.decode(sco_apogee_reader.APO_INPUT_ENCODING)
+    data = data_b.decode(cfg.input_encoding)
     apo_data = sco_apogee_csv.ApoData(data, orig_filename=csvfile.filename)
     return apo_data
 
diff --git a/app/scodoc/sco_apogee_csv.py b/app/scodoc/sco_apogee_csv.py
index 6cd68fadc972685a6c4a3aa90f141b15e08e0e84..e3d1803314c55834c79862f0d86e3b4b5be84d37 100644
--- a/app/scodoc/sco_apogee_csv.py
+++ b/app/scodoc/sco_apogee_csv.py
@@ -60,9 +60,9 @@ from app.models import (
 
 from app.models.config import ScoDocSiteConfig
 from app.scodoc.sco_apogee_reader import (
-    APO_DECIMAL_SEP,
     ApoCSVReadWrite,
     ApoEtudTuple,
+    ApoIOConfig,
 )
 import app.scodoc.sco_utils as scu
 from app.scodoc.sco_exceptions import ScoValueError, ScoFormatError
@@ -80,7 +80,7 @@ from app.scodoc import sco_cursus
 from app.scodoc import sco_formsemestre
 
 
-def _apo_fmt_note(note, fmt="%3.2f"):
+def _apo_fmt_note(note, cfg: ApoIOConfig = None):
     "Formatte une note pour Apogée (séparateur décimal: ',')"
     # if not note and isinstance(note, float): changé le 31/1/2022, étrange ?
     #    return ""
@@ -90,7 +90,7 @@ def _apo_fmt_note(note, fmt="%3.2f"):
         return ""
     if np.isnan(val):
         return ""
-    return (fmt % val).replace(".", APO_DECIMAL_SEP)
+    return (cfg.notes_fmt % val).replace(".", cfg.decimal_sep)
 
 
 class EtuCol:
@@ -113,6 +113,7 @@ class ApoEtud(dict):
     def __init__(
         self,
         apo_etud_tuple: ApoEtudTuple,
+        cfg: ApoIOConfig = None,
         export_res_etape=True,
         export_res_sem=True,
         export_res_ues=True,
@@ -120,6 +121,7 @@ class ApoEtud(dict):
         export_res_sdj=True,
         export_res_rat=True,
     ):
+        self.cfg = cfg  # config i/o, formats nombres etc
         self["nip"] = apo_etud_tuple.nip
         self["nom"] = apo_etud_tuple.nom
         self["prenom"] = apo_etud_tuple.prenom
@@ -144,9 +146,7 @@ class ApoEtud(dict):
         self.export_res_sdj = export_res_sdj
         "export meme si pas de decision de jury"
         self.export_res_rat = export_res_rat
-        self.fmt_note = functools.partial(
-            _apo_fmt_note, fmt=ScoDocSiteConfig.get_code_apo("NOTES_FMT") or "3.2f"
-        )
+        self.fmt_note = functools.partial(_apo_fmt_note, cfg=self.cfg)
         # Initialisés par associate_sco:
         self.autre_formsemestre: FormSemestre = None
         self.autre_res: NotesTableCompat = None
@@ -692,6 +692,13 @@ class ApoEtud(dict):
 
 
 class ApoData:
+    """Lecture du fichier CSV Apogée
+    Regroupe les élements importants d'un fichier CSV Apogée
+    periode = 1 (sept-jan) ou 2 (fev-jul), mais cette info n'est pas
+        (toujours) présente dans les CSV Apogée et doit être indiquée par l'utilisateur
+    Laisser periode à None si etape en 1 semestre (LP, décalés, ...)
+    """
+
     def __init__(
         self,
         data: str,
@@ -703,13 +710,9 @@ class ApoData:
         export_res_sdj=True,
         export_res_rat=True,
         orig_filename=None,
+        cfg: ApoIOConfig | None = None,
     ):
-        """Lecture du fichier CSV Apogée
-        Regroupe les élements importants d'un fichier CSV Apogée
-        periode = 1 (sept-jan) ou 2 (fev-jul), mais cette info n'est pas
-         (toujours) présente dans les CSV Apogée et doit être indiquée par l'utilisateur
-        Laisser periode à None si etape en 1 semestre (LP, décalés, ...)
-        """
+        self.cfg = cfg or ApoIOConfig()
         self.export_res_etape = export_res_etape  # VET, ...
         self.export_res_sem = export_res_sem  # elt_sem_apo
         self.export_res_ues = export_res_ues
@@ -722,7 +725,7 @@ class ApoData:
         self.is_apc = None
         "Vrai si BUT"
         try:
-            self.apo_csv = ApoCSVReadWrite(data)
+            self.apo_csv = ApoCSVReadWrite(data, self.cfg)
         except ScoFormatError as e:
             # enrichit le message d'erreur
             filename = self.orig_filename or e.filename
@@ -749,6 +752,7 @@ class ApoData:
                 export_res_modules=export_res_modules,
                 export_res_sdj=export_res_sdj,
                 export_res_rat=export_res_rat,
+                cfg=self.cfg,
             )
             for apo_etud_tuple in self.apo_csv.csv_etuds
         ]
diff --git a/app/scodoc/sco_apogee_reader.py b/app/scodoc/sco_apogee_reader.py
index 32c7f872870371dfef96568d47cf8b0c6ed4c022..48d64cfe488e51a76153ed9095dded8e693e6723 100644
--- a/app/scodoc/sco_apogee_reader.py
+++ b/app/scodoc/sco_apogee_reader.py
@@ -77,6 +77,7 @@ import re
 from chardet import detect as chardet_detect
 
 from app import log
+from app.models.config import ScoDocSiteConfig
 from app.scodoc.sco_exceptions import ScoFormatError
 from app.scodoc import sco_preferences
 
@@ -92,6 +93,33 @@ APO_NEWLINE = "\r\n"
 ApoEtudTuple = namedtuple("ApoEtudTuple", ("nip", "nom", "prenom", "naissance", "cols"))
 
 
+class ApoIOConfig:
+    "Configuration pour la lecture/écriture de fichiers Apogée"
+
+    def __init__(self):
+        self.reload()
+
+    def reload(self):
+        "Load config from ScoDoc configuration"
+        self.notes_fmt = ScoDocSiteConfig.get_code_apo("NOTES_FMT") or "3.2f"
+        self.input_encoding = (
+            ScoDocSiteConfig.get_code_apo("APO_INPUT_ENCODING") or APO_INPUT_ENCODING
+        )
+        self.output_encoding = (
+            ScoDocSiteConfig.get_code_apo("APO_OUTPUT_ENCODING") or APO_OUTPUT_ENCODING
+        )
+        self.decimal_sep = (
+            ScoDocSiteConfig.get_code_apo("APO_DECIMAL_SEP") or APO_DECIMAL_SEP
+        )
+        self.sep = ScoDocSiteConfig.get_code_apo("APO_SEP") or APO_SEP
+        self.newline = ScoDocSiteConfig.get_code_apo("APO_NEWLINE") or APO_NEWLINE
+
+    def __repr__(self):
+        return f"""ApoIOConfig(input_encoding={self.input_encoding}, output_encoding={
+            self.output_encoding}, decimal_sep={self.decimal_sep}, sep={self.sep}, newline={
+            self.newline}, notes_fmt={self.notes_fmt})"""
+
+
 class DictCol(dict):
     "A dict, where we can add attributes"
 
@@ -111,9 +139,10 @@ class StringIOWithLineNumber(io.StringIO):
 class ApoCSVReadWrite:
     "Gestion lecture/écriture de fichiers csv Apogée"
 
-    def __init__(self, data: str):
+    def __init__(self, data: str, cfg: ApoIOConfig = None):
         if not data:
             raise ScoFormatError("Fichier Apogée vide !")
+        self.cfg = cfg
         self.data = data
         self._file = StringIOWithLineNumber(data)  # pour traiter comme un fichier
         self.apo_elts: dict = None
@@ -374,7 +403,7 @@ class ApoCSVReadWrite:
         f = io.StringIO()
         self._write_header(f)
         self._write_etuds(f, apo_etuds)
-        return f.getvalue().encode(APO_OUTPUT_ENCODING)
+        return f.getvalue().encode(self.cfg.output_encoding)
 
     def _write_etuds(self, f, apo_etuds: list["ApoEtud"]):
         """write apo CSV etuds on f"""
@@ -451,8 +480,9 @@ def guess_data_encoding(text: bytes, threshold=0.6):
 
 def fix_data_encoding(
     text: bytes,
-    default_source_encoding=APO_INPUT_ENCODING,
-    dest_encoding=APO_INPUT_ENCODING,
+    default_source_encoding: str = None,
+    dest_encoding: str = None,
+    cfg: ApoIOConfig = None,
 ) -> tuple[bytes, str]:
     """Try to ensure that text is using dest_encoding
     returns converted text, and a message describing the conversion.
@@ -460,6 +490,8 @@ def fix_data_encoding(
     Raises UnicodeEncodeError en cas de problème, en général liée à
     une auto-détection errornée.
     """
+    default_source_encoding = default_source_encoding or cfg.input_encoding
+    dest_encoding = dest_encoding or cfg.output_encoding
     message = ""
     detected_encoding = guess_data_encoding(text)
     if not detected_encoding:
diff --git a/app/scodoc/sco_etape_apogee.py b/app/scodoc/sco_etape_apogee.py
index e11e5713001ec9da1824b983998c2761a6b6d504..ea586aaf96790df39be4e25cc463567a9d0ecd10 100644
--- a/app/scodoc/sco_etape_apogee.py
+++ b/app/scodoc/sco_etape_apogee.py
@@ -26,49 +26,49 @@
 ##############################################################################
 
 """ScoDoc : stockage et vérifications des "maquettes" Apogée
-   (fichiers CSV pour l'export vers Apogée)
-   associées aux années scolaires
+(fichiers CSV pour l'export vers Apogée)
+associées aux années scolaires
 
-   Voir sco_apogee_csv.py pour la structure du fichier Apogée.
+Voir sco_apogee_csv.py pour la structure du fichier Apogée.
 
-   Stockage: utilise sco_archive.py
-    exemple:
-   /opt/scodoc-data/archives/apo_csv/<dept_id>/2016-1/2016-07-03-16-12-19/V3ASR!111.csv
-   pour une maquette de l'étape V3ASR version VDI 111.
+Stockage: utilise sco_archive.py
+ exemple:
+/opt/scodoc-data/archives/apo_csv/<dept_id>/2016-1/2016-07-03-16-12-19/V3ASR!111.csv
+pour une maquette de l'étape V3ASR version VDI 111.
 
-   La version VDI sera ignorée sauf si elle est indiquée dans l'étape du semestre.
-   apo_csv_get()
+La version VDI sera ignorée sauf si elle est indiquée dans l'étape du semestre.
+apo_csv_get()
 
-   API:
-   # apo_csv_store(csv_data, annee_scolaire, sem_id)
-      store maq file (archive)
+API:
+# apo_csv_store(csv_data, annee_scolaire, sem_id)
+   store maq file (archive)
 
-   apo_csv_get(etape_apo, annee_scolaire, sem_id, vdi_apo=None)
-      get maq data (read stored file and returns string)
-      if vdi_apo, get maq for this etape/vdi, else returns the first matching etape.
+apo_csv_get(etape_apo, annee_scolaire, sem_id, vdi_apo=None)
+   get maq data (read stored file and returns string)
+   if vdi_apo, get maq for this etape/vdi, else returns the first matching etape.
 
-   apo_csv_delete(etape_apo, annee_scolaire, sem_id)
+apo_csv_delete(etape_apo, annee_scolaire, sem_id)
 
-   apo_csv_list_stored_etapes(annee_scolaire=None, sem_id=None, etapes=None)
-       returns: liste des codes etapes et version vdi stockés (pour l'annee_scolaire et le sem_id indiqués)
+apo_csv_list_stored_etapes(annee_scolaire=None, sem_id=None, etapes=None)
+    returns: liste des codes etapes et version vdi stockés (pour l'annee_scolaire et le sem_id indiqués)
 
-   apo_csv_semset_check(semset)
-      check students in stored maqs vs students in sem
-      Cas à détecter:
-      - etudiants ScoDoc sans code NIP
-      - etudiants dans sem (ScoDoc) mais dans aucun CSV
-      - etudiants dans un CSV mais pas dans sem ScoDoc
-      - etudiants dans plusieurs CSV (argh!)
-      detecte aussi si on a plusieurs années scolaires
+apo_csv_semset_check(semset)
+   check students in stored maqs vs students in sem
+   Cas à détecter:
+   - etudiants ScoDoc sans code NIP
+   - etudiants dans sem (ScoDoc) mais dans aucun CSV
+   - etudiants dans un CSV mais pas dans sem ScoDoc
+   - etudiants dans plusieurs CSV (argh!)
+   detecte aussi si on a plusieurs années scolaires
 
-      returns: etuds_ok (in ScoDoc and CSVs)
-               etuds_no_apo
-               unknown_apo : liste de { 'NIP', 'nom', 'prenom' }
-               dups_apo : liste de { 'NIP', 'nom', 'prenom', 'etapes_apo' }
-               etapes_missing_csv : liste des étapes du semestre sans maquette CSV
+   returns: etuds_ok (in ScoDoc and CSVs)
+            etuds_no_apo
+            unknown_apo : liste de { 'NIP', 'nom', 'prenom' }
+            dups_apo : liste de { 'NIP', 'nom', 'prenom', 'etapes_apo' }
+            etapes_missing_csv : liste des étapes du semestre sans maquette CSV
 
-   apo_csv_check_etape(semset, set_nips, etape_apo)
-      check une etape
+apo_csv_check_etape(semset, set_nips, etape_apo)
+   check une etape
 
 """
 
@@ -77,6 +77,7 @@ import re
 import app.scodoc.sco_utils as scu
 from app.scodoc import sco_archives
 from app.scodoc import sco_apogee_csv, sco_apogee_reader
+from app.scodoc.sco_apogee_reader import ApoIOConfig
 from app.scodoc.sco_exceptions import ScoValueError
 
 
@@ -96,7 +97,7 @@ APO_CSV_ARCHIVER = ApoCSVArchiver()
 #     return archive_id
 
 
-def apo_csv_store(csv_data: str, annee_scolaire, sem_id):
+def apo_csv_store(csv_data: str, annee_scolaire, sem_id, cfg: ApoIOConfig):
     """
     csv_data: maquette content (str))
     annee_scolaire: int (2016)
@@ -127,7 +128,7 @@ def apo_csv_store(csv_data: str, annee_scolaire, sem_id):
     oid = f"{annee_scolaire}-{sem_id}"
     description = f"""{str(apo_data.etape)};{annee_scolaire};{sem_id}"""
     archive_id = APO_CSV_ARCHIVER.create_obj_archive(oid, description)
-    csv_data_bytes = csv_data.encode(sco_apogee_reader.APO_OUTPUT_ENCODING)
+    csv_data_bytes = csv_data.encode(cfg.output_encoding)
     APO_CSV_ARCHIVER.store(archive_id, filename, csv_data_bytes)
 
     return apo_data.etape
@@ -199,20 +200,22 @@ def apo_csv_get_archive(etape_apo, annee_scolaire="", sem_id=""):
     return None
 
 
-def apo_csv_get(etape_apo="", annee_scolaire="", sem_id="") -> str:
+def apo_csv_get(
+    etape_apo="", annee_scolaire="", sem_id="", cfg: ApoIOConfig = None
+) -> str:
     """Get CSV data for given etape_apo
     :return: CSV, as a data string
     """
     info = apo_csv_get_archive(etape_apo, annee_scolaire, sem_id)
     if not info:
         raise ScoValueError(
-            "Etape %s non enregistree (%s, %s)" % (etape_apo, annee_scolaire, sem_id)
+            f"Étape {etape_apo} non enregistree ({annee_scolaire}, {sem_id})"
         )
     archive_id = info["archive_id"]
     data = APO_CSV_ARCHIVER.get(archive_id, etape_apo + ".csv")
     # ce fichier a été archivé donc généré par ScoDoc
     # son encodage est donc APO_OUTPUT_ENCODING
-    return data.decode(sco_apogee_reader.APO_OUTPUT_ENCODING)
+    return data.decode(cfg.output_encoding)
 
 
 # ------------------------------------------------------------------------
@@ -229,11 +232,13 @@ def apo_get_sem_etapes(sem):
     return sem["etapes"]
 
 
-def apo_csv_check_etape(semset, set_nips, etape_apo):
+def apo_csv_check_etape(semset, set_nips, etape_apo, cfg: ApoIOConfig = None):
     """Check etape vs set of sems"""
     # Etudiants dans la maquette CSV:
-    csv_data = apo_csv_get(etape_apo, semset["annee_scolaire"], semset["sem_id"])
-    apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
+    csv_data = apo_csv_get(
+        etape_apo, semset["annee_scolaire"], semset["sem_id"], cfg=cfg
+    )
+    apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"], cfg=cfg)
     apo_nips = {e["nip"] for e in apo_data.etuds}
     #
     nips_ok = set_nips.intersection(apo_nips)
@@ -258,6 +263,7 @@ def apo_csv_semset_check(semset, allow_missing_apo=False, allow_missing_csv=Fals
       - etudiants dans plusieurs CSV (argh!)
       + si plusieurs annees scolaires
     """
+    cfg = ApoIOConfig()
     # Etapes du semestre sans maquette CSV:
     etapes_apo = apo_csv_list_stored_etapes(
         semset["annee_scolaire"], semset["sem_id"], etapes=semset.list_etapes()
@@ -286,7 +292,7 @@ def apo_csv_semset_check(semset, allow_missing_apo=False, allow_missing_csv=Fals
             et_nips_no_sco,
             et_maq_elems,
             et_sem_elems,
-        ) = apo_csv_check_etape(semset, set_nips, etape_apo)
+        ) = apo_csv_check_etape(semset, set_nips, etape_apo, cfg)
         nips_ok |= et_nips_ok
         nips_no_apo -= et_apo_nips
         nips_no_sco |= et_nips_no_sco
@@ -324,7 +330,7 @@ def apo_csv_semset_check(semset, allow_missing_apo=False, allow_missing_csv=Fals
     )
 
 
-def apo_csv_retreive_etuds_by_nip(semset, nips):
+def apo_csv_retreive_etuds_by_nip(semset, nips, cfg: ApoIOConfig = None):
     """
     Search info about listed nips in stored CSV
     :return: list [ { 'etape_apo', 'nip', 'nom', 'prenom' } ]
@@ -332,7 +338,9 @@ def apo_csv_retreive_etuds_by_nip(semset, nips):
     apo_etuds_by_nips = {}
     etapes_apo = apo_csv_list_stored_etapes(semset["annee_scolaire"], semset["sem_id"])
     for etape_apo in etapes_apo:
-        csv_data = apo_csv_get(etape_apo, semset["annee_scolaire"], semset["sem_id"])
+        csv_data = apo_csv_get(
+            etape_apo, semset["annee_scolaire"], semset["sem_id"], cfg=cfg
+        )
         apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
         etape_apo = apo_data.etape_apogee
         for e in apo_data.etuds:
diff --git a/app/scodoc/sco_etape_apogee_view.py b/app/scodoc/sco_etape_apogee_view.py
index 0e7913fd2f0ebcae4a80c5516d2a647c4aad36e8..32e0ad62b6852755b504d332bf58053b3cca9061 100644
--- a/app/scodoc/sco_etape_apogee_view.py
+++ b/app/scodoc/sco_etape_apogee_view.py
@@ -25,8 +25,7 @@
 #
 ##############################################################################
 
-"""ScoDoc : formulaires gestion maquettes Apogee / export resultats
-"""
+"""ScoDoc : formulaires gestion maquettes Apogee / export resultats"""
 
 import io
 from zipfile import ZipFile
@@ -45,7 +44,7 @@ from app.scodoc import sco_preferences
 from app.scodoc import sco_semset
 from app.scodoc import sco_etud
 from app.scodoc.gen_tables import GenTable
-from app.scodoc.sco_apogee_reader import APO_INPUT_ENCODING, APO_OUTPUT_ENCODING
+from app.scodoc.sco_apogee_reader import ApoIOConfig
 from app.scodoc.sco_exceptions import ScoValueError
 
 
@@ -63,6 +62,7 @@ def apo_semset_maq_status(
     """Page statut / tableau de bord"""
     if not semset_id:
         raise ScoValueError("invalid null semset_id")
+    cfg = ApoIOConfig()
     semset = sco_semset.SemSet(semset_id=semset_id)
     semset.fill_formsemestres()
     # autorise export meme si etudiants Apo manquants:
@@ -384,7 +384,7 @@ def apo_semset_maq_status(
     l'export des résultats après les jurys, puis de remplir et exporter ces fichiers.
     </p>
     <p>
-    Les fichiers ("maquettes") Apogée sont de type CSV, du texte codé en {APO_INPUT_ENCODING}.
+    Les fichiers ("maquettes") Apogée sont de type CSV, du texte codé en {cfg.input_encoding}.
     </p>
     <p>On a un fichier par étape Apogée. Pour les obtenir, soit on peut les télécharger
     directement (si votre ScoDoc est interfacé avec Apogée), soit se débrouiller pour
@@ -444,6 +444,7 @@ def apo_semset_maq_status(
 def table_apo_csv_list(semset):
     """Table des archives (triée par date d'archivage)"""
     annee_scolaire = semset["annee_scolaire"]
+    cfg = ApoIOConfig()
     sem_id = semset["sem_id"]
 
     rows = sco_etape_apogee.apo_csv_list_stored_archives(
@@ -452,7 +453,9 @@ def table_apo_csv_list(semset):
 
     for t in rows:
         # Ajoute qq infos pour affichage:
-        csv_data = sco_etape_apogee.apo_csv_get(t["etape_apo"], annee_scolaire, sem_id)
+        csv_data = sco_etape_apogee.apo_csv_get(
+            t["etape_apo"], annee_scolaire, sem_id, cfg=cfg
+        )
         apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
         t["filename"] = apo_data.apo_csv.titles["apoC_Fichier_Exp"]
         t["nb_etuds"] = len(apo_data.etuds)
@@ -507,12 +510,13 @@ def view_apo_etuds(semset_id, title="", nip_list="", fmt="html"):
     if not semset_id:
         raise ValueError("invalid null semset_id")
     semset = sco_semset.SemSet(semset_id=semset_id)
+    cfg = ApoIOConfig()
     # annee_scolaire = semset["annee_scolaire"]
     # sem_id = semset["sem_id"]
     if not isinstance(nip_list, str):
         nip_list = str(nip_list)
     nips = nip_list.split(",")
-    etuds = sco_etape_apogee.apo_csv_retreive_etuds_by_nip(semset, nips)
+    etuds = sco_etape_apogee.apo_csv_retreive_etuds_by_nip(semset, nips, cfg=cfg)
     # Ils sont parfois dans ScoDoc même si pas dans le semestre: essaie de les retrouver
     for etud in etuds.values():
         etud_sco = sco_etud.get_etud_info(code_nip=etud["nip"], filled=True)
@@ -617,6 +621,7 @@ def view_apo_csv_store(semset_id="", csvfile=None, data: bytes = "", autodetect=
     """
     if not semset_id:
         raise ValueError("invalid null semset_id")
+    cfg = ApoIOConfig()
     semset = sco_semset.SemSet(semset_id=semset_id)
     try:
         if csvfile:
@@ -624,7 +629,7 @@ def view_apo_csv_store(semset_id="", csvfile=None, data: bytes = "", autodetect=
             if autodetect:
                 # check encoding (although documentation states that users SHOULD upload LATIN1)
 
-                data, message = sco_apogee_reader.fix_data_encoding(data)
+                data, message = sco_apogee_reader.fix_data_encoding(data, cfg=cfg)
                 if message:
                     log(f"view_apo_csv_store: {message}")
             else:
@@ -632,7 +637,7 @@ def view_apo_csv_store(semset_id="", csvfile=None, data: bytes = "", autodetect=
         if not data:
             raise ScoValueError("view_apo_csv_store: no data")
         # data est du bytes, encodé en APO_INPUT_ENCODING
-        data_str = data.decode(APO_INPUT_ENCODING)
+        data_str = data.decode(cfg.input_encoding)
     except (UnicodeDecodeError, UnicodeEncodeError) as exc:
         dest_url = url_for(
             "notes.apo_semset_maq_status",
@@ -644,7 +649,7 @@ def view_apo_csv_store(semset_id="", csvfile=None, data: bytes = "", autodetect=
                 f"""
                     Erreur: l'encodage du fichier est mal détecté.
                     Essayez sans auto-détection, ou vérifiez le codage et le contenu
-                    du fichier (qui doit être en {sco_apogee_reader.APO_INPUT_ENCODING}).
+                    du fichier (qui doit être en {cfg.input_encoding}).
                     """,
                 dest_url=dest_url,
             ) from exc
@@ -652,7 +657,7 @@ def view_apo_csv_store(semset_id="", csvfile=None, data: bytes = "", autodetect=
             raise ScoValueError(
                 f"""
                     Erreur: l'encodage du fichier est incorrect.
-                    Vérifiez qu'il est bien en {sco_apogee_reader.APO_INPUT_ENCODING}
+                    Vérifiez qu'il est bien en {cfg.input_encoding}
                     """,
                 dest_url=dest_url,
             ) from exc
@@ -672,7 +677,9 @@ def view_apo_csv_store(semset_id="", csvfile=None, data: bytes = "", autodetect=
             dest_url=dest_url,
         )
 
-    sco_etape_apogee.apo_csv_store(data_str, semset["annee_scolaire"], semset["sem_id"])
+    sco_etape_apogee.apo_csv_store(
+        data_str, semset["annee_scolaire"], semset["sem_id"], cfg=cfg
+    )
 
     return flask.redirect(dest_url)
 
@@ -682,13 +689,14 @@ def view_apo_csv_download_and_store(etape_apo="", semset_id=""):
     if not semset_id:
         raise ValueError("invalid null semset_id")
     semset = sco_semset.SemSet(semset_id=semset_id)
+    cfg = ApoIOConfig()
 
     data = sco_portal_apogee.get_maquette_apogee(
         etape=etape_apo, annee_scolaire=semset["annee_scolaire"]
     )
     # here, data is str
     # but we store and generate latin1 files, to ease further import in Apogée
-    data = data.encode(APO_OUTPUT_ENCODING)
+    data = data.encode(cfg.output_encoding)
     return view_apo_csv_store(semset_id, data=data, autodetect=False)
 
 
@@ -727,11 +735,12 @@ def view_apo_csv(etape_apo="", semset_id="", fmt="html"):
     semset = sco_semset.SemSet(semset_id=semset_id)
     annee_scolaire = semset["annee_scolaire"]
     sem_id = semset["sem_id"]
-    csv_data = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, sem_id)
+    cfg = ApoIOConfig()
+    csv_data = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, sem_id, cfg=cfg)
     if fmt == "raw":
         return scu.send_file(csv_data, etape_apo, suffix=".txt", mime=scu.CSV_MIMETYPE)
 
-    apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"])
+    apo_data = sco_apogee_csv.ApoData(csv_data, periode=semset["sem_id"], cfg=cfg)
 
     (
         ok_for_export,
@@ -868,6 +877,7 @@ def apo_csv_export_results(
         raise ValueError("invalid null semset_id")
     semset = sco_semset.SemSet(semset_id=semset_id)
     annee_scolaire = semset["annee_scolaire"]
+    cfg = ApoIOConfig()
     periode = semset["sem_id"]
 
     data = io.BytesIO()
@@ -876,7 +886,9 @@ def apo_csv_export_results(
             annee_scolaire, periode, etapes=semset.list_etapes()
         )
         for etape_apo in etapes_apo:
-            apo_csv = sco_etape_apogee.apo_csv_get(etape_apo, annee_scolaire, periode)
+            apo_csv = sco_etape_apogee.apo_csv_get(
+                etape_apo, annee_scolaire, periode, cfg=cfg
+            )
             sco_apogee_csv.export_csv_to_apogee(
                 apo_csv,
                 periode=periode,
diff --git a/app/templates/configuration.j2 b/app/templates/configuration.j2
index 7d6b34744016a52da1c1a1c0723beee62dd95d0d..53fffe2719a3cd7af00bdfd9a8913802e25be517 100644
--- a/app/templates/configuration.j2
+++ b/app/templates/configuration.j2
@@ -89,7 +89,7 @@ Heure: <b><tt>{{ time.strftime("%d/%m/%Y %H:%M") }}</tt></b>
 
 <div class="scobox">
     <div class="scobox-title">Exports Apogée</div>
-    <p><a class="stdlink" href="{{url_for('scodoc.config_codes_decisions')}}">Configuration des codes de décision</a>
+    <p><a class="stdlink" href="{{url_for('scodoc.config_codes_decisions')}}">Configuration des codes de décision et paramétrage Apogée</a>
     </p>
 </div>
 
diff --git a/tests/unit/test_apogee_csv.py b/tests/unit/test_apogee_csv.py
index eb4b31bb84369008157662318ef2619653040059..0bbed0b765f4a8b65a80ba305600f4367d4906d6 100644
--- a/tests/unit/test_apogee_csv.py
+++ b/tests/unit/test_apogee_csv.py
@@ -4,9 +4,7 @@
 # See LICENSE
 ##############################################################################
 
-""" Test lecture/érciture fichiers Apogée
-
-"""
+"""Test lecture/érciture fichiers Apogée"""
 
 import pytest
 from flask import g
@@ -45,7 +43,7 @@ def test_apogee_csv(test_client):
     formsemestre.etapes.append(etape)
     db.session.add(formsemestre)
     db.session.commit()
-    #
+    # utilise l'encodage par defaut, APO_INPUT_ENCODING, et non la config
     with open(APO_CSV_FILE, encoding=sco_apogee_reader.APO_INPUT_ENCODING) as f:
         data = f.read()
     assert "ETUDIANT" in data