diff --git a/README.md b/README.md
index 4a09a144da1115a598849e46e4c73125e8112c2d..5933bd80822722eb9565d323d09ca6bda7bf3ab0 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,20 @@
 # calng
 
 calng is a collection of Karabo devices to perform online processing of 2D X-ray detector data at runtime. It is the successor of the calPy package.
+
+# CalCat secrets and deployment
+Correction devices each run their own `calibration_client.CalibrationClient`, so they need to have credentials for CalCat.
+They expect to be able to load these from a JSON file; by default, this will be in `$KARABO/var/data/calibration-client-secrets.json` (`var/data` is CWD of Karabo devices).
+The file should look something like:
+
+```json
+{
+	"base_url": "https://in.xfel.eu/test_calibration",
+	"client_id": "[sort of secret]",
+	"client_secret": "[actual secret]",
+	"user_email": "[eh, not that secret]",
+	"caldb_store_path": "/gpfs/exfel/d/cal/caldb_store"
+}
+```
+
+For deployment, you'll want `/calibration` instead of `/test_calibration` and the caldb store as seen from ONC will be `/common/cal/caldb_store`.
diff --git a/src/calng/AgipdCorrection.py b/src/calng/AgipdCorrection.py
index a9d9e3a47e22b2e5d4ea3f8ce1088427f0d29979..22b505813fe70169b0c1b993e748db8132648e26 100644
--- a/src/calng/AgipdCorrection.py
+++ b/src/calng/AgipdCorrection.py
@@ -1,3 +1,4 @@
+import pathlib
 import timeit
 
 import numpy as np
@@ -160,7 +161,10 @@ class AgipdCorrection(BaseCorrection):
 
     def __init__(self, config):
         super().__init__(config)
-        self.calibration_constant_manager = AgipdCalcatFriend(self)
+        # TODO: consider putting this initialization in base class
+        self.calibration_constant_manager = AgipdCalcatFriend(
+            self, pathlib.Path.cwd() / "calibration-client-secrets.json"
+        )
         # TODO: different gpu runner for fixed gain mode
         self.gain_mode = AgipdGainMode[config.get("gainMode")]
         self.bad_pixel_mask_value = eval(config.get("corrections.badPixelMaskValue"))
@@ -316,7 +320,9 @@ class AgipdCorrection(BaseCorrection):
         self.flush_constants()
         for constant in AgipdConstants:
             try:
-                metadata, data = self.calibration_constant_manager.get_constant_version(constant)
+                metadata, data = self.calibration_constant_manager.get_constant_version(
+                    constant
+                )
             except Exception as e:
                 self.log.WARN(f"Failed getting {constant} with {e}")
             else:
diff --git a/src/calng/calcat_utils.py b/src/calng/calcat_utils.py
index 2d1997cdb31694714c8e5d1d45636d93562cbd43..5d4c8c874eca63b4a954f1e8a7e51db97f1388ec 100644
--- a/src/calng/calcat_utils.py
+++ b/src/calng/calcat_utils.py
@@ -1,6 +1,7 @@
 import copy
 import enum
 import functools
+import json
 import pathlib
 import threading
 import typing
@@ -101,34 +102,8 @@ class BaseCalcatFriend:
         AgipdCalcatFriend.dark_condition.
         """
 
-        # Settings needed to use calibration client and find constants
         (
             NODE_ELEMENT(schema).key(prefix).commit(),
-            NODE_ELEMENT(schema).key(f"{prefix}.calCat").commit(),
-            STRING_ELEMENT(schema)
-            .key(f"{prefix}.calCat.baseUrl")
-            .assignmentMandatory()
-            .commit(),
-            STRING_ELEMENT(schema)
-            .key(f"{prefix}.calCat.clientId")
-            .assignmentMandatory()
-            .commit(),
-            STRING_ELEMENT(schema)
-            .key(f"{prefix}.calCat.clientSecret")
-            .assignmentMandatory()
-            .commit(),
-            STRING_ELEMENT(schema)
-            .key(f"{prefix}.calCat.userEmail")
-            .assignmentOptional()
-            .defaultValue("")
-            .commit(),
-            STRING_ELEMENT(schema)
-            .key(f"{prefix}.calCat.caldbStore")
-            .displayedName("Location of caldb_store")
-            .assignmentOptional()
-            .defaultValue("/gpfs/exfel/d/cal/caldb_store")
-            .options("/gpfs/exfel/d/cal/caldb_store,/common/cal/caldb_store")
-            .commit(),
         )
 
         # Parameters which any detector would probably have (extend this in subclass)
@@ -174,21 +149,27 @@ class BaseCalcatFriend:
     def __init__(
         self,
         device,
+        secrets_fn: pathlib.Path,
         prefix="constantParameters",
     ):
         self.device = device
         self.prefix = prefix
-        # TODO: can constants be accessed from ONC?
-        self.caldb_store = pathlib.Path(self._get("calCat.caldbStore"))
+
+        if not secrets_fn.is_file():
+            self.device.log.WARN(f"Missing CalCat secrets file (expected {secrets_fn})")
+        with secrets_fn.open("r") as fd:
+            calcat_secrets = json.load(fd)
+
+        self.caldb_store = pathlib.Path(calcat_secrets["caldb_store_path"])
         if not self.caldb_store.is_dir():
             raise ValueError(f"caldb_store location '{self.caldb_store}' is not dir")
 
-        # TODO: secret / token management
-        base_url = self._get("calCat.baseUrl")
+        self.device.log.INFO(f"Connecting to CalCat at {calcat_secrets['base_url']}")
+        base_url = calcat_secrets["base_url"]
         self.client = calibration_client.CalibrationClient(
-            client_id=self._get("calCat.clientId"),
-            client_secret=self._get("calCat.clientSecret"),
-            user_email=self._get("calCat.userEmail"),
+            client_id=calcat_secrets["client_id"],
+            client_secret=calcat_secrets["client_secret"],
+            user_email=calcat_secrets["user_email"],
             base_api_url=f"{base_url}/api/",
             token_url=f"{base_url}/oauth/token",
             refresh_url=f"{base_url}/oauth/token",
@@ -196,7 +177,7 @@ class BaseCalcatFriend:
             scope="public",
             session_token=None,
         )
-        self.device.log.INFO("calibration_client initialized")
+        self.device.log.INFO("CalCat connection established")
 
     def _get(self, key):
         """Helper to get value from attached device schema"""
diff --git a/src/tests/test_calcat_utils.py b/src/tests/test_calcat_utils.py
index 36f2b322eafc1dadd2ac6c8c5e2e8ff02c22fab9..c0c69fbe6191c730b6aa633ebae61670b6d69ee9 100644
--- a/src/tests/test_calcat_utils.py
+++ b/src/tests/test_calcat_utils.py
@@ -6,8 +6,7 @@ from karabo.bound import Hash, Schema
 
 # TODO: secrets management
 _test_dir = pathlib.Path(__file__).absolute().parent
-with (_test_dir / "calibration-client-secrets.txt").open("r") as fd:
-    base_url, client_id, client_secret, user_email = fd.read().splitlines()
+_test_calcat_secrets_fn = _test_dir / "calibration-client-secrets.json"
 
 
 class DummyLogger:
@@ -46,7 +45,7 @@ class DummyAgipdDevice:
         self.schema = config
         self.calibration_constant_manager = calcat_utils.AgipdCalcatFriend(
             self,
-            "constantParameters",
+            _test_calcat_secrets_fn,
         )
 
     def get(self, key):
@@ -62,7 +61,7 @@ class DummyDsscDevice:
     @staticmethod
     def expectedParameters(expected):
         # super(DummyDsscDevice, DummyDsscDevice).expectedParameters(expected)
-        calcat_utils.DsscCalcatFriend.add_schema(expected, "constantParameters")
+        calcat_utils.DsscCalcatFriend.add_schema(expected)
 
     def __init__(self, config):
         self.log = DummyLogger()
@@ -70,7 +69,7 @@ class DummyDsscDevice:
         self.schema = config
         self.calibration_constant_manager = calcat_utils.DsscCalcatFriend(
             self,
-            "constantParameters",
+            _test_calcat_secrets_fn,
         )
 
     def get(self, key):
@@ -83,12 +82,6 @@ DummyDsscDevice.expectedParameters(DummyDsscDevice.device_class_schema)
 def test_agipd_constants_and_caching_and_async():
     # def test_agipd_constants():
     conf = Hash()
-    conf["constantParameters.calCat.baseUrl"] = base_url
-    conf["constantParameters.calCat.clientId"] = client_id
-    conf["constantParameters.calCat.clientSecret"] = client_secret
-    conf["constantParameters.calCat.userEmail"] = user_email
-    conf["constantParameters.calCat.caldbStore"] = "/gpfs/exfel/d/cal/caldb_store"
-
     conf["constantParameters.detectorType"] = "AGIPD-Type"
     conf["constantParameters.detectorName"] = "SPB_DET_AGIPD1M-1"
     conf["constantParameters.karaboDa"] = "AGIPD00"
@@ -145,13 +138,6 @@ def test_agipd_constants_and_caching_and_async():
 
 def test_dssc_constants():
     conf = Hash()
-
-    conf["constantParameters.calCat.baseUrl"] = base_url
-    conf["constantParameters.calCat.clientId"] = client_id
-    conf["constantParameters.calCat.clientSecret"] = client_secret
-    conf["constantParameters.calCat.userEmail"] = user_email
-    conf["constantParameters.calCat.caldbStore"] = "/gpfs/exfel/d/cal/caldb_store"
-
     conf["constantParameters.detectorType"] = "DSSC-Type"
     conf["constantParameters.detectorName"] = "SCS_DET_DSSC1M-1"
     conf["constantParameters.karaboDa"] = "DSSC00"