diff --git a/src/_zkapauthorizer/_plugin.py b/src/_zkapauthorizer/_plugin.py
index 4ba84ddd840e6a56e7cf54fd0ed6ae8bc3449f40..a1935a562ad674d8895951d19d2881977357c192 100644
--- a/src/_zkapauthorizer/_plugin.py
+++ b/src/_zkapauthorizer/_plugin.py
@@ -175,12 +175,24 @@ class ZKAPAuthorizer(object):
         )
 
 
-    def get_client_resource(self, node_config):
+    def get_client_resource(self, node_config, default_token_count=None):
+        """
+        Get an ``IZKAPRoot`` for the given node configuration.
+
+        :param allmydata.node._Config node_config: The configuration object
+            for the relevant node.
+
+        :param int default_token_count: Configure the payment controller with
+            a default number of tokens to request during voucher redemption.
+            This is only used if a number of tokens isn't specified at the
+            point of redemption.
+        """
         from twisted.internet import reactor
         return resource_from_configuration(
             node_config,
             store=self._get_store(node_config),
             redeemer=self._get_redeemer(node_config, None, reactor),
+            default_token_count=default_token_count,
         )
 
 
diff --git a/src/_zkapauthorizer/controller.py b/src/_zkapauthorizer/controller.py
index bee2bf301eca18189507fbb0394f43627da16560..720366e1c64738bae7770696431a5f2291c4ce19 100644
--- a/src/_zkapauthorizer/controller.py
+++ b/src/_zkapauthorizer/controller.py
@@ -90,9 +90,6 @@ from .model import (
     Error as model_Error,
 )
 
-# The number of tokens to submit with a voucher redemption.
-NUM_TOKENS = 512000
-
 RETRY_INTERVAL = timedelta(minutes=3)
 
 class AlreadySpent(Exception):
@@ -535,6 +532,10 @@ class PaymentController(object):
          the voucher.  The data store marks the voucher as redeemed and stores
          the unblinded tokens for use by the storage client.
 
+    :ivar int default_token_count: The number of tokens to request when
+        redeeming a voucher, if no other count is given when the redemption is
+        started.
+
     :ivar dict[unicode, datetime] _active: A mapping from voucher identifiers
         which currently have redemption attempts in progress to timestamps
         when the attempt began.
@@ -551,6 +552,7 @@ class PaymentController(object):
 
     store = attr.ib()
     redeemer = attr.ib()
+    default_token_count = attr.ib()
 
     _clock = attr.ib(
         default=attr.Factory(partial(namedAny, "twisted.internet.reactor")),
@@ -664,7 +666,7 @@ class PaymentController(object):
         # number of passes that can be constructed is still only the size of
         # the set of random tokens.
         if num_tokens is None:
-            num_tokens = NUM_TOKENS
+            num_tokens = self.default_token_count
         tokens = self._get_random_tokens_for_voucher(voucher, num_tokens)
         return self._perform_redeem(voucher, tokens)
 
diff --git a/src/_zkapauthorizer/resource.py b/src/_zkapauthorizer/resource.py
index 8841479122b40287f0086c0b2ba426cd76063742..79c8d3146e82c3d42b5c5a9f404ee8496eaafd85 100644
--- a/src/_zkapauthorizer/resource.py
+++ b/src/_zkapauthorizer/resource.py
@@ -58,6 +58,10 @@ from .controller import (
     get_redeemer,
 )
 
+# The number of tokens to submit with a voucher redemption.
+NUM_TOKENS = 512000
+
+
 class IZKAPRoot(IResource):
     """
     The root of the resource tree of this plugin's client web presence.
@@ -66,7 +70,7 @@ class IZKAPRoot(IResource):
     controller = Attribute("The ``PaymentController`` used by this resource tree.")
 
 
-def from_configuration(node_config, store, redeemer=None):
+def from_configuration(node_config, store, redeemer=None, default_token_count=None):
     """
     Instantiate the plugin root resource using data from its configuration
     section, **storageclient.plugins.privatestorageio-zkapauthz-v1**, in the
@@ -93,7 +97,9 @@ def from_configuration(node_config, store, redeemer=None):
             None,
             None,
         )
-    controller = PaymentController(store, redeemer)
+    if default_token_count is None:
+        default_token_count = NUM_TOKENS
+    controller = PaymentController(store, redeemer, default_token_count)
     root = Resource()
     root.store = store
     root.controller = controller
diff --git a/src/_zkapauthorizer/tests/test_client_resource.py b/src/_zkapauthorizer/tests/test_client_resource.py
index 402ea78c41cc0cbd449858c7895f50295d5248f1..f901d020fa1e23d9b26c141369996560d00a0d44 100644
--- a/src/_zkapauthorizer/tests/test_client_resource.py
+++ b/src/_zkapauthorizer/tests/test_client_resource.py
@@ -130,9 +130,6 @@ from ..model import (
     VoucherStore,
     memory_connect,
 )
-from ..controller import (
-    NUM_TOKENS,
-)
 from ..resource import (
     from_configuration,
 )
@@ -156,6 +153,9 @@ from .matchers import (
     Provides,
 )
 
+# A small number of tokens to work with in the tests.
+NUM_TOKENS = 10
+
 TRANSIENT_ERROR = u"something went wrong, who knows what"
 
 # Helper to work-around https://github.com/twisted/treq/issues/161
@@ -251,6 +251,7 @@ def root_from_config(config, now):
             now,
             memory_connect,
         ),
+        default_token_count=NUM_TOKENS,
     )
 
 
@@ -911,10 +912,7 @@ class VoucherTests(TestCase):
                         created=now,
                         state=Redeemed(
                             finished=now,
-                            # Value duplicated from
-                            # PaymentController.redeem
-                            # default.  Should do this better.
-                            token_count=100,
+                            token_count=NUM_TOKENS,
                         ),
                     ).marshal()
                     for voucher
diff --git a/src/_zkapauthorizer/tests/test_controller.py b/src/_zkapauthorizer/tests/test_controller.py
index dec0176991e75bc905b725fd6a08ba165c4b1d3c..0dd8bdd7d982f14eea28abfbe27adb34cc81bd7a 100644
--- a/src/_zkapauthorizer/tests/test_controller.py
+++ b/src/_zkapauthorizer/tests/test_controller.py
@@ -139,6 +139,7 @@ class PaymentControllerTests(TestCase):
         controller = PaymentController(
             store,
             NonRedeemer(),
+            default_token_count=10,
         )
         controller.redeem(voucher)
 
@@ -157,6 +158,7 @@ class PaymentControllerTests(TestCase):
         controller = PaymentController(
             store,
             DummyRedeemer(),
+            default_token_count=10,
         )
         controller.redeem(voucher)
 
@@ -165,7 +167,7 @@ class PaymentControllerTests(TestCase):
             persisted_voucher.state,
             Equals(model_Redeemed(
                 finished=now,
-                token_count=100,
+                token_count=10,
             )),
         )
 
@@ -179,6 +181,7 @@ class PaymentControllerTests(TestCase):
         controller = PaymentController(
             store,
             DoubleSpendRedeemer(),
+            default_token_count=10,
         )
         controller.redeem(voucher)
 
@@ -204,6 +207,7 @@ class PaymentControllerTests(TestCase):
         unpaid_controller = PaymentController(
             store,
             UnpaidRedeemer(),
+            default_token_count=10,
         )
         unpaid_controller.redeem(voucher)
 
@@ -219,6 +223,7 @@ class PaymentControllerTests(TestCase):
         success_controller = PaymentController(
             store,
             DummyRedeemer(),
+            default_token_count=10,
         )
 
         self.assertThat(
@@ -247,7 +252,8 @@ class PaymentControllerTests(TestCase):
         controller = PaymentController(
             store,
             UnpaidRedeemer(),
-            clock,
+            default_token_count=10,
+            clock=clock,
         )
         controller.redeem(voucher)
         # It fails this time.
diff --git a/src/_zkapauthorizer/tests/test_plugin.py b/src/_zkapauthorizer/tests/test_plugin.py
index e9c8eecc3ce3290ca8bafb2f286b600442f47c6a..41c2dd43f4b3bd240ec9bbf852fe19e93c328de0 100644
--- a/src/_zkapauthorizer/tests/test_plugin.py
+++ b/src/_zkapauthorizer/tests/test_plugin.py
@@ -461,7 +461,7 @@ class ClientResourceTests(TestCase):
         nodedir = tempdir.join(b"node")
         config = get_config(nodedir, b"tub.port")
         self.assertThat(
-            storage_server.get_client_resource(config),
+            storage_server.get_client_resource(config, default_token_count=10),
             Provides([IResource]),
         )