diff --git a/README.md b/README.md
index 3cfbf39de69b319c698be3bd7bc8175547a53118..6c79f8bba25ff66cea43471760dc05e9f36f3210 100644
--- a/README.md
+++ b/README.md
@@ -16,10 +16,10 @@ La version 9.0 s'efforce de reproduire presque à l'identique le fonctionnement
 de ScoDoc7, avec des composants logiciels différents (Debian 11, Python 3,
 Flask, SQLAlchemy, au lien de Python2/Zope dans les versions précédentes).
 
-### État actuel (nov 22)
+### État actuel (dec 22)
 
-- 9.3.x est en production
-- le prochain jalon est 9.4. Voir branches sur gitea.
+- 9.4.x est en production
+- le prochain jalon est 9.5. Voir branches sur gitea.
 
 ### Lignes de commandes
 
diff --git a/app/api/logos.py b/app/api/logos.py
index 49c0619c60604dcd399e559b44180216cf73519f..5c31ed7586a8a92f1daceabd9f4894b34027fc8c 100644
--- a/app/api/logos.py
+++ b/app/api/logos.py
@@ -48,6 +48,7 @@ from app.scodoc.sco_permissions import Permission
 @scodoc
 @permission_required(Permission.ScoSuperAdmin)
 def api_get_glob_logos():
+    """Liste tous les logos"""
     logos = list_logos()[None]
     return jsonify(list(logos.keys()))
 
diff --git a/tests/api/README.md b/tests/api/README.md
index 535b6cdee489d58471e3d54045c278bc32af2a80..c1b7b86e845c473a765e190499cecf2fc15bd2b7 100644
--- a/tests/api/README.md
+++ b/tests/api/README.md
@@ -1,28 +1,44 @@
 # Tests unitaires de l'API ScoDoc
 
-Démarche générale:
+## Lancement des tests
 
- 1. On génère une base SQL de test: voir
-   `tools/fakedatabase/create_test_api_database.py`
+La première fois, copier le fichier `tests/api/dotenv_exemple` vers
+`tests/api/.env`. Il est normalement inutile de modifier son contenu.
 
-    1. modifier `/opt/scodoc/.env` pour indiquer
+Dans un shell, lancer le script `start_api_server.py`, qui se charge
+d'initialiser une base SQL de test et de lancer le serveur ScoDoc approprié.
 
-    ```bash
-    FLASK_ENV=test_api
-    FLASK_DEBUG=1 
-    ```
+```bash
+tests/api/start_api_server.sh
+```
+
+Dans un autre shell, lancer les tests:
+
+```bash
+pytest tests/api
+```
+
+## Notes sur la démarche
+
+ 1. On génère une base SQL de test: voir
+   `tools/fakedatabase/create_test_api_database.py`
 
-    2. En tant qu'utilisateur scodoc, lancer:
+    1. En tant qu'utilisateur scodoc, lancer:
 
     ```bash
+    # evite de modifier /opt/scodoc/.env
+    export FLASK_ENV=test_api
+    export FLASK_DEBUG=1 
     tools/create_database.sh --drop SCODOC_TEST_API
     flask db upgrade
     flask sco-db-init --erase
     flask init-test-database
     ```
 
-    en plus court: ```bash
-    tools/create_database.sh --drop SCODOC_TEST_API && flask db upgrade &&flask sco-db-init --erase && flask init-test-database
+    en plus court:
+
+    ```bash
+    export FLASK_ENV=test_api && tools/create_database.sh --drop SCODOC_TEST_API && flask db upgrade &&flask sco-db-init --erase && flask init-test-database
     ```
 
  2. On lance le serveur ScoDoc sur cette base
diff --git a/tests/api/dotenv_exemple b/tests/api/dotenv_exemple
index e1857bd810ea456652673d951f4111b0427ac1e3..e990d0c5c92be788421ed4de74539df39a870d16 100644
--- a/tests/api/dotenv_exemple
+++ b/tests/api/dotenv_exemple
@@ -4,8 +4,11 @@
 # et à remplir.
 
 # URL du serveur ScoDoc à interroger
-SCODOC_URL = "http://localhost:5000/"
+SCODOC_URL="http://localhost:5000/"
 
 # Le client (python) doit-il vérifier le certificat SSL du serveur ?
 # ou True si serveur de production avec certif SSL valide
-CHECK_CERTIFICATE = False
+CHECK_CERTIFICATE=False
+
+API_USER="lecteur_api"
+API_PASSWORD="azerty"
diff --git a/tests/api/start_api_server.sh b/tests/api/start_api_server.sh
new file mode 100755
index 0000000000000000000000000000000000000000..67a3120a7aaaca42a6b15b86f08c2e23b6050c93
--- /dev/null
+++ b/tests/api/start_api_server.sh
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# Script recreating the TEST API database and starting the serveur
+
+set -e
+
+# Le répertoire de ce script:
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
+# récupère API_USER et API_PASSWORD
+source "$SCRIPT_DIR"/.env
+
+export FLASK_ENV=test_api
+export FLASK_DEBUG=1 
+tools/create_database.sh --drop SCODOC_TEST_API
+flask db upgrade
+flask sco-db-init --erase
+flask init-test-database
+
+flask user-create "$API_USER" LecteurAPI @all
+flask user-password --password "$API_PASSWORD" "$API_USER"
+flask edit-role LecteurAPI -a ScoView
+flask user-role "$API_USER" -a LecteurAPI
+
+flask run --host 0.0.0.0
\ No newline at end of file
diff --git a/tests/api/test_api_etudiants.py b/tests/api/test_api_etudiants.py
index 72009093636bde45ca85c3c6b93d361a36c3a917..b4fc97f3b986892c92af20ed94a9dcf97ddca5d0 100644
--- a/tests/api/test_api_etudiants.py
+++ b/tests/api/test_api_etudiants.py
@@ -76,7 +76,6 @@ def test_etudiants_courant(api_headers):
 
     etud = etudiants[-1]
     assert verify_fields(etud, fields) is True
-    assert etud["id"] == etud["etudid"]
     assert isinstance(etud["id"], int)
     assert isinstance(etud["code_nip"], str)
     assert isinstance(etud["nom"], str)
diff --git a/tests/api/test_api_logos.py b/tests/api/test_api_logos.py
index 9ae59e87fb7e146c5791f3bb4a3322f2b6715fb1..8277c18400c11eb900f15df7e01b8788aec4d831 100644
--- a/tests/api/test_api_logos.py
+++ b/tests/api/test_api_logos.py
@@ -13,8 +13,13 @@ utilisation:
 # XXX TODO
 #  Ce test a une logique très différente des autres : A UNIFIER
 
-
-from tests.api.setup_test_api import API_URL, api_admin_headers, api_headers
+import requests
+from tests.api.setup_test_api import (
+    API_URL,
+    api_admin_headers,
+    api_headers,
+    CHECK_CERTIFICATE,
+)
 
 from scodoc import app
 from tests.unit.config_test_logos import (
@@ -30,147 +35,159 @@ def test_super_access(api_admin_headers):
     """
     Route: /logos
     """
-    headers = api_admin_headers
-    with app.test_client(api_admin_headers) as client:
-        response = client.get(API_URL + "/logos", headers=headers)
-        assert response.status_code == 200
-        assert response.json is not None
-
-
-def test_admin_access(api_headers):
-    """
-    Route: /logos
-    """
-    headers = api_headers
-    with app.test_client() as client:
-        response = client.get(API_URL + "/logos", headers=headers)
-        assert response.status_code == 401
+    response = requests.get(
+        API_URL + "/logos",
+        headers=api_admin_headers,
+        verify=CHECK_CERTIFICATE,
+    )
+    assert response.status_code == 200
+    assert response.json() is not None
 
 
 def test_lambda_access(api_headers):
     """
     Route: /logos
     """
-    headers = api_headers
-    with app.test_client() as client:
-        response = client.get(API_URL + "/logos", headers=headers)
-        assert response.status_code == 401
+    response = requests.get(
+        API_URL + "/logos",
+        headers=api_headers,
+        verify=CHECK_CERTIFICATE,
+    )
+    assert response.status_code == 401
 
 
 def test_global_logos(api_admin_headers):
     """
     Route:
     """
-    headers = api_admin_headers
-    with app.test_client() as client:
-        response = client.get(API_URL + "/logos", headers=headers)
-        assert response.status_code == 200
-        assert response.json is not None
-        assert "header" in response.json
-        assert "footer" in response.json
-        assert "B" in response.json
-        assert "C" in response.json
+    response = requests.get(
+        API_URL + "/logos",
+        headers=api_admin_headers,
+        verify=CHECK_CERTIFICATE,
+    )
+    assert response.status_code == 200
+    assert response.json() is not None
+    assert "header" in response.json()
+    assert "footer" in response.json()
+    assert "B" in response.json()
+    assert "C" in response.json()
 
 
 def test_local_by_id_logos(api_admin_headers):
     """
     Route: /departement/id/1/logos
     """
-    headers = api_admin_headers
-    with app.test_client() as client:
-        response = client.get(API_URL + "/departement/id/1/logos", headers=headers)
-        assert response.status_code == 200
-        assert response.json is not None
-        assert "A" in response.json
-        assert "D" in response.json
+    response = requests.get(
+        API_URL + "/departement/id/1/logos",
+        headers=api_admin_headers,
+        verify=CHECK_CERTIFICATE,
+    )
+    assert response.status_code == 200
+    assert response.json() is not None
+    assert "A" in response.json()
+    assert "D" in response.json()
 
 
 def test_local_by_name_logos(api_admin_headers):
     """
     Route: /departement/TAPI/logos
     """
-    headers = api_admin_headers
-    with app.test_client() as client:
-        response = client.get(API_URL + "/departement/TAPI/logos", headers=headers)
-        assert response.status_code == 200
-        assert response.json is not None
-        assert "A" in response.json
-        assert "D" in response.json
+    response = requests.get(
+        API_URL + "/departement/TAPI/logos",
+        headers=api_admin_headers,
+        verify=CHECK_CERTIFICATE,
+    )
+    assert response.status_code == 200
+    assert response.json() is not None
+    assert "A" in response.json()
+    assert "D" in response.json()
 
 
 def test_local_png_by_id_logo(api_admin_headers):
     """
     Route: /departement/id/1/logo/D
     """
-    headers = api_admin_headers
-    with app.test_client() as client:
-        response = client.get(API_URL + "/departement/id/1/logo/D", headers=headers)
-        assert response.status_code == 200
-        assert response.headers["Content-Type"] == "image/png"
-        assert response.headers["Content-Disposition"].startswith("inline")
-        assert "logo_D.png" in response.headers["Content-Disposition"]
+    response = requests.get(
+        API_URL + "/departement/id/1/logo/D",
+        headers=api_admin_headers,
+        verify=CHECK_CERTIFICATE,
+    )
+    assert response.status_code == 200
+    assert response.headers["Content-Type"] == "image/png"
+    assert response.headers["Content-Disposition"].startswith("inline")
+    assert "logo_D.png" in response.headers["Content-Disposition"]
 
 
 def test_global_png_logo(api_admin_headers):
     """
     Route: /logo/C
     """
-    headers = api_admin_headers
-    with app.test_client() as client:
-        response = client.get(API_URL + "/logo/C", headers=headers)
-        assert response.status_code == 200
-        assert response.headers["Content-Type"] == "image/png"
-        assert response.headers["Content-Disposition"].startswith("inline")
-        assert "logo_C.png" in response.headers["Content-Disposition"]
+    response = requests.get(
+        API_URL + "/logo/C",
+        headers=api_admin_headers,
+        verify=CHECK_CERTIFICATE,
+    )
+    assert response.status_code == 200
+    assert response.headers["Content-Type"] == "image/png"
+    assert response.headers["Content-Disposition"].startswith("inline")
+    assert "logo_C.png" in response.headers["Content-Disposition"]
 
 
 def test_global_jpg_logo(api_admin_headers):
     """
     Route: /logo/B
     """
-    headers = api_admin_headers
-    with app.test_client() as client:
-        response = client.get(API_URL + "/logo/B", headers=headers)
-        assert response.status_code == 200
-        assert response.headers["Content-Type"] == "image/jpg"
-        assert response.headers["Content-Disposition"].startswith("inline")
-        assert "logo_B.jpg" in response.headers["Content-Disposition"]
+    response = requests.get(
+        API_URL + "/logo/B",
+        headers=api_admin_headers,
+        verify=CHECK_CERTIFICATE,
+    )
+    assert response.status_code == 200
+    assert response.headers["Content-Type"] == "image/jpg"
+    assert response.headers["Content-Disposition"].startswith("inline")
+    assert "logo_B.jpg" in response.headers["Content-Disposition"]
 
 
 def test_local_png_by_name_logo(api_admin_headers):
     """
-    Route: /departement/TAPI/logo/A
+    Route: /departement/TAPI/logo/D
     """
-    headers = api_admin_headers
-    with app.test_client() as client:
-        response = client.get(API_URL + "/departement/TAPI/logo/D", headers=headers)
-        assert response.status_code == 200
-        assert response.headers["Content-Type"] == "image/png"
-        assert response.headers["Content-Disposition"].startswith("inline")
-        assert "logo_D.png" in response.headers["Content-Disposition"]
+    response = requests.get(
+        API_URL + "/departement/TAPI/logo/D",
+        headers=api_admin_headers,
+        verify=CHECK_CERTIFICATE,
+    )
+    assert response.status_code == 200
+    assert response.headers["Content-Type"] == "image/png"
+    assert response.headers["Content-Disposition"].startswith("inline")
+    assert "logo_D.png" in response.headers["Content-Disposition"]
 
 
 def test_local_jpg_by_id_logo(api_admin_headers):
     """
-    Route: /departement/id/1/logo/D
+    Route: /departement/id/1/logo/A
     """
-    headers = api_admin_headers
-    with app.test_client() as client:
-        response = client.get(API_URL + "/departement/id/1/logo/A", headers=headers)
-        assert response.status_code == 200
-        assert response.headers["Content-Type"] == "image/jpg"
-        assert response.headers["Content-Disposition"].startswith("inline")
-        assert "logo_A.jpg" in response.headers["Content-Disposition"]
+    response = requests.get(
+        API_URL + "/departement/id/1/logo/A",
+        headers=api_admin_headers,
+        verify=CHECK_CERTIFICATE,
+    )
+    assert response.status_code == 200
+    assert response.headers["Content-Type"] == "image/jpg"
+    assert response.headers["Content-Disposition"].startswith("inline")
+    assert "logo_A.jpg" in response.headers["Content-Disposition"]
 
 
 def test_local_jpg_by_name_logo(api_admin_headers):
     """
     Route: /departement/TAPI/logo/A
     """
-    headers = api_admin_headers
-    with app.test_client() as client:
-        response = client.get(API_URL + "/departement/TAPI/logo/A", headers=headers)
-        assert response.status_code == 200
-        assert response.headers["Content-Type"] == "image/jpg"
-        assert response.headers["Content-Disposition"].startswith("inline")
-        assert "logo_A.jpg" in response.headers["Content-Disposition"]
+    response = requests.get(
+        API_URL + "/departement/TAPI/logo/A",
+        headers=api_admin_headers,
+        verify=CHECK_CERTIFICATE,
+    )
+    assert response.status_code == 200
+    assert response.headers["Content-Type"] == "image/jpg"
+    assert response.headers["Content-Disposition"].startswith("inline")
+    assert "logo_A.jpg" in response.headers["Content-Disposition"]
diff --git a/tools/create_database.sh b/tools/create_database.sh
index e1d7ffb9564a00d0e87563d9a3a31b23b634a71d..6e9a939ed5ee652d406afaca2e00052d0f8cad49 100755
--- a/tools/create_database.sh
+++ b/tools/create_database.sh
@@ -15,7 +15,7 @@ if [ "$1" = "--drop" ]
 then
   db_name="$2"
   echo "Dropping database $db_name..."
-  dropdb "$db_name"
+  dropdb --if-exists "$db_name"
 else
   db_name="$1"
 fi