View Issue Details

IDProjectCategoryView StatusLast Update
0006067Talerexchange API (HTTP specification)public2020-03-31 16:04
ReporterChristian GrothoffAssigned ToFlorian Dold 
PriorityurgentSeverityfeatureReproducibilityN/A
Status closedResolutionfixed 
Platformi7OSDebian GNU/LinuxOS Versionsqueeze
Product Versiongit (master) 
Target Version0.7.0Fixed in Version 
Summary0006067: rest-ify api more
DescriptionSpecifically, coins (from withdraw) and reserves (via wire or tight integration) and wire transfers should come with a base URI, i.e. exchange/coins/$COIN_PUB/ and exchange/reserves/$RESERVE_PUB/ and exchange/transfer/$TID/ behind which the associated operations are done. The origin does NOT have to be the same as that of the /keys or /wire responses. This will enable sharing and CBDC deployments where a commercial bank may proxy the withdraw operation.
TagsNo tags attached.

Activities

Florian Dold

2020-01-28 15:34

manager   ~0015301

After having agreed on this new API, we should proceed incrementally, just like I did for the last pybank API change: First implement the new API, keep the old API and test cases for it. Then also add test cases for the new API (i.e. just copy and adjust the old ones), and then when the merchant, wallet etc. all understand the new exchange API, we can delete the old one.

This will allow stuff that consumes the exchange API to be updated more easily, as for example in the wallet I can migrate all endpoints one-by-one.

I don't want to be in a state again where nothing works!

Christian Grothoff

2020-02-05 21:16

manager   ~0015310

Specification done, waiting for feedback.

Christian Grothoff

2020-02-26 17:31

manager   ~0015411

Phase 1 diff: implement new protocol in exchange httpd (client library not updated). Also, various functions and files should probably be renamed.

phase1.diff (97,840 bytes)
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c
index 6f021d72..288b4578 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014, 2015, 2016, 2019 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free Software
@@ -147,6 +147,92 @@ static unsigned long long req_count;
 static unsigned long long req_max;
 
 
+/**
+ * Handle a "/coins/$COIN_PUB/$OP" POST request.  Parses the "coin_pub"
+ * EdDSA key of the coin and demultiplexes based on $OP.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param root uploaded JSON data
+ * @param args array of additional options (first must be the
+ *         reserve public key, the second one should be "withdraw")
+ * @return MHD result code
+ */
+static int
+handle_post_coins (const struct TEH_RequestHandler *rh,
+                   struct MHD_Connection *connection,
+                   const json_t *root,
+                   const char *const args[2])
+{
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+  static const struct
+  {
+    /**
+     * Name of the operation (args[1])
+     */
+    const char *op;
+
+    /**
+     * Function to call to perform the operation.
+     *
+     * @param connection the MHD connection to handle
+     * @param coin_pub the public key of the coin
+     * @param root uploaded JSON data
+     * @return MHD result code
+     *///
+    int
+    (*handler)(struct MHD_Connection *connection,
+               const struct TALER_CoinSpendPublicKeyP *coin_pub,
+               const json_t *root);
+  } h[] = {
+    {
+      .op = "deposit",
+      .handler = &TEH_DEPOSIT_handler_deposit
+    },
+    {
+      .op = "melt",
+      .handler = &TEH_REFRESH_handler_melt
+    },
+    {
+      .op = "recoup",
+      .handler = &TEH_RECOUP_handler_recoup
+    },
+    {
+      .op = "refund",
+      .handler = &TEH_REFUND_handler_refund
+    },
+    {
+      .op = NULL,
+      .handler = NULL
+    },
+  };
+
+  (void) rh;
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &coin_pub,
+                                     sizeof (coin_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_COINS_INVALID_COIN_PUB,
+                                       "coin public key malformed");
+  }
+  for (unsigned int i = 0; NULL != h[i].op; i++)
+    if (0 == strcmp (h[i].op,
+                     args[1]))
+      return h[i].handler (connection,
+                           &coin_pub,
+                           root);
+  return TALER_MHD_reply_with_error (connection,
+                                     MHD_HTTP_NOT_FOUND,
+                                     TALER_EC_OPERATION_INVALID,
+                                     "requested operation on coin unknown");
+}
+
+
 /**
  * Function called whenever MHD is done with a request.  If the
  * request was a POST, we may have stored a `struct Buffer *` in the
@@ -205,6 +291,120 @@ is_valid_correlation_id (const char *correlation_id)
 }
 
 
+/**
+ * We found @a rh responsible for handling a request. Parse the
+ * @a upload_data (if applicable) and the @a url and call the
+ * handler.
+ *
+ * @param rh request handler to call
+ * @param connection connection being handled
+ * @param url rest of the URL to parse
+ * @param inner_cls closure for the handler, if needed
+ * @param upload_data upload data to parse (if available)
+ * @param upload_data_size[in,out] number of bytes in @a upload_data
+ * @return MHD result code
+ */
+static int
+proceed_with_handler (const struct TEH_RequestHandler *rh,
+                      struct MHD_Connection *connection,
+                      const char *url,
+                      void **inner_cls,
+                      const char *upload_data,
+                      size_t *upload_data_size)
+{
+  const char *args[rh->nargs + 1];
+  size_t ulen = strlen (url) + 1;
+  json_t *root;
+  int ret;
+
+  /* We do check for "ulen" here, because we'll later stack-allocate a buffer
+     of that size and don't want to enable malicious clients to cause us
+     huge stack allocations. */
+  if (ulen > 512)
+  {
+    /* 512 is simply "big enough", as it is bigger than "6 * 54",
+       which is the longest URL format we ever get (for
+       /deposits/).  The value should be adjusted if we ever define protocol
+       endpoints with plausibly longer inputs.  */
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_URI_TOO_LONG,
+                                       TALER_EC_URI_TOO_LONG,
+                                       "The URI given is too long");
+  }
+
+  /* All POST endpoints come with a body in JSON format. So we parse
+     the JSON here. */
+  if (0 == strcasecmp (rh->method,
+                       MHD_HTTP_METHOD_POST))
+  {
+    int res;
+
+    res = TALER_MHD_parse_post_json (connection,
+                                     inner_cls,
+                                     upload_data,
+                                     upload_data_size,
+                                     &root);
+    if (GNUNET_SYSERR == res)
+      return MHD_NO;
+    if ( (GNUNET_NO == res) || (NULL == root) )
+      return MHD_YES;
+  }
+
+  {
+    char d[ulen];
+
+    /* Parse command-line arguments, if applicable */
+    if (rh->nargs > 0)
+    {
+      unsigned int i;
+
+      /* make a copy of 'url' because 'strtok()' will modify */
+      memcpy (d,
+              url,
+              ulen);
+      i = 0;
+      args[i++] = strtok (d, "/");
+      while ( (NULL != args[i - 1]) &&
+              (i < rh->nargs) )
+        args[i++] = strtok (NULL, "/");
+      /* make sure above loop ran nicely until completion, and also
+         that there is no excess data in 'd' afterwards */
+      if ( (i != rh->nargs) ||
+           (NULL == args[i - 1]) ||
+           (NULL != strtok (NULL, "/")) )
+      {
+        GNUNET_break_op (0);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_NOT_FOUND,
+                                           TALER_EC_WRONG_NUMBER_OF_SEGMENTS,
+                                           "Number of segments does not match");
+      }
+    }
+
+    /* just to be safe(r), we always terminate the array with a NULL
+       (which handlers should not read, but at least if they do, they'll
+       crash pretty reliably... */
+    args[rh->nargs] = NULL;
+
+    /* Above logic ensures that 'root' is exactly non-NULL for POST operations */
+    if (NULL != root)
+      ret = rh->handler.post (rh,
+                              connection,
+                              root,
+                              args);
+    else /* and we only have "POST" or "GET" in the API for at this point
+            (OPTIONS/HEAD are taken care of earlier) */
+      ret = rh->handler.get (rh,
+                             connection,
+                             args);
+  }
+  if (NULL != root)
+    json_decref (root);
+  return ret;
+}
+
+
 /**
  * Handle incoming HTTP request.
  *
@@ -229,134 +429,111 @@ handle_mhd_request (void *cls,
                     void **con_cls)
 {
   static struct TEH_RequestHandler handlers[] = {
-    /* Landing page, tell humans to go away. */
-    { "/", MHD_HTTP_METHOD_GET, "text/plain",
-      "Hello, I'm the Taler exchange. This HTTP server is not for humans.\n", 0,
-      &TEH_MHD_handler_static_response, MHD_HTTP_OK },
     /* /robots.txt: disallow everything */
-    { "/robots.txt", MHD_HTTP_METHOD_GET, "text/plain",
-      "User-agent: *\nDisallow: /\n", 0,
-      &TEH_MHD_handler_static_response, MHD_HTTP_OK },
+    {
+      .url = "robots.txt",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_MHD_handler_static_response,
+      .mime_type = "text/plain",
+      .data = "User-agent: *\nDisallow: /\n",
+      .response_code = MHD_HTTP_OK
+    },
+    /* Landing page, tell humans to go away. */
+    {
+      .url = "",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = TEH_MHD_handler_static_response,
+      .mime_type = "text/plain",
+      .data =
+        "Hello, I'm the Taler exchange. This HTTP server is not for humans.\n",
+      .response_code = MHD_HTTP_OK
+    },
     /* AGPL licensing page, redirect to source. As per the AGPL-license,
        every deployment is required to offer the user a download of the
        source. We make this easy by including a redirect to the source
        here. */
-    { "/agpl", MHD_HTTP_METHOD_GET, "text/plain",
-      NULL, 0,
-      &TEH_MHD_handler_agpl_redirect, MHD_HTTP_FOUND },
+    {
+      .url = "agpl",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_MHD_handler_agpl_redirect
+    },
     /* Terms of service */
-    { "/terms", MHD_HTTP_METHOD_GET, NULL,
-      NULL, 0,
-      &TEH_handler_terms, MHD_HTTP_OK },
+    {
+      .url = "terms",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_terms
+    },
     /* Privacy policy */
-    { "/privacy", MHD_HTTP_METHOD_GET, NULL,
-      NULL, 0,
-      &TEH_handler_privacy, MHD_HTTP_OK },
+    {
+      .url = "privacy",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_privacy
+    },
     /* Return key material and fundamental properties for this exchange */
-    { "/keys", MHD_HTTP_METHOD_GET, "application/json",
-      NULL, 0,
-      &TEH_KS_handler_keys, MHD_HTTP_OK },
-    { "/keys", NULL, "text/plain",
-      "Only GET is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
+    {
+      .url = "/keys",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_KS_handler_keys,
+    },
     /* Requests for wiring information */
-    { "/wire", MHD_HTTP_METHOD_GET, "application/json",
-      NULL, 0,
-      &TEH_WIRE_handler_wire, MHD_HTTP_OK },
-    { "/wire", NULL, "text/plain",
-      "Only GET is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
+    {
+      .url = "wire",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_WIRE_handler_wire
+    },
     /* Withdrawing coins / interaction with reserves */
-    { "/reserve/status", MHD_HTTP_METHOD_GET, "application/json",
-      NULL, 0,
-      &TEH_RESERVE_handler_reserve_status, MHD_HTTP_OK },
-    { "/reserve/status", NULL, "text/plain",
-      "Only GET is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    { "/reserve/withdraw", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &TEH_RESERVE_handler_reserve_withdraw, MHD_HTTP_OK },
-    { "/reserve/withdraw", NULL, "text/plain",
-      "Only POST is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    /* Depositing coins */
-    { "/deposit", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &TEH_DEPOSIT_handler_deposit, MHD_HTTP_OK },
-    { "/deposit", NULL, "text/plain",
-      "Only POST is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    /* Refunding coins */
-    { "/refund", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &TEH_REFUND_handler_refund, MHD_HTTP_OK },
-    { "/refund", NULL, "text/plain",
-      "Only POST is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    /* Dealing with change */
-    { "/refresh/melt", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &TEH_REFRESH_handler_refresh_melt, MHD_HTTP_OK },
-    { "/refresh/melt", NULL, "text/plain",
-      "Only POST is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    { "/refresh/reveal", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &TEH_REFRESH_handler_refresh_reveal, MHD_HTTP_OK },
-    { "/refresh/reveal", NULL, "text/plain",
-      "Only POST is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    { "/refresh/reveal", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &TEH_REFRESH_handler_refresh_reveal, MHD_HTTP_OK },
-    { "/refresh/reveal", NULL, "text/plain",
-      "Only POST is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    { "/refresh/link", MHD_HTTP_METHOD_GET, "application/json",
-      NULL, 0,
-      &TEH_REFRESH_handler_refresh_link, MHD_HTTP_OK },
-    { "/refresh/link", NULL, "text/plain",
-      "Only GET is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    { "/track/transfer", MHD_HTTP_METHOD_GET, "application/json",
-      NULL, 0,
-      &TEH_TRACKING_handler_track_transfer, MHD_HTTP_OK },
-    { "/track/transfer", NULL, "text/plain",
-      "Only GET is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-    { "/track/transaction", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &TEH_TRACKING_handler_track_transaction, MHD_HTTP_OK },
-    { "/track/transaction", NULL, "text/plain",
-      "Only POST is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    { "/recoup", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &TEH_RECOUP_handler_recoup, MHD_HTTP_OK },
-    { "/refresh/link", NULL, "text/plain",
-      "Only GET is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    { NULL, NULL, NULL, NULL, 0, NULL, 0 }
-  };
-  static struct TEH_RequestHandler h404 = {
-    "", NULL, "text/html",
-    "<html><title>404: not found</title></html>", 0,
-    &TEH_MHD_handler_static_response, MHD_HTTP_NOT_FOUND
+    {
+      .url = "reserves",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_RESERVE_handler_reserve_status,
+      .nargs = 1
+    },
+    {
+      .url = "reserves",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &TEH_RESERVE_handler_reserve_withdraw,
+      .nargs = 2
+    },
+    /* coins */
+    {
+      .url = "coins",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &handle_post_coins,
+      .nargs = 2
+    },
+    {
+      .url = "coins",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = TEH_REFRESH_handler_link,
+      .nargs = 2,
+    },
+    /* refreshing */
+    {
+      .url = "refreshes",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &TEH_REFRESH_handler_reveal,
+      .nargs = 2
+    },
+    /* tracking transfers */
+    {
+      .url = "transfers",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_TRACKING_handler_track_transfer,
+      .nargs = 1
+    },
+    /* tracking deposits */
+    {
+      .url = "deposits",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_TRACKING_handler_track_transaction,
+      .nargs = 4
+    },
+    /* mark end of list */
+    {
+      .url = NULL
+    }
   };
   struct ExchangeHttpRequestClosure *ecls = *con_cls;
-  int ret;
   void **inner_cls;
   struct GNUNET_AsyncScopeSave old_scope;
   const char *correlation_id = NULL;
@@ -367,7 +544,8 @@ handle_mhd_request (void *cls,
   {
     unsigned long long cnt;
 
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Handling new request\n");
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Handling new request\n");
     cnt = __sync_add_and_fetch (&req_count, 1LLU);
     if (req_max == cnt)
     {
@@ -395,7 +573,8 @@ handle_mhd_request (void *cls,
   }
 
   inner_cls = &ecls->opaque_post_parsing_context;
-  GNUNET_async_scope_enter (&ecls->async_scope_id, &old_scope);
+  GNUNET_async_scope_enter (&ecls->async_scope_id,
+                            &old_scope);
   if (NULL != correlation_id)
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Handling request (%s) for URL '%s', correlation_id=%s\n",
@@ -410,55 +589,100 @@ handle_mhd_request (void *cls,
   /* on repeated requests, check our cache first */
   if (NULL != ecls->rh)
   {
-    ret = ecls->rh->handler (ecls->rh,
-                             connection,
-                             inner_cls,
-                             upload_data,
-                             upload_data_size);
+    int ret;
+
+    ret = proceed_with_handler (ecls->rh,
+                                connection,
+                                url,
+                                inner_cls,
+                                upload_data,
+                                upload_data_size);
     GNUNET_async_scope_restore (&old_scope);
     return ret;
   }
+
   if (0 == strcasecmp (method,
                        MHD_HTTP_METHOD_HEAD))
-    method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */
-  for (unsigned int i = 0; NULL != handlers[i].url; i++)
-  {
-    struct TEH_RequestHandler *rh = &handlers[i];
-
-    if (0 != strcmp (url, rh->url))
-      continue;
+    method = MHD_HTTP_METHOD_GET;   /* treat HEAD as GET here, MHD will do the rest */
 
-    /* The URL is a match!  What we now do depends on the method. */
-    if (0 == strcasecmp (method, MHD_HTTP_METHOD_OPTIONS))
+  /* parse first part of URL */
+  {
+    int found = GNUNET_NO;
+    size_t tok_size;
+    const char *tok;
+    const char *rest;
+
+    if ('\0' == url[0])
+      /* strange, should start with '/', treat as just "/" */
+      url = "/";
+    tok = url + 1;
+    rest = strchr (tok, '/');
+    if (NULL == rest)
+    {
+      tok_size = 0;
+    }
+    else
+    {
+      tok_size = rest - tok;
+      rest++; /* skip over '/' */
+    }
+    for (unsigned int i = 0; NULL != handlers[i].url; i++)
     {
-      GNUNET_async_scope_restore (&old_scope);
-      return TALER_MHD_reply_cors_preflight (connection);
+      struct TEH_RequestHandler *rh = &handlers[i];
+
+      if (0 != strncmp (tok,
+                        rh->url,
+                        tok_size))
+        continue;
+      found = GNUNET_YES;
+      /* The URL is a match!  What we now do depends on the method. */
+      if (0 == strcasecmp (method, MHD_HTTP_METHOD_OPTIONS))
+      {
+        GNUNET_async_scope_restore (&old_scope);
+        return TALER_MHD_reply_cors_preflight (connection);
+      }
+      GNUNET_assert (NULL != rh->method);
+      if (0 == strcasecmp (method,
+                           rh->method))
+      {
+        int ret;
+
+        /* cache to avoid the loop next time */
+        ecls->rh = rh;
+        /* run handler */
+        ret = proceed_with_handler (rh,
+                                    connection,
+                                    url,
+                                    inner_cls,
+                                    upload_data,
+                                    upload_data_size);
+        GNUNET_async_scope_restore (&old_scope);
+        return ret;
+      }
     }
 
-    if ( (NULL == rh->method) ||
-         (0 == strcasecmp (method,
-                           rh->method)) )
+    if (GNUNET_YES == found)
     {
-      /* cache to avoid the loop next time */
-      ecls->rh = rh;
-      /* run handler */
-      ret = rh->handler (rh,
-                         connection,
-                         inner_cls,
-                         upload_data,
-                         upload_data_size);
-      GNUNET_async_scope_restore (&old_scope);
-      return ret;
+      /* we found a matching address, but the method is wrong */
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_METHOD_NOT_ALLOWED,
+                                         TALER_EC_METHOD_INVALID,
+                                         "The HTTP method used is invalid for this URL");
     }
   }
+
   /* No handler matches, generate not found */
-  ret = TEH_MHD_handler_static_response (&h404,
-                                         connection,
-                                         inner_cls,
-                                         upload_data,
-                                         upload_data_size);
-  GNUNET_async_scope_restore (&old_scope);
-  return ret;
+  {
+    int ret;
+
+    ret = TALER_MHD_reply_with_error (connection,
+                                      MHD_HTTP_NOT_FOUND,
+                                      TALER_EC_ENDPOINT_UNKNOWN,
+                                      "No handler found for the given URL");
+    GNUNET_async_scope_restore (&old_scope);
+    return ret;
+  }
 }
 
 
diff --git a/src/exchange/taler-exchange-httpd.h b/src/exchange/taler-exchange-httpd.h
index 38c611c6..8489d179 100644
--- a/src/exchange/taler-exchange-httpd.h
+++ b/src/exchange/taler-exchange-httpd.h
@@ -24,6 +24,8 @@
 #define TALER_EXCHANGE_HTTPD_H
 
 #include <microhttpd.h>
+#include "taler_json_lib.h"
+#include "taler_crypto_lib.h"
 
 
 /**
@@ -65,51 +67,77 @@ struct TEH_RequestHandler
 {
 
   /**
-   * URL the handler is for.
+   * URL the handler is for (first part only).
    */
   const char *url;
 
   /**
-   * Method the handler is for, NULL for "all".
+   * Method the handler is for.
    */
   const char *method;
 
+  /**
+   * Callbacks for handling of the request. Which one is used
+   * depends on @e method.
+   */
+  union
+  {
+    /**
+     * Function to call to handle a GET requests (and those
+     * with @e method NULL).
+     *
+     * @param rh this struct
+     * @param mime_type the @e mime_type for the reply (hint, can be NULL)
+     * @param connection the MHD connection to handle
+     * @param args array of arguments, needs to be of length @e args_expected
+     * @return MHD result code
+     */
+    int (*get)(const struct TEH_RequestHandler *rh,
+               struct MHD_Connection *connection,
+               const char *const args[]);
+
+
+    /**
+     * Function to call to handle a POST request.
+     *
+     * @param rh this struct
+     * @param mime_type the @e mime_type for the reply (hint, can be NULL)
+     * @param connection the MHD connection to handle
+     * @param json uploaded JSON data
+     * @param args array of arguments, needs to be of length @e args_expected
+     * @return MHD result code
+     */
+    int (*post)(const struct TEH_RequestHandler *rh,
+                struct MHD_Connection *connection,
+                const json_t *root,
+                const char *const args[]);
+
+  } handler;
+
+  /**
+   * Number of arguments this handler expects in the @a args array.
+   */
+  unsigned int nargs;
+
   /**
    * Mime type to use in reply (hint, can be NULL).
    */
   const char *mime_type;
 
   /**
-   * Raw data for the @e handler
+   * Raw data for the @e handler, can be NULL for none provided.
    */
   const void *data;
 
   /**
-   * Number of bytes in @e data, 0 for 0-terminated.
+   * Number of bytes in @e data, 0 for data is 0-terminated (!).
    */
   size_t data_size;
 
   /**
-   * Function to call to handle the request.
-   *
-   * @param rh this struct
-   * @param mime_type the @e mime_type for the reply (hint, can be NULL)
-   * @param connection the MHD connection to handle
-   * @param[in,out] connection_cls the connection's closure (can be updated)
-   * @param upload_data upload data
-   * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
-   * @return MHD result code
-   */
-  int (*handler)(struct TEH_RequestHandler *rh,
-                 struct MHD_Connection *connection,
-                 void **connection_cls,
-                 const char *upload_data,
-                 size_t *upload_data_size);
-
-  /**
-   * Default response code.
+   * Default response code. 0 for none provided.
    */
-  int response_code;
+  unsigned int response_code;
 };
 
 
diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c
index 49b9cc2f..da89ff47 100644
--- a/src/exchange/taler-exchange-httpd_deposit.c
+++ b/src/exchange/taler-exchange-httpd_deposit.c
@@ -381,27 +381,22 @@ check_timestamp_current (struct GNUNET_TIME_Absolute ts)
 
 
 /**
- * Handle a "/deposit" request.  Parses the JSON, and, if successful,
- * passes the JSON data to #verify_and_execute_deposit() to further
- * check the details of the operation specified.  If everything checks
+ * Handle a "/coins/$COIN_PUB/deposit" request.  Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_deposit() to
+ * further check the details of the operation specified.  If everything checks
  * out, this will ultimately lead to the "/deposit" being executed, or
  * rejected.
  *
- * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
  * @return MHD result code
   */
 int
-TEH_DEPOSIT_handler_deposit (struct TEH_RequestHandler *rh,
-                             struct MHD_Connection *connection,
-                             void **connection_cls,
-                             const char *upload_data,
-                             size_t *upload_data_size)
+TEH_DEPOSIT_handler_deposit (struct MHD_Connection *connection,
+                             const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                             const json_t *root)
 {
-  json_t *json;
   int res;
   json_t *wire;
   enum TALER_ErrorCode ec;
@@ -415,7 +410,6 @@ TEH_DEPOSIT_handler_deposit (struct TEH_RequestHandler *rh,
     GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
                                  &deposit.coin.denom_pub_hash),
     TALER_JSON_spec_denomination_signature ("ub_sig", &deposit.coin.denom_sig),
-    GNUNET_JSON_spec_fixed_auto ("coin_pub", &deposit.coin.coin_pub),
     GNUNET_JSON_spec_fixed_auto ("merchant_pub", &deposit.merchant_pub),
     GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &deposit.h_contract_terms),
     GNUNET_JSON_spec_fixed_auto ("h_wire", &deposit.h_wire),
@@ -428,27 +422,13 @@ TEH_DEPOSIT_handler_deposit (struct TEH_RequestHandler *rh,
     GNUNET_JSON_spec_end ()
   };
 
-  (void) rh;
-  res = TALER_MHD_parse_post_json (connection,
-                                   connection_cls,
-                                   upload_data,
-                                   upload_data_size,
-                                   &json);
-  if (GNUNET_SYSERR == res)
-  {
-    GNUNET_break (0);
-    return MHD_NO;
-  }
-  if ( (GNUNET_NO == res) ||
-       (NULL == json) )
-    return MHD_YES;
   memset (&deposit,
           0,
           sizeof (deposit));
+  deposit.coin.coin_pub = *coin_pub;
   res = TALER_MHD_parse_json_data (connection,
-                                   json,
+                                   root,
                                    spec);
-  json_decref (json);
   if (GNUNET_SYSERR == res)
   {
     GNUNET_break (0);
diff --git a/src/exchange/taler-exchange-httpd_deposit.h b/src/exchange/taler-exchange-httpd_deposit.h
index ed1f87d5..23c46c28 100644
--- a/src/exchange/taler-exchange-httpd_deposit.h
+++ b/src/exchange/taler-exchange-httpd_deposit.h
@@ -29,22 +29,21 @@
 
 
 /**
- * Handle a "/deposit" request.  Parses the JSON, and, if successful,
- * checks the signatures.  If everything checks out, this will
- * ultimately lead to the "/deposit" being executed, or rejected.
+ * Handle a "/coins/$COIN_PUB/deposit" request.  Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_deposit() to
+ * further check the details of the operation specified.  If everything checks
+ * out, this will ultimately lead to the "/deposit" being executed, or
+ * rejected.
  *
- * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
  * @return MHD result code
   */
 int
-TEH_DEPOSIT_handler_deposit (struct TEH_RequestHandler *rh,
-                             struct MHD_Connection *connection,
-                             void **connection_cls,
-                             const char *upload_data,
-                             size_t *upload_data_size);
+TEH_DEPOSIT_handler_deposit (struct MHD_Connection *connection,
+                             const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                             const json_t *root);
+
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_keystate.c b/src/exchange/taler-exchange-httpd_keystate.c
index 27f22925..f0ab2a0d 100644
--- a/src/exchange/taler-exchange-httpd_keystate.c
+++ b/src/exchange/taler-exchange-httpd_keystate.c
@@ -2381,17 +2381,13 @@ krd_search_comparator (const void *key,
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_KS_handler_keys (struct TEH_RequestHandler *rh,
+TEH_KS_handler_keys (const struct TEH_RequestHandler *rh,
                      struct MHD_Connection *connection,
-                     void **connection_cls,
-                     const char *upload_data,
-                     size_t *upload_data_size)
+                     const char *const args[])
 {
   int ret;
   const char *have_cherrypick;
@@ -2400,9 +2396,8 @@ TEH_KS_handler_keys (struct TEH_RequestHandler *rh,
   struct GNUNET_TIME_Absolute now;
   const struct KeysResponseData *krd;
 
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
+  (void) rh;
+  (void) args;
   have_cherrypick = MHD_lookup_connection_value (connection,
                                                  MHD_GET_ARGUMENT_KIND,
                                                  "last_issue_date");
@@ -2493,7 +2488,7 @@ TEH_KS_handler_keys (struct TEH_RequestHandler *rh,
                                          "no key response found");
     }
     ret = MHD_queue_response (connection,
-                              rh->response_code,
+                              MHD_HTTP_OK,
                               (MHD_YES == TALER_MHD_can_compress (connection))
                               ? krd->response_compressed
                               : krd->response_uncompressed);
diff --git a/src/exchange/taler-exchange-httpd_keystate.h b/src/exchange/taler-exchange-httpd_keystate.h
index ebcefa08..a6906096 100644
--- a/src/exchange/taler-exchange-httpd_keystate.h
+++ b/src/exchange/taler-exchange-httpd_keystate.h
@@ -188,17 +188,13 @@ TEH_KS_sign (const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
-  */
+ */
 int
-TEH_KS_handler_keys (struct TEH_RequestHandler *rh,
+TEH_KS_handler_keys (const struct TEH_RequestHandler *rh,
                      struct MHD_Connection *connection,
-                     void **connection_cls,
-                     const char *upload_data,
-                     size_t *upload_data_size);
+                     const char *const args[]);
 
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_mhd.c b/src/exchange/taler-exchange-httpd_mhd.c
index 0f2ce033..0d59fad1 100644
--- a/src/exchange/taler-exchange-httpd_mhd.c
+++ b/src/exchange/taler-exchange-httpd_mhd.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free Software
@@ -39,27 +39,23 @@
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_MHD_handler_static_response (struct TEH_RequestHandler *rh,
+TEH_MHD_handler_static_response (const struct TEH_RequestHandler *rh,
                                  struct MHD_Connection *connection,
-                                 void **connection_cls,
-                                 const char *upload_data,
-                                 size_t *upload_data_size)
+                                 const char *const args[])
 {
   struct MHD_Response *response;
   int ret;
+  size_t dlen;
 
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
-  if (0 == rh->data_size)
-    rh->data_size = strlen ((const char *) rh->data);
-  response = MHD_create_response_from_buffer (rh->data_size,
+  (void) args;
+  dlen = (0 == rh->data_size)
+         ? strlen ((const char *) rh->data)
+         : rh->data_size;
+  response = MHD_create_response_from_buffer (dlen,
                                               (void *) rh->data,
                                               MHD_RESPMEM_PERSISTENT);
   if (NULL == response)
@@ -86,22 +82,16 @@ TEH_MHD_handler_static_response (struct TEH_RequestHandler *rh,
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_MHD_handler_agpl_redirect (struct TEH_RequestHandler *rh,
+TEH_MHD_handler_agpl_redirect (const struct TEH_RequestHandler *rh,
                                struct MHD_Connection *connection,
-                               void **connection_cls,
-                               const char *upload_data,
-                               size_t *upload_data_size)
+                               const char *const args[])
 {
   (void) rh;
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
+  (void) args;
   return TALER_MHD_reply_agpl (connection,
                                "http://www.git.taler.net/?p=exchange.git");
 }
@@ -113,21 +103,15 @@ TEH_MHD_handler_agpl_redirect (struct TEH_RequestHandler *rh,
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_MHD_handler_send_json_pack_error (struct TEH_RequestHandler *rh,
+TEH_MHD_handler_send_json_pack_error (const struct TEH_RequestHandler *rh,
                                       struct MHD_Connection *connection,
-                                      void **connection_cls,
-                                      const char *upload_data,
-                                      size_t *upload_data_size)
+                                      const char *const args[])
 {
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
+  (void) args;
   return TALER_MHD_reply_with_error (connection,
                                      rh->response_code,
                                      TALER_EC_METHOD_INVALID,
diff --git a/src/exchange/taler-exchange-httpd_mhd.h b/src/exchange/taler-exchange-httpd_mhd.h
index 16fc5a01..0364f046 100644
--- a/src/exchange/taler-exchange-httpd_mhd.h
+++ b/src/exchange/taler-exchange-httpd_mhd.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free Software
@@ -34,17 +34,13 @@
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_MHD_handler_static_response (struct TEH_RequestHandler *rh,
+TEH_MHD_handler_static_response (const struct TEH_RequestHandler *rh,
                                  struct MHD_Connection *connection,
-                                 void **connection_cls,
-                                 const char *upload_data,
-                                 size_t *upload_data_size);
+                                 const char *const args[]);
 
 
 /**
@@ -53,40 +49,13 @@ TEH_MHD_handler_static_response (struct TEH_RequestHandler *rh,
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_MHD_handler_agpl_redirect (struct TEH_RequestHandler *rh,
+TEH_MHD_handler_agpl_redirect (const struct TEH_RequestHandler *rh,
                                struct MHD_Connection *connection,
-                               void **connection_cls,
-                               const char *upload_data,
-                               size_t *upload_data_size);
-
-
-/**
- * Function to call to handle the request by building a JSON
- * reply from varargs.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param response_code HTTP response code to use
- * @param do_cache can the response be cached? (0: no, 1: yes)
- * @param fmt format string for pack
- * @param ... varargs
- * @return MHD result code
- */
-int
-TEH_MHD_helper_send_json_pack (struct TEH_RequestHandler *rh,
-                               struct MHD_Connection *connection,
-                               void *connection_cls,
-                               int response_code,
-                               int do_cache,
-                               const char *fmt,
-                               ...);
+                               const char *const args[]);
 
 
 /**
@@ -95,17 +64,13 @@ TEH_MHD_helper_send_json_pack (struct TEH_RequestHandler *rh,
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_MHD_handler_send_json_pack_error (struct TEH_RequestHandler *rh,
+TEH_MHD_handler_send_json_pack_error (const struct TEH_RequestHandler *rh,
                                       struct MHD_Connection *connection,
-                                      void **connection_cls,
-                                      const char *upload_data,
-                                      size_t *upload_data_size);
+                                      const char *const args[]);
 
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_recoup.c b/src/exchange/taler-exchange-httpd_recoup.c
index 26bd65b4..f4cd9915 100644
--- a/src/exchange/taler-exchange-httpd_recoup.c
+++ b/src/exchange/taler-exchange-httpd_recoup.c
@@ -562,27 +562,21 @@ verify_and_execute_recoup (struct MHD_Connection *connection,
 
 
 /**
- * Handle a "/recoup" request.  Parses the JSON, and, if successful,
- * passes the JSON data to #verify_and_execute_recoup() to
- * further check the details of the operation specified.  If
- * everything checks out, this will ultimately lead to the "/refund"
- * being executed, or rejected.
+ * Handle a "/coins/$COIN_PUB/recoup" request.  Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_recoup() to further
+ * check the details of the operation specified.  If everything checks out,
+ * this will ultimately lead to the refund being executed, or rejected.
  *
- * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
  * @return MHD result code
   */
 int
-TEH_RECOUP_handler_recoup (struct TEH_RequestHandler *rh,
-                           struct MHD_Connection *connection,
-                           void **connection_cls,
-                           const char *upload_data,
-                           size_t *upload_data_size)
+TEH_RECOUP_handler_recoup (struct MHD_Connection *connection,
+                           const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                           const json_t *root)
 {
-  json_t *json;
   int res;
   struct TALER_CoinPublicInfo coin;
   struct TALER_DenominationBlindingKeyP coin_bks;
@@ -593,8 +587,6 @@ TEH_RECOUP_handler_recoup (struct TEH_RequestHandler *rh,
                                  &coin.denom_pub_hash),
     TALER_JSON_spec_denomination_signature ("denom_sig",
                                             &coin.denom_sig),
-    GNUNET_JSON_spec_fixed_auto ("coin_pub",
-                                 &coin.coin_pub),
     GNUNET_JSON_spec_fixed_auto ("coin_blind_key_secret",
                                  &coin_bks),
     GNUNET_JSON_spec_fixed_auto ("coin_sig",
@@ -605,20 +597,10 @@ TEH_RECOUP_handler_recoup (struct TEH_RequestHandler *rh,
     GNUNET_JSON_spec_end ()
   };
 
-  (void) rh;
-  res = TALER_MHD_parse_post_json (connection,
-                                   connection_cls,
-                                   upload_data,
-                                   upload_data_size,
-                                   &json);
-  if (GNUNET_SYSERR == res)
-    return MHD_NO;
-  if ( (GNUNET_NO == res) || (NULL == json) )
-    return MHD_YES;
+  coin.coin_pub = *coin_pub;
   res = TALER_MHD_parse_json_data (connection,
-                                   json,
+                                   root,
                                    spec);
-  json_decref (json);
   if (GNUNET_SYSERR == res)
     return MHD_NO; /* hard failure */
   if (GNUNET_NO == res)
diff --git a/src/exchange/taler-exchange-httpd_recoup.h b/src/exchange/taler-exchange-httpd_recoup.h
index 1baefc8e..f86bf60e 100644
--- a/src/exchange/taler-exchange-httpd_recoup.h
+++ b/src/exchange/taler-exchange-httpd_recoup.h
@@ -27,25 +27,20 @@
 
 
 /**
- * Handle a "/recoup" request.  Parses the JSON, and, if successful,
- * passes the JSON data to #verify_and_execute_recoup() to
- * further check the details of the operation specified.  If
- * everything checks out, this will ultimately lead to the "/refund"
- * being executed, or rejected.
+ * Handle a "/coins/$COIN_PUB/recoup" request.  Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_recoup() to further
+ * check the details of the operation specified.  If everything checks out,
+ * this will ultimately lead to the refund being executed, or rejected.
  *
- * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
  * @return MHD result code
   */
 int
-TEH_RECOUP_handler_recoup (struct TEH_RequestHandler *rh,
-                           struct MHD_Connection *connection,
-                           void **connection_cls,
-                           const char *upload_data,
-                           size_t *upload_data_size);
+TEH_RECOUP_handler_recoup (struct MHD_Connection *connection,
+                           const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                           const json_t *root);
 
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_refresh_link.c b/src/exchange/taler-exchange-httpd_refresh_link.c
index 5e436091..6dbaed49 100644
--- a/src/exchange/taler-exchange-httpd_refresh_link.c
+++ b/src/exchange/taler-exchange-httpd_refresh_link.c
@@ -172,43 +172,37 @@ refresh_link_transaction (void *cls,
 
 
 /**
- * Handle a "/refresh/link" request.  Note that for "/refresh/link"
- * we do use a simple HTTP GET, and a HTTP POST!
+ * Handle a "/coins/$COIN_PUB/link" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (length: 2, first is the coin_pub, second must be "link")
  * @return MHD result code
   */
 int
-TEH_REFRESH_handler_refresh_link (struct TEH_RequestHandler *rh,
-                                  struct MHD_Connection *connection,
-                                  void **connection_cls,
-                                  const char *upload_data,
-                                  size_t *upload_data_size)
+TEH_REFRESH_handler_link (const struct TEH_RequestHandler *rh,
+                          struct MHD_Connection *connection,
+                          const char *const args[2])
 {
-  int mhd_ret;
-  int res;
   struct HTD_Context ctx;
+  int mhd_ret;
 
   (void) rh;
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
   memset (&ctx,
           0,
           sizeof (ctx));
-  res = TALER_MHD_parse_request_arg_data (connection,
-                                          "coin_pub",
-                                          &ctx.coin_pub,
-                                          sizeof (struct
-                                                  TALER_CoinSpendPublicKeyP));
-  if (GNUNET_SYSERR == res)
-    return MHD_NO;
-  if (GNUNET_OK != res)
-    return MHD_YES;
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &ctx.coin_pub,
+                                     sizeof (ctx.coin_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_COINS_INVALID_COIN_PUB,
+                                       "coin public key malformed");
+  }
   ctx.mlist = json_array ();
   if (GNUNET_OK !=
       TEH_DB_run_transaction (connection,
diff --git a/src/exchange/taler-exchange-httpd_refresh_link.h b/src/exchange/taler-exchange-httpd_refresh_link.h
index d0fcff33..9469c471 100644
--- a/src/exchange/taler-exchange-httpd_refresh_link.h
+++ b/src/exchange/taler-exchange-httpd_refresh_link.h
@@ -29,21 +29,17 @@
 
 
 /**
- * Handle a "/refresh/link" request
+ * Handle a "/coins/$COIN_PUB/link" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (length: 2, first is the coin_pub, second must be "link")
  * @return MHD result code
   */
 int
-TEH_REFRESH_handler_refresh_link (struct TEH_RequestHandler *rh,
-                                  struct MHD_Connection *connection,
-                                  void **connection_cls,
-                                  const char *upload_data,
-                                  size_t *upload_data_size);
+TEH_REFRESH_handler_link (const struct TEH_RequestHandler *rh,
+                          struct MHD_Connection *connection,
+                          const char *const args[2]);
 
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_refresh_melt.c b/src/exchange/taler-exchange-httpd_refresh_melt.c
index c7dc700f..9d92a4ce 100644
--- a/src/exchange/taler-exchange-httpd_refresh_melt.c
+++ b/src/exchange/taler-exchange-httpd_refresh_melt.c
@@ -577,31 +577,24 @@ check_for_denomination_key (struct MHD_Connection *connection,
 
 
 /**
- * Handle a "/refresh/melt" request.  Parses the request into the JSON
- * components and then hands things of to #check_for_denomination_key()
- * to validate the melted coins, the signature and execute the melt
- * using handle_refresh_melt().
- *
- * @param rh context of the handler
+ * Handle a "/coins/$COIN_PUB/melt" request.  Parses the request into the JSON
+ * components and then hands things of to #check_for_denomination_key() to
+ * validate the melted coins, the signature and execute the melt using
+ * handle_refresh_melt().
+
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
  * @return MHD result code
  */
 int
-TEH_REFRESH_handler_refresh_melt (struct TEH_RequestHandler *rh,
-                                  struct MHD_Connection *connection,
-                                  void **connection_cls,
-                                  const char *upload_data,
-                                  size_t *upload_data_size)
+TEH_REFRESH_handler_melt (struct MHD_Connection *connection,
+                          const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                          const json_t *root)
 {
-  json_t *root;
   struct RefreshMeltContext rmc;
   int res;
   struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("coin_pub",
-                                 &rmc.refresh_session.coin.coin_pub),
     TALER_JSON_spec_denomination_signature ("denom_sig",
                                             &rmc.refresh_session.coin.denom_sig),
     GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
@@ -615,25 +608,13 @@ TEH_REFRESH_handler_refresh_melt (struct TEH_RequestHandler *rh,
     GNUNET_JSON_spec_end ()
   };
 
-  (void) rh;
-  res = TALER_MHD_parse_post_json (connection,
-                                   connection_cls,
-                                   upload_data,
-                                   upload_data_size,
-                                   &root);
-  if (GNUNET_SYSERR == res)
-    return MHD_NO;
-  if ( (GNUNET_NO == res) ||
-       (NULL == root) )
-    return MHD_YES;
-
   memset (&rmc,
           0,
           sizeof (rmc));
+  rmc.refresh_session.coin.coin_pub = *coin_pub;
   res = TALER_MHD_parse_json_data (connection,
                                    root,
                                    spec);
-  json_decref (root);
   if (GNUNET_OK != res)
     return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
 
diff --git a/src/exchange/taler-exchange-httpd_refresh_melt.h b/src/exchange/taler-exchange-httpd_refresh_melt.h
index c50fdcb4..41488c81 100644
--- a/src/exchange/taler-exchange-httpd_refresh_melt.h
+++ b/src/exchange/taler-exchange-httpd_refresh_melt.h
@@ -29,25 +29,20 @@
 
 
 /**
- * Handle a "/refresh/melt" request after the first parsing has
- * happened.  We now need to validate the coins being melted and the
- * session signature and then hand things of to execute the melt
- * operation.  This function parses the JSON arrays and then passes
- * processing on to #refresh_melt_transaction().
- *
- * @param rh context of the handler
+ * Handle a "/coins/$COIN_PUB/melt" request.  Parses the request into the JSON
+ * components and then hands things of to #check_for_denomination_key() to
+ * validate the melted coins, the signature and execute the melt using
+ * handle_refresh_melt().
+
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
  * @return MHD result code
  */
 int
-TEH_REFRESH_handler_refresh_melt (struct TEH_RequestHandler *rh,
-                                  struct MHD_Connection *connection,
-                                  void **connection_cls,
-                                  const char *upload_data,
-                                  size_t *upload_data_size);
+TEH_REFRESH_handler_melt (struct MHD_Connection *connection,
+                          const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                          const json_t *root);
 
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_refresh_reveal.c b/src/exchange/taler-exchange-httpd_refresh_reveal.c
index 1e03c8e7..b7d7fb1c 100644
--- a/src/exchange/taler-exchange-httpd_refresh_reveal.c
+++ b/src/exchange/taler-exchange-httpd_refresh_reveal.c
@@ -884,30 +884,27 @@ handle_refresh_reveal_json (struct MHD_Connection *connection,
 
 
 /**
- * Handle a "/refresh/reveal" request. This time, the client reveals the
+ * Handle a "/refreshes/$RCH/reveal" request. This time, the client reveals the
  * private transfer keys except for the cut-and-choose value returned from
- * "/refresh/melt".  This function parses the revealed keys and secrets and
+ * "/coins/$COIN_PUB/melt".  This function parses the revealed keys and secrets and
  * ultimately passes everything to #resolve_refresh_reveal_denominations()
  * which will verify that the revealed information is valid then runs the
  * transaction in #refresh_reveal_transaction() and finally returns the signed
  * refreshed coins.
  *
  * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
+ * @param args array of additional options (length: 2, session hash and the string "reveal")
  * @return MHD result code
-  */
+ */
 int
-TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh,
-                                    struct MHD_Connection *connection,
-                                    void **connection_cls,
-                                    const char *upload_data,
-                                    size_t *upload_data_size)
+TEH_REFRESH_handler_reveal (const struct TEH_RequestHandler *rh,
+                            struct MHD_Connection *connection,
+                            const json_t *root,
+                            const char *const args[2])
 {
   int res;
-  json_t *root;
   json_t *coin_evs;
   json_t *transfer_privs;
   json_t *link_sigs;
@@ -924,24 +921,34 @@ TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh,
   };
 
   (void) rh;
-  res = TALER_MHD_parse_post_json (connection,
-                                   connection_cls,
-                                   upload_data,
-                                   upload_data_size,
-                                   &root);
-  if (GNUNET_SYSERR == res)
-    return MHD_NO;
-  if ( (GNUNET_NO == res) ||
-       (NULL == root) )
-    return MHD_YES;
-
   memset (&rctx,
           0,
           sizeof (rctx));
+
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &rctx.rc,
+                                     sizeof (rctx.rc)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_REFRESHES_INVALID_RCH,
+                                       "refresh commitment hash malformed");
+  }
+  if (0 != strcmp (args[1],
+                   "reveal"))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_OPERATION_INVALID,
+                                       "expected 'reveal' operation");
+  }
   res = TALER_MHD_parse_json_data (connection,
                                    root,
                                    spec);
-  json_decref (root);
   if (GNUNET_OK != res)
   {
     GNUNET_break_op (0);
diff --git a/src/exchange/taler-exchange-httpd_refresh_reveal.h b/src/exchange/taler-exchange-httpd_refresh_reveal.h
index 0b0c29b7..afc9adce 100644
--- a/src/exchange/taler-exchange-httpd_refresh_reveal.h
+++ b/src/exchange/taler-exchange-httpd_refresh_reveal.h
@@ -29,26 +29,25 @@
 
 
 /**
- * Handle a "/refresh/reveal" request. This time, the client reveals the
+ * Handle a "/refreshes/$RCH/reveal" request. This time, the client reveals the
  * private transfer keys except for the cut-and-choose value returned from
- * "/refresh/melt".  This function parses the revealed keys and secrets and
+ * "/coins/$COIN_PUB/melt".  This function parses the revealed keys and secrets and
  * ultimately passes everything to #resolve_refresh_reveal_denominations()
  * which will verify that the revealed information is valid then runs the
  * transaction in #refresh_reveal_transaction() and finally returns the signed
  * refreshed coins.
  *
  * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
+ * @param args array of additional options (length: 2, session hash and the string "reveal")
  * @return MHD result code
-  */
+ */
 int
-TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh,
-                                    struct MHD_Connection *connection,
-                                    void **connection_cls,
-                                    const char *upload_data,
-                                    size_t *upload_data_size);
+TEH_REFRESH_handler_reveal (const struct TEH_RequestHandler *rh,
+                            struct MHD_Connection *connection,
+                            const json_t *root,
+                            const char *const args[2]);
+
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_refund.c b/src/exchange/taler-exchange-httpd_refund.c
index 8e24b9b4..e7e34e0b 100644
--- a/src/exchange/taler-exchange-httpd_refund.c
+++ b/src/exchange/taler-exchange-httpd_refund.c
@@ -532,27 +532,21 @@ verify_and_execute_refund (struct MHD_Connection *connection,
 
 
 /**
- * Handle a "/refund" request.  Parses the JSON, and, if successful,
- * passes the JSON data to #verify_and_execute_refund() to
- * further check the details of the operation specified.  If
- * everything checks out, this will ultimately lead to the "/refund"
- * being executed, or rejected.
+ * Handle a "/coins/$COIN_PUB/refund" request.  Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_refund() to further
+ * check the details of the operation specified.  If everything checks out,
+ * this will ultimately lead to the refund being executed, or rejected.
  *
- * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
  * @return MHD result code
   */
 int
-TEH_REFUND_handler_refund (struct TEH_RequestHandler *rh,
-                           struct MHD_Connection *connection,
-                           void **connection_cls,
-                           const char *upload_data,
-                           size_t *upload_data_size)
+TEH_REFUND_handler_refund (struct MHD_Connection *connection,
+                           const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                           const json_t *root)
 {
-  json_t *json;
   int res;
   struct TALER_EXCHANGEDB_Refund refund;
   struct GNUNET_JSON_Specification spec[] = {
@@ -560,7 +554,6 @@ TEH_REFUND_handler_refund (struct TEH_RequestHandler *rh,
     TALER_JSON_spec_amount ("refund_fee", &refund.details.refund_fee),
     GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
                                  &refund.details.h_contract_terms),
-    GNUNET_JSON_spec_fixed_auto ("coin_pub", &refund.coin.coin_pub),
     GNUNET_JSON_spec_fixed_auto ("merchant_pub", &refund.details.merchant_pub),
     GNUNET_JSON_spec_uint64 ("rtransaction_id",
                              &refund.details.rtransaction_id),
@@ -568,20 +561,10 @@ TEH_REFUND_handler_refund (struct TEH_RequestHandler *rh,
     GNUNET_JSON_spec_end ()
   };
 
-  (void) rh;
-  res = TALER_MHD_parse_post_json (connection,
-                                   connection_cls,
-                                   upload_data,
-                                   upload_data_size,
-                                   &json);
-  if (GNUNET_SYSERR == res)
-    return MHD_NO;
-  if ( (GNUNET_NO == res) || (NULL == json) )
-    return MHD_YES;
+  refund.coin.coin_pub = *coin_pub;
   res = TALER_MHD_parse_json_data (connection,
-                                   json,
+                                   root,
                                    spec);
-  json_decref (json);
   if (GNUNET_SYSERR == res)
     return MHD_NO; /* hard failure */
   if (GNUNET_NO == res)
diff --git a/src/exchange/taler-exchange-httpd_refund.h b/src/exchange/taler-exchange-httpd_refund.h
index 4f2b868e..b79419f1 100644
--- a/src/exchange/taler-exchange-httpd_refund.h
+++ b/src/exchange/taler-exchange-httpd_refund.h
@@ -29,24 +29,19 @@
 
 
 /**
- * Handle a "/refund" request.  Parses the JSON, and, if successful,
- * passes the JSON data to #verify_and_execute_refund() to
- * further check the details of the operation specified.  If
- * everything checks out, this will ultimately lead to the "/refund"
- * being executed, or rejected.
+ * Handle a "/coins/$COIN_PUB/refund" request.  Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_refund() to further
+ * check the details of the operation specified.  If everything checks out,
+ * this will ultimately lead to the refund being executed, or rejected.
  *
- * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
  * @return MHD result code
   */
 int
-TEH_REFUND_handler_refund (struct TEH_RequestHandler *rh,
-                           struct MHD_Connection *connection,
-                           void **connection_cls,
-                           const char *upload_data,
-                           size_t *upload_data_size);
+TEH_REFUND_handler_refund (struct MHD_Connection *connection,
+                           const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                           const json_t *root);
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_reserve_status.c b/src/exchange/taler-exchange-httpd_reserve_status.c
index e2d35aae..25127125 100644
--- a/src/exchange/taler-exchange-httpd_reserve_status.c
+++ b/src/exchange/taler-exchange-httpd_reserve_status.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2017 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free Software
@@ -15,7 +15,7 @@
 */
 /**
  * @file taler-exchange-httpd_reserve_status.c
- * @brief Handle /reserve/status requests
+ * @brief Handle /reserves/$RESERVE_PUB GET requests
  * @author Florian Dold
  * @author Benedikt Mueller
  * @author Christian Grothoff
@@ -114,42 +114,37 @@ reserve_status_transaction (void *cls,
 
 
 /**
- * Handle a "/reserve/status" request.  Parses the
- * given "reserve_pub" argument (which should contain the
+ * Handle a GET "/reserves/" request.  Parses the
+ * given "reserve_pub" in @a args (which should contain the
  * EdDSA public key of a reserve) and then respond with the
  * status of the reserve.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (length: 1, just the reserve_pub)
  * @return MHD result code
  */
 int
-TEH_RESERVE_handler_reserve_status (struct TEH_RequestHandler *rh,
+TEH_RESERVE_handler_reserve_status (const struct TEH_RequestHandler *rh,
                                     struct MHD_Connection *connection,
-                                    void **connection_cls,
-                                    const char *upload_data,
-                                    size_t *upload_data_size)
+                                    const char *const args[1])
 {
   struct ReserveStatusContext rsc;
-  int res;
   int mhd_ret;
 
   (void) rh;
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
-  res = TALER_MHD_parse_request_arg_data (connection,
-                                          "reserve_pub",
-                                          &rsc.reserve_pub,
-                                          sizeof (struct
-                                                  TALER_ReservePublicKeyP));
-  if (GNUNET_SYSERR == res)
-    return MHD_NO; /* internal error */
-  if (GNUNET_NO == res)
-    return MHD_YES; /* parse error */
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &rsc.reserve_pub,
+                                     sizeof (rsc.reserve_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_RESERVES_INVALID_RESERVE_PUB,
+                                       "reserve public key malformed");
+  }
   rsc.rh = NULL;
   if (GNUNET_OK !=
       TEH_DB_run_transaction (connection,
diff --git a/src/exchange/taler-exchange-httpd_reserve_status.h b/src/exchange/taler-exchange-httpd_reserve_status.h
index 67eba230..584bd3dc 100644
--- a/src/exchange/taler-exchange-httpd_reserve_status.h
+++ b/src/exchange/taler-exchange-httpd_reserve_status.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2017 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free Software
@@ -15,7 +15,7 @@
 */
 /**
  * @file taler-exchange-httpd_reserve_status.h
- * @brief Handle /reserve/status requests
+ * @brief Handle /reserves/$RESERVE_PUB GET requests
  * @author Florian Dold
  * @author Benedikt Mueller
  * @author Christian Grothoff
@@ -26,24 +26,21 @@
 #include <microhttpd.h>
 #include "taler-exchange-httpd.h"
 
+
 /**
- * Handle a "/reserve/status" request.  Parses the
- * given "reserve_pub" argument (which should contain the
+ * Handle a GET "/reserves/" request.  Parses the
+ * given "reserve_pub" in @a args (which should contain the
  * EdDSA public key of a reserve) and then respond with the
  * status of the reserve.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (length: 1, just the reserve_pub)
  * @return MHD result code
-  */
+ */
 int
-TEH_RESERVE_handler_reserve_status (struct TEH_RequestHandler *rh,
+TEH_RESERVE_handler_reserve_status (const struct TEH_RequestHandler *rh,
                                     struct MHD_Connection *connection,
-                                    void **connection_cls,
-                                    const char *upload_data,
-                                    size_t *upload_data_size);
+                                    const char *const args[1]);
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_reserve_withdraw.c b/src/exchange/taler-exchange-httpd_reserve_withdraw.c
index 9daad0a0..25747eff 100644
--- a/src/exchange/taler-exchange-httpd_reserve_withdraw.c
+++ b/src/exchange/taler-exchange-httpd_reserve_withdraw.c
@@ -335,30 +335,27 @@ withdraw_transaction (void *cls,
 
 
 /**
- * Handle a "/reserve/withdraw" request.  Parses the "reserve_pub"
- * EdDSA key of the reserve and the requested "denom_pub" which
- * specifies the key/value of the coin to be withdrawn, and checks
- * that the signature "reserve_sig" makes this a valid withdrawal
- * request from the specified reserve.  If so, the envelope
- * with the blinded coin "coin_ev" is passed down to execute the
- * withdrawl operation.
+ * Handle a "/reserves/$RESERVE_PUB/withdraw" request.  Parses the
+ * "reserve_pub" EdDSA key of the reserve and the requested "denom_pub" which
+ * specifies the key/value of the coin to be withdrawn, and checks that the
+ * signature "reserve_sig" makes this a valid withdrawal request from the
+ * specified reserve.  If so, the envelope with the blinded coin "coin_ev" is
+ * passed down to execute the withdrawl operation.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param root uploaded JSON data
+ * @param args array of additional options (first must be the
+ *         reserve public key, the second one should be "withdraw")
  * @return MHD result code
  */
 int
-TEH_RESERVE_handler_reserve_withdraw (struct TEH_RequestHandler *rh,
+TEH_RESERVE_handler_reserve_withdraw (const struct TEH_RequestHandler *rh,
                                       struct MHD_Connection *connection,
-                                      void **connection_cls,
-                                      const char *upload_data,
-                                      size_t *upload_data_size)
+                                      const json_t *root,
+                                      const char *const args[2])
 {
   struct WithdrawContext wc;
-  json_t *root;
   int res;
   int mhd_ret;
   unsigned int hc;
@@ -369,8 +366,6 @@ TEH_RESERVE_handler_reserve_withdraw (struct TEH_RequestHandler *rh,
     GNUNET_JSON_spec_varsize ("coin_ev",
                               (void **) &wc.blinded_msg,
                               &wc.blinded_msg_len),
-    GNUNET_JSON_spec_fixed_auto ("reserve_pub",
-                                 &wc.wsrd.reserve_pub),
     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
                                  &wc.signature),
     GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
@@ -379,19 +374,22 @@ TEH_RESERVE_handler_reserve_withdraw (struct TEH_RequestHandler *rh,
   };
 
   (void) rh;
-  res = TALER_MHD_parse_post_json (connection,
-                                   connection_cls,
-                                   upload_data,
-                                   upload_data_size,
-                                   &root);
-  if (GNUNET_SYSERR == res)
-    return MHD_NO;
-  if ( (GNUNET_NO == res) || (NULL == root) )
-    return MHD_YES;
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &wc.wsrd.reserve_pub,
+                                     sizeof (wc.wsrd.reserve_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_RESERVES_INVALID_RESERVE_PUB,
+                                       "reserve public key malformed");
+  }
+
   res = TALER_MHD_parse_json_data (connection,
                                    root,
                                    spec);
-  json_decref (root);
   if (GNUNET_OK != res)
     return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
   wc.key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ());
diff --git a/src/exchange/taler-exchange-httpd_reserve_withdraw.h b/src/exchange/taler-exchange-httpd_reserve_withdraw.h
index 67b4cad0..c3e56eaa 100644
--- a/src/exchange/taler-exchange-httpd_reserve_withdraw.h
+++ b/src/exchange/taler-exchange-httpd_reserve_withdraw.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2017 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free Software
@@ -28,26 +28,24 @@
 
 
 /**
- * Handle a "/reserve/withdraw" request.  Parses the "reserve_pub"
- * EdDSA key of the reserve and the requested "denom_pub" which
- * specifies the key/value of the coin to be withdrawn, and checks
- * that the signature "reserve_sig" makes this a valid withdrawl
- * request from the specified reserve.  If so, the envelope
- * with the blinded coin "coin_ev" is passed down to execute the
- * withdrawl operation.
+ * Handle a "/reserves/$RESERVE_PUB/withdraw" request.  Parses the
+ * "reserve_pub" EdDSA key of the reserve and the requested "denom_pub" which
+ * specifies the key/value of the coin to be withdrawn, and checks that the
+ * signature "reserve_sig" makes this a valid withdrawl request from the
+ * specified reserve.  If so, the envelope with the blinded coin "coin_ev" is
+ * passed down to execute the withdrawl operation.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param root uploaded JSON data
+ * @param args array of additional options (first must be the
+ *         reserve public key, the second one should be "withdraw")
  * @return MHD result code
   */
 int
-TEH_RESERVE_handler_reserve_withdraw (struct TEH_RequestHandler *rh,
+TEH_RESERVE_handler_reserve_withdraw (const struct TEH_RequestHandler *rh,
                                       struct MHD_Connection *connection,
-                                      void **connection_cls,
-                                      const char *upload_data,
-                                      size_t *upload_data_size);
+                                      const json_t *root,
+                                      const char *const args[2]);
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_terms.c b/src/exchange/taler-exchange-httpd_terms.c
index 47905f60..121e1c78 100644
--- a/src/exchange/taler-exchange-httpd_terms.c
+++ b/src/exchange/taler-exchange-httpd_terms.c
@@ -43,22 +43,16 @@ static struct TALER_MHD_Legal *pp;
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_handler_terms (struct TEH_RequestHandler *rh,
+TEH_handler_terms (const struct TEH_RequestHandler *rh,
                    struct MHD_Connection *connection,
-                   void **connection_cls,
-                   const char *upload_data,
-                   size_t *upload_data_size)
+                   const char *const args[])
 {
   (void) rh;
-  (void) upload_data;
-  (void) upload_data_size;
-  (void) connection_cls;
+  (void) args;
   return TALER_MHD_reply_legal (connection,
                                 tos);
 }
@@ -69,22 +63,16 @@ TEH_handler_terms (struct TEH_RequestHandler *rh,
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_handler_privacy (struct TEH_RequestHandler *rh,
+TEH_handler_privacy (const struct TEH_RequestHandler *rh,
                      struct MHD_Connection *connection,
-                     void **connection_cls,
-                     const char *upload_data,
-                     size_t *upload_data_size)
+                     const char *const args[])
 {
   (void) rh;
-  (void) upload_data;
-  (void) upload_data_size;
-  (void) connection_cls;
+  (void) args;
   return TALER_MHD_reply_legal (connection,
                                 pp);
 }
diff --git a/src/exchange/taler-exchange-httpd_terms.h b/src/exchange/taler-exchange-httpd_terms.h
index 75909df9..7fe7774a 100644
--- a/src/exchange/taler-exchange-httpd_terms.h
+++ b/src/exchange/taler-exchange-httpd_terms.h
@@ -34,34 +34,27 @@
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_handler_terms (struct TEH_RequestHandler *rh,
+TEH_handler_terms (const struct TEH_RequestHandler *rh,
                    struct MHD_Connection *connection,
-                   void **connection_cls,
-                   const char *upload_data,
-                   size_t *upload_data_size);
+                   const char *const args[]);
+
 
 /**
  * Handle a "/privacy" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_handler_privacy (struct TEH_RequestHandler *rh,
+TEH_handler_privacy (const struct TEH_RequestHandler *rh,
                      struct MHD_Connection *connection,
-                     void **connection_cls,
-                     const char *upload_data,
-                     size_t *upload_data_size);
+                     const char *const args[]);
 
 
 /**
diff --git a/src/exchange/taler-exchange-httpd_track_transaction.c b/src/exchange/taler-exchange-httpd_track_transaction.c
index e8143213..d0f1d0aa 100644
--- a/src/exchange/taler-exchange-httpd_track_transaction.c
+++ b/src/exchange/taler-exchange-httpd_track_transaction.c
@@ -336,62 +336,86 @@ check_and_handle_track_transaction_request (struct MHD_Connection *connection,
 
 
 /**
- * Handle a "/track/transaction" request.
+ * Handle a "/deposits/$H_WIRE/$MERCHANT_PUB/$H_CONTRACT_TERMS/$COIN_PUB"
+ * request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (length: 4, contains:
+ *      h_wire, merchant_pub, h_contract_terms and coin_pub)
  * @return MHD result code
- */
+  */
 int
-TEH_TRACKING_handler_track_transaction (struct TEH_RequestHandler *rh,
+TEH_TRACKING_handler_track_transaction (const struct TEH_RequestHandler *rh,
                                         struct MHD_Connection *connection,
-                                        void **connection_cls,
-                                        const char *upload_data,
-                                        size_t *upload_data_size)
+                                        const char *const args[4])
 {
   int res;
-  json_t *json;
   struct TALER_DepositTrackPS tps;
   struct TALER_MerchantSignatureP merchant_sig;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("h_wire", &tps.h_wire),
-    GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &tps.h_contract_terms),
-    GNUNET_JSON_spec_fixed_auto ("coin_pub", &tps.coin_pub),
-    GNUNET_JSON_spec_fixed_auto ("merchant_pub", &tps.merchant),
-    GNUNET_JSON_spec_fixed_auto ("merchant_sig", &merchant_sig),
-    GNUNET_JSON_spec_end ()
-  };
-
-  (void) rh;
-  res = TALER_MHD_parse_post_json (connection,
-                                   connection_cls,
-                                   upload_data,
-                                   upload_data_size,
-                                   &json);
-  if (GNUNET_SYSERR == res)
-    return MHD_NO;
-  if ( (GNUNET_NO == res) || (NULL == json) )
-    return MHD_YES;
-  res = TALER_MHD_parse_json_data (connection,
-                                   json,
-                                   spec);
-  if (GNUNET_OK != res)
+
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &tps.h_wire,
+                                     sizeof (tps.h_wire)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_DEPOSITS_INVALID_H_WIRE,
+                                       "wire hash malformed");
+  }
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[1],
+                                     strlen (args[1]),
+                                     &tps.merchant,
+                                     sizeof (tps.merchant)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_DEPOSITS_INVALID_MERCHANT_PUB,
+                                       "merchant public key malformed");
+  }
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[2],
+                                     strlen (args[2]),
+                                     &tps.h_contract_terms,
+                                     sizeof (tps.h_contract_terms)))
   {
-    json_decref (json);
-    return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_DEPOSITS_INVALID_H_CONTRACT_TERMS,
+                                       "contract terms hash malformed");
   }
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[3],
+                                     strlen (args[3]),
+                                     &tps.coin_pub,
+                                     sizeof (tps.coin_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_DEPOSITS_INVALID_COIN_PUB,
+                                       "coin public key malformed");
+  }
+  res = TALER_MHD_parse_request_arg_data (connection,
+                                          "merchant_sig",
+                                          &merchant_sig,
+                                          sizeof (merchant_sig));
+  if (GNUNET_SYSERR == res)
+    return MHD_NO; /* internal error */
+  if (GNUNET_NO == res)
+    return MHD_YES; /* parse error */
   tps.purpose.size = htonl (sizeof (struct TALER_DepositTrackPS));
   tps.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION);
-  res = check_and_handle_track_transaction_request (connection,
-                                                    &tps,
-                                                    &tps.merchant,
-                                                    &merchant_sig);
-  GNUNET_JSON_parse_free (spec);
-  json_decref (json);
-  return res;
+  return check_and_handle_track_transaction_request (connection,
+                                                     &tps,
+                                                     &tps.merchant,
+                                                     &merchant_sig);
 }
 
 
diff --git a/src/exchange/taler-exchange-httpd_track_transaction.h b/src/exchange/taler-exchange-httpd_track_transaction.h
index 929ee638..5f54754f 100644
--- a/src/exchange/taler-exchange-httpd_track_transaction.h
+++ b/src/exchange/taler-exchange-httpd_track_transaction.h
@@ -27,21 +27,19 @@
 
 
 /**
- * Handle a "/track/transaction" request.
+ * Handle a "/deposits/$H_WIRE/$MERCHANT_PUB/$H_CONTRACT_TERMS/$COIN_PUB"
+ * request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (length: 4, contains:
+ *      h_wire, merchant_pub, h_contract_terms and coin_pub)
  * @return MHD result code
   */
 int
-TEH_TRACKING_handler_track_transaction (struct TEH_RequestHandler *rh,
+TEH_TRACKING_handler_track_transaction (const struct TEH_RequestHandler *rh,
                                         struct MHD_Connection *connection,
-                                        void **connection_cls,
-                                        const char *upload_data,
-                                        size_t *upload_data_size);
+                                        const char *const args[4]);
 
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_track_transfer.c b/src/exchange/taler-exchange-httpd_track_transfer.c
index 1a780c06..cff6045e 100644
--- a/src/exchange/taler-exchange-httpd_track_transfer.c
+++ b/src/exchange/taler-exchange-httpd_track_transfer.c
@@ -482,40 +482,37 @@ free_ctx (struct WtidTransactionContext *ctx)
 
 
 /**
- * Handle a "/track/transfer" request.
+ * Handle a GET "/transfers/$WTID" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (length: 1, just the wtid)
  * @return MHD result code
  */
 int
-TEH_TRACKING_handler_track_transfer (struct TEH_RequestHandler *rh,
+TEH_TRACKING_handler_track_transfer (const struct TEH_RequestHandler *rh,
                                      struct MHD_Connection *connection,
-                                     void **connection_cls,
-                                     const char *upload_data,
-                                     size_t *upload_data_size)
+                                     const char *const args[1])
 {
   struct WtidTransactionContext ctx;
-  int res;
   int mhd_ret;
 
   (void) rh;
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
-  memset (&ctx, 0, sizeof (ctx));
-  res = TALER_MHD_parse_request_arg_data (connection,
-                                          "wtid",
-                                          &ctx.wtid,
-                                          sizeof (struct
-                                                  TALER_WireTransferIdentifierRawP));
-  if (GNUNET_SYSERR == res)
-    return MHD_NO; /* internal error */
-  if (GNUNET_NO == res)
-    return MHD_YES; /* parse error */
+  memset (&ctx,
+          0,
+          sizeof (ctx));
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &ctx.wtid,
+                                     sizeof (ctx.wtid)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_TRANSFERS_INVALID_WTID,
+                                       "wire transfer identifier malformed");
+  }
   if (GNUNET_OK !=
       TEH_DB_run_transaction (connection,
                               "run track transfer",
diff --git a/src/exchange/taler-exchange-httpd_track_transfer.h b/src/exchange/taler-exchange-httpd_track_transfer.h
index c68cb288..c6bd7c5d 100644
--- a/src/exchange/taler-exchange-httpd_track_transfer.h
+++ b/src/exchange/taler-exchange-httpd_track_transfer.h
@@ -27,20 +27,17 @@
 
 
 /**
- * Handle a "/track/transfer" request.
+ * Handle a GET "/transfers/$WTID" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (length: 1, just the wtid)
  * @return MHD result code
  */
 int
-TEH_TRACKING_handler_track_transfer (struct TEH_RequestHandler *rh,
+TEH_TRACKING_handler_track_transfer (const struct TEH_RequestHandler *rh,
                                      struct MHD_Connection *connection,
-                                     void **connection_cls,
-                                     const char *upload_data,
-                                     size_t *upload_data_size);
+                                     const char *const args[1]);
+
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_validation.c b/src/exchange/taler-exchange-httpd_validation.c
index e3dd8e86..e55100e1 100644
--- a/src/exchange/taler-exchange-httpd_validation.c
+++ b/src/exchange/taler-exchange-httpd_validation.c
@@ -25,7 +25,6 @@
 #include "taler-exchange-httpd_validation.h"
 #include "taler-exchange-httpd_wire.h"
 #include "taler_exchangedb_lib.h"
-#include "taler_json_lib.h"
 
 
 /**
diff --git a/src/exchange/taler-exchange-httpd_wire.c b/src/exchange/taler-exchange-httpd_wire.c
index e4bcbec5..de4e2db4 100644
--- a/src/exchange/taler-exchange-httpd_wire.c
+++ b/src/exchange/taler-exchange-httpd_wire.c
@@ -124,22 +124,16 @@ TEH_WIRE_get_fees (const char *method)
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
   */
 int
-TEH_WIRE_handler_wire (struct TEH_RequestHandler *rh,
+TEH_WIRE_handler_wire (const struct TEH_RequestHandler *rh,
                        struct MHD_Connection *connection,
-                       void **connection_cls,
-                       const char *upload_data,
-                       size_t *upload_data_size)
+                       const char *const args[])
 {
   (void) rh;
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
+  (void) args;
   GNUNET_assert (NULL != wire_methods);
   return TALER_MHD_reply_json (connection,
                                wire_methods,
diff --git a/src/exchange/taler-exchange-httpd_wire.h b/src/exchange/taler-exchange-httpd_wire.h
index 75c60353..ac4ea39c 100644
--- a/src/exchange/taler-exchange-httpd_wire.h
+++ b/src/exchange/taler-exchange-httpd_wire.h
@@ -51,17 +51,13 @@ TEH_WIRE_get_fees (const char *method);
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
-  */
+ */
 int
-TEH_WIRE_handler_wire (struct TEH_RequestHandler *rh,
+TEH_WIRE_handler_wire (const struct TEH_RequestHandler *rh,
                        struct MHD_Connection *connection,
-                       void **connection_cls,
-                       const char *upload_data,
-                       size_t *upload_data_size);
+                       const char *const args[]);
 
 
 #endif
diff --git a/src/include/taler_error_codes.h b/src/include/taler_error_codes.h
index 917ac36d..1c48fe33 100644
--- a/src/include/taler_error_codes.h
+++ b/src/include/taler_error_codes.h
@@ -85,6 +85,32 @@ enum TALER_ErrorCode
    */
   TALER_EC_METHOD_INVALID = 8,
 
+  /**
+   * Operation specified invalid for this URL (resulting in a "NOT
+   * FOUND" for the overall response).
+   */
+  TALER_EC_OPERATION_INVALID = 9,
+
+  /**
+   * There is no endpoint defined for the URL provided by the client
+   * (returned together with a MHD_HTTP_NOT FOUND status code).
+   */
+  TALER_EC_ENDPOINT_UNKNOWN = 10,
+
+  /**
+   * The URI is longer than the longest URI the HTTP server is willing
+   * to parse. Returned together with an HTTP status code of
+   * MHD_HTTP_URI_TOO_LONG.
+   */
+  TALER_EC_URI_TOO_LONG = 11,
+
+  /**
+   * The number of segments included in the URI does not match the
+   * number of segments expected by the endpoint. (returned together
+   * with a MHD_HTTP_NOT FOUND status code).
+   */
+  TALER_EC_WRONG_NUMBER_OF_SEGMENTS = 12,
+
   /**
    * The exchange failed to even just initialize its connection to the
    * database.  This response is provided with HTTP status code
@@ -181,6 +207,50 @@ enum TALER_ErrorCode
    */
   TALER_EC_DB_COIN_HISTORY_STORE_ERROR = 1014,
 
+  /**
+   * The public key of given to a /coins/ handler was malformed.
+   */
+  TALER_EC_COINS_INVALID_COIN_PUB = 1050,
+
+  /**
+   * The public key of given to a /reserves/ handler was malformed.
+   */
+  TALER_EC_RESERVES_INVALID_RESERVE_PUB = 1051,
+
+  /**
+   * The public key of given to a /transfers/ handler was malformed.
+   */
+  TALER_EC_TRANSFERS_INVALID_WTID = 1052,
+
+  /**
+   * The hash of the wire details of given to a /deposits/ handler was
+   * malformed.
+   */
+  TALER_EC_DEPOSITS_INVALID_H_WIRE = 1053,
+
+  /**
+   * The merchant public key given to a /deposits/ handler was
+   * malformed.
+   */
+  TALER_EC_DEPOSITS_INVALID_MERCHANT_PUB = 1054,
+
+  /**
+   * The hash of the contract given to a /deposits/ handler was
+   * malformed.
+   */
+  TALER_EC_DEPOSITS_INVALID_H_CONTRACT_TERMS = 1055,
+
+  /**
+   * The coin public key given to a /deposits/ handler was malformed.
+   */
+  TALER_EC_DEPOSITS_INVALID_COIN_PUB = 1056,
+
+  /**
+   * The hash of the refresh commitment given to a /refreshes/ handler
+   * was malformed.
+   */
+  TALER_EC_REFRESHES_INVALID_RCH = 1057,
+
   /**
    * The given reserve does not have sufficient funds to admit the
    * requested withdraw operation at this time.  The response includes
phase1.diff (97,840 bytes)

Christian Grothoff

2020-02-26 22:29

manager   ~0015412

Attached patch updates libtalerexchange to use the new REST API. Tests still fail, to be investigated why.
Also still big rename-fest ahead. Might even want to look at the afl test generation again :-(.

stage2.diff (27,914 bytes)
diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c
index 20a87c33..06eeb6a2 100644
--- a/src/lib/exchange_api_deposit.c
+++ b/src/lib/exchange_api_deposit.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014, 2015, 2018, 2019 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -513,7 +513,23 @@ TALER_EXCHANGE_deposit (struct TALER_EXCHANGE_Handle *exchange,
   struct GNUNET_HashCode h_wire;
   struct GNUNET_HashCode denom_pub_hash;
   struct TALER_Amount amount_without_fee;
+  char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
 
+  {
+    char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (coin_pub,
+                                         sizeof (struct
+                                                 TALER_CoinSpendPublicKeyP),
+                                         pub_str,
+                                         sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/coins/%s/deposit",
+                     pub_str);
+  }
   (void) GNUNET_TIME_round_abs (&wire_deadline);
   (void) GNUNET_TIME_round_abs (&refund_deadline);
   GNUNET_assert (refund_deadline.abs_value_us <= wire_deadline.abs_value_us);
@@ -557,7 +573,7 @@ TALER_EXCHANGE_deposit (struct TALER_EXCHANGE_Handle *exchange,
 
   deposit_obj = json_pack ("{s:o, s:O," /* f/wire */
                            " s:o, s:o," /* h_wire, h_contract_terms */
-                           " s:o, s:o," /* coin_pub, denom_pub */
+                           " s:o," /* denom_pub */
                            " s:o, s:o," /* ub_sig, timestamp */
                            " s:o," /* merchant_pub */
                            " s:o, s:o," /* refund_deadline, wire_deadline */
@@ -567,7 +583,6 @@ TALER_EXCHANGE_deposit (struct TALER_EXCHANGE_Handle *exchange,
                            "h_wire", GNUNET_JSON_from_data_auto (&h_wire),
                            "h_contract_terms", GNUNET_JSON_from_data_auto (
                              h_contract_terms),
-                           "coin_pub", GNUNET_JSON_from_data_auto (coin_pub),
                            "denom_pub_hash", GNUNET_JSON_from_data_auto (
                              &denom_pub_hash),
                            "ub_sig", GNUNET_JSON_from_rsa_signature (
@@ -592,7 +607,8 @@ TALER_EXCHANGE_deposit (struct TALER_EXCHANGE_Handle *exchange,
   dh->exchange = exchange;
   dh->cb = cb;
   dh->cb_cls = cb_cls;
-  dh->url = TEAH_path_to_url (exchange, "/deposit");
+  dh->url = TEAH_path_to_url (exchange,
+                              arg_str);
   dh->depconf.purpose.size = htonl (sizeof (struct
                                             TALER_DepositConfirmationPS));
   dh->depconf.purpose.purpose = htonl (
diff --git a/src/lib/exchange_api_recoup.c b/src/lib/exchange_api_recoup.c
index 1a332ad3..a31d5b40 100644
--- a/src/lib/exchange_api_recoup.c
+++ b/src/lib/exchange_api_recoup.c
@@ -328,6 +328,7 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
   struct GNUNET_HashCode h_denom_pub;
   json_t *recoup_obj;
   CURL *eh;
+  char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
 
   GNUNET_assert (GNUNET_YES ==
                  TEAH_handle_is_ready (exchange));
@@ -345,14 +346,12 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
                                            &coin_sig.eddsa_signature));
 
   recoup_obj = json_pack ("{s:o, s:o," /* denom pub/sig */
-                          " s:o, s:o,"  /* coin pub/sig */
+                          " s:o,"  /* sig */
                           " s:o, s:o}",  /* coin_bks */
                           "denom_pub_hash", GNUNET_JSON_from_data_auto (
                             &h_denom_pub),
                           "denom_sig", GNUNET_JSON_from_rsa_signature (
                             denom_sig->rsa_signature),
-                          "coin_pub", GNUNET_JSON_from_data_auto (
-                            &pr.coin_pub),
                           "coin_sig", GNUNET_JSON_from_data_auto (&coin_sig),
                           "coin_blind_key_secret", GNUNET_JSON_from_data_auto (
                             &ps->blinding_key),
@@ -364,6 +363,22 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
     return NULL;
   }
 
+  {
+    char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (&pr.coin_pub,
+                                         sizeof (struct
+                                                 TALER_CoinSpendPublicKeyP),
+                                         pub_str,
+                                         sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/coins/%s/recoup",
+                     pub_str);
+  }
+
   ph = GNUNET_new (struct TALER_EXCHANGE_RecoupHandle);
   ph->coin_pub = pr.coin_pub;
   ph->exchange = exchange;
@@ -371,7 +386,8 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
   ph->pk.key.rsa_public_key = NULL; /* zero out, as lifetime cannot be warranted */
   ph->cb = recoup_cb;
   ph->cb_cls = recoup_cb_cls;
-  ph->url = TEAH_path_to_url (exchange, "/recoup");
+  ph->url = TEAH_path_to_url (exchange,
+                              arg_str);
   ph->was_refreshed = was_refreshed;
   eh = TEL_curl_easy_get (ph->url);
   if (GNUNET_OK !=
diff --git a/src/lib/exchange_api_refresh.c b/src/lib/exchange_api_refresh.c
index e097ee3f..7a1304c1 100644
--- a/src/lib/exchange_api_refresh.c
+++ b/src/lib/exchange_api_refresh.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2015, 2016, 2017, 2019 Taler Systems SA
+  Copyright (C) 2015-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -1165,6 +1165,7 @@ TALER_EXCHANGE_refresh_melt (struct TALER_EXCHANGE_Handle *exchange,
   struct TALER_CoinSpendSignatureP confirm_sig;
   struct TALER_RefreshMeltCoinAffirmationPS melt;
   struct GNUNET_HashCode h_denom_pub;
+  char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
 
   GNUNET_assert (GNUNET_YES ==
                  TEAH_handle_is_ready (exchange));
@@ -1175,7 +1176,6 @@ TALER_EXCHANGE_refresh_melt (struct TALER_EXCHANGE_Handle *exchange,
     GNUNET_break (0);
     return NULL;
   }
-
   melt.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT);
   melt.purpose.size = htonl (sizeof (struct
                                      TALER_RefreshMeltCoinAffirmationPS));
@@ -1212,6 +1212,22 @@ TALER_EXCHANGE_refresh_melt (struct TALER_EXCHANGE_Handle *exchange,
     free_melt_data (md);
     return NULL;
   }
+  {
+    char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (&melt.coin_pub,
+                                         sizeof (struct
+                                                 TALER_CoinSpendPublicKeyP),
+                                         pub_str,
+                                         sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/coins/%s/melt",
+                     pub_str);
+  }
+
   key_state = TALER_EXCHANGE_get_keys (exchange);
   dki = TALER_EXCHANGE_get_denomination_key (key_state,
                                              &md->melted_coin.pub_key);
@@ -1226,7 +1242,7 @@ TALER_EXCHANGE_refresh_melt (struct TALER_EXCHANGE_Handle *exchange,
   rmh->melt_cb_cls = melt_cb_cls;
   rmh->md = md;
   rmh->url = TEAH_path_to_url (exchange,
-                               "/refresh/melt");
+                               arg_str);
   eh = TEL_curl_easy_get (rmh->url);
   if (GNUNET_OK !=
       TALER_curl_easy_post (&rmh->ctx,
@@ -1555,6 +1571,7 @@ TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange,
   struct GNUNET_CURL_Context *ctx;
   struct MeltData *md;
   struct TALER_TransferPublicKeyP transfer_pub;
+  char arg_str[sizeof (struct TALER_RefreshCommitmentP) * 2 + 32];
 
   if (noreveal_index >= TALER_CNC_KAPPA)
   {
@@ -1661,9 +1678,7 @@ TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange,
   }
 
   /* build main JSON request */
-  reveal_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}",
-                          "rc",
-                          GNUNET_JSON_from_data_auto (&md->rc),
+  reveal_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o}",
                           "transfer_pub",
                           GNUNET_JSON_from_data_auto (&transfer_pub),
                           "transfer_privs",
@@ -1680,6 +1695,21 @@ TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange,
     return NULL;
   }
 
+  {
+    char pub_str[sizeof (struct TALER_RefreshCommitmentP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (&md->rc,
+                                         sizeof (struct
+                                                 TALER_RefreshCommitmentP),
+                                         pub_str,
+                                         sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/refreshes/%s/reveal",
+                     pub_str);
+  }
   /* finally, we can actually issue the request */
   rrh = GNUNET_new (struct TALER_EXCHANGE_RefreshRevealHandle);
   rrh->exchange = exchange;
diff --git a/src/lib/exchange_api_refresh_link.c b/src/lib/exchange_api_refresh_link.c
index 6a747d1b..4b4b38ba 100644
--- a/src/lib/exchange_api_refresh_link.c
+++ b/src/lib/exchange_api_refresh_link.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2015, 2016, 2019 Taler Systems SA
+  Copyright (C) 2015-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -423,8 +423,7 @@ TALER_EXCHANGE_refresh_link (struct TALER_EXCHANGE_Handle *exchange,
   CURL *eh;
   struct GNUNET_CURL_Context *ctx;
   struct TALER_CoinSpendPublicKeyP coin_pub;
-  char *pub_str;
-  char *arg_str;
+  char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
 
   if (GNUNET_YES !=
       TEAH_handle_is_ready (exchange))
@@ -435,23 +434,28 @@ TALER_EXCHANGE_refresh_link (struct TALER_EXCHANGE_Handle *exchange,
 
   GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
                                       &coin_pub.eddsa_pub);
-  pub_str = GNUNET_STRINGS_data_to_string_alloc (&coin_pub,
-                                                 sizeof (struct
-                                                         TALER_CoinSpendPublicKeyP));
-  GNUNET_asprintf (&arg_str,
-                   "/refresh/link?coin_pub=%s",
-                   pub_str);
-  GNUNET_free (pub_str);
-
+  {
+    char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (&coin_pub,
+                                         sizeof (struct
+                                                 TALER_CoinSpendPublicKeyP),
+                                         pub_str,
+                                         sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/coins/%s/link",
+                     pub_str);
+  }
   rlh = GNUNET_new (struct TALER_EXCHANGE_RefreshLinkHandle);
   rlh->exchange = exchange;
   rlh->link_cb = link_cb;
   rlh->link_cb_cls = link_cb_cls;
   rlh->coin_priv = *coin_priv;
-  rlh->url = TEAH_path_to_url (exchange, arg_str);
-  GNUNET_free (arg_str);
-
-
+  rlh->url = TEAH_path_to_url (exchange,
+                               arg_str);
   eh = TEL_curl_easy_get (rlh->url);
   ctx = TEAH_handle_to_context (exchange);
   rlh->job = GNUNET_CURL_job_add (ctx,
diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c
index ef9a4359..8f2c0c4d 100644
--- a/src/lib/exchange_api_refund.c
+++ b/src/lib/exchange_api_refund.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014, 2015, 2016 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -333,16 +333,31 @@ TALER_EXCHANGE_refund2 (struct TALER_EXCHANGE_Handle *exchange,
   struct GNUNET_CURL_Context *ctx;
   json_t *refund_obj;
   CURL *eh;
+  char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
 
+  {
+    char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (coin_pub,
+                                         sizeof (struct
+                                                 TALER_CoinSpendPublicKeyP),
+                                         pub_str,
+                                         sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/coins/%s/refund",
+                     pub_str);
+  }
   refund_obj = json_pack ("{s:o, s:o," /* amount/fee */
-                          " s:o, s:o," /* h_contract_terms, coin_pub */
+                          " s:o," /* h_contract_terms */
                           " s:I," /* rtransaction id */
                           " s:o, s:o}", /* merchant_pub, merchant_sig */
                           "refund_amount", TALER_JSON_from_amount (amount),
                           "refund_fee", TALER_JSON_from_amount (refund_fee),
                           "h_contract_terms", GNUNET_JSON_from_data_auto (
                             h_contract_terms),
-                          "coin_pub", GNUNET_JSON_from_data_auto (coin_pub),
                           "rtransaction_id", (json_int_t) rtransaction_id,
                           "merchant_pub", GNUNET_JSON_from_data_auto (
                             merchant_pub),
@@ -359,7 +374,8 @@ TALER_EXCHANGE_refund2 (struct TALER_EXCHANGE_Handle *exchange,
   rh->exchange = exchange;
   rh->cb = cb;
   rh->cb_cls = cb_cls;
-  rh->url = TEAH_path_to_url (exchange, "/refund");
+  rh->url = TEAH_path_to_url (exchange,
+                              arg_str);
   rh->depconf.purpose.size = htonl (sizeof (struct TALER_RefundConfirmationPS));
   rh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND);
   rh->depconf.h_contract_terms = *h_contract_terms;
diff --git a/src/lib/exchange_api_reserve.c b/src/lib/exchange_api_reserve.c
index 710cd588..7fa3f308 100644
--- a/src/lib/exchange_api_reserve.c
+++ b/src/lib/exchange_api_reserve.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014, 2015 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -659,8 +659,7 @@ TALER_EXCHANGE_reserve_status (struct TALER_EXCHANGE_Handle *exchange,
   struct TALER_EXCHANGE_ReserveStatusHandle *rsh;
   struct GNUNET_CURL_Context *ctx;
   CURL *eh;
-  char *pub_str;
-  char *arg_str;
+  char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 16];
 
   if (GNUNET_YES !=
       TEAH_handle_is_ready (exchange))
@@ -668,13 +667,21 @@ TALER_EXCHANGE_reserve_status (struct TALER_EXCHANGE_Handle *exchange,
     GNUNET_break (0);
     return NULL;
   }
-  pub_str = GNUNET_STRINGS_data_to_string_alloc (reserve_pub,
-                                                 sizeof (struct
-                                                         TALER_ReservePublicKeyP));
-  GNUNET_asprintf (&arg_str,
-                   "/reserve/status?reserve_pub=%s",
-                   pub_str);
-  GNUNET_free (pub_str);
+  {
+    char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (reserve_pub,
+                                         sizeof (struct
+                                                 TALER_ReservePublicKeyP),
+                                         pub_str,
+                                         sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/reserves/%s",
+                     pub_str);
+  }
   rsh = GNUNET_new (struct TALER_EXCHANGE_ReserveStatusHandle);
   rsh->exchange = exchange;
   rsh->cb = cb;
@@ -682,8 +689,6 @@ TALER_EXCHANGE_reserve_status (struct TALER_EXCHANGE_Handle *exchange,
   rsh->reserve_pub = *reserve_pub;
   rsh->url = TEAH_path_to_url (exchange,
                                arg_str);
-  GNUNET_free (arg_str);
-
   eh = TEL_curl_easy_get (rsh->url);
   ctx = TEAH_handle_to_context (exchange);
   rsh->job = GNUNET_CURL_job_add (ctx,
@@ -1063,26 +1068,40 @@ reserve_withdraw_internal (struct TALER_EXCHANGE_Handle *exchange,
   json_t *withdraw_obj;
   CURL *eh;
   struct GNUNET_HashCode h_denom_pub;
+  char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
 
+  {
+    char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (reserve_pub,
+                                         sizeof (struct
+                                                 TALER_ReservePublicKeyP),
+                                         pub_str,
+                                         sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/reserves/%s/withdraw",
+                     pub_str);
+  }
   wsh = GNUNET_new (struct TALER_EXCHANGE_ReserveWithdrawHandle);
   wsh->exchange = exchange;
   wsh->cb = res_cb;
   wsh->cb_cls = res_cb_cls;
   wsh->pk = *pk;
-  wsh->pk.key.rsa_public_key = GNUNET_CRYPTO_rsa_public_key_dup (
-    pk->key.rsa_public_key);
+  wsh->pk.key.rsa_public_key
+    = GNUNET_CRYPTO_rsa_public_key_dup (pk->key.rsa_public_key);
   wsh->reserve_pub = *reserve_pub;
   wsh->c_hash = pd->c_hash;
   GNUNET_CRYPTO_rsa_public_key_hash (pk->key.rsa_public_key,
                                      &h_denom_pub);
   withdraw_obj = json_pack ("{s:o, s:o," /* denom_pub_hash and coin_ev */
-                            " s:o, s:o}",/* reserve_pub and reserve_sig */
+                            " s:o}",/* reserve_pub and reserve_sig */
                             "denom_pub_hash", GNUNET_JSON_from_data_auto (
                               &h_denom_pub),
                             "coin_ev", GNUNET_JSON_from_data (pd->coin_ev,
                                                               pd->coin_ev_size),
-                            "reserve_pub", GNUNET_JSON_from_data_auto (
-                              reserve_pub),
                             "reserve_sig", GNUNET_JSON_from_data_auto (
                               reserve_sig));
   if (NULL == withdraw_obj)
@@ -1095,9 +1114,9 @@ reserve_withdraw_internal (struct TALER_EXCHANGE_Handle *exchange,
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Attempting to withdraw from reserve %s\n",
               TALER_B2S (reserve_pub));
-
   wsh->ps = *ps;
-  wsh->url = TEAH_path_to_url (exchange, "/reserve/withdraw");
+  wsh->url = TEAH_path_to_url (exchange,
+                               arg_str);
   eh = TEL_curl_easy_get (wsh->url);
   if (GNUNET_OK !=
       TALER_curl_easy_post (&wsh->ctx,
diff --git a/src/lib/exchange_api_track_transaction.c b/src/lib/exchange_api_track_transaction.c
index adf9373b..503ceea5 100644
--- a/src/lib/exchange_api_track_transaction.c
+++ b/src/lib/exchange_api_track_transaction.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014, 2015, 2016 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -280,8 +280,12 @@ TALER_EXCHANGE_track_transaction (struct TALER_EXCHANGE_Handle *exchange,
   struct TALER_MerchantSignatureP merchant_sig;
   struct TALER_EXCHANGE_TrackTransactionHandle *dwh;
   struct GNUNET_CURL_Context *ctx;
-  json_t *deposit_wtid_obj;
   CURL *eh;
+  char arg_str[(sizeof (struct TALER_CoinSpendPublicKeyP)
+                + sizeof (struct GNUNET_HashCode)
+                + sizeof (struct TALER_MerchantPublicKeyP)
+                + sizeof (struct GNUNET_HashCode)
+                + sizeof (struct TALER_MerchantSignatureP)) * 2 + 48];
 
   if (GNUNET_YES !=
       TEAH_handle_is_ready (exchange))
@@ -301,29 +305,61 @@ TALER_EXCHANGE_track_transaction (struct TALER_EXCHANGE_Handle *exchange,
                  GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv,
                                            &dtp.purpose,
                                            &merchant_sig.eddsa_sig));
-  deposit_wtid_obj = json_pack ("{s:o, s:o," /* h_wire, h_contract_terms */
-                                " s:o," /* coin_pub */
-                                " s:o, s:o}", /* merchant_pub, merchant_sig */
-                                "h_wire", GNUNET_JSON_from_data_auto (h_wire),
-                                "h_contract_terms", GNUNET_JSON_from_data_auto (
-                                  h_contract_terms),
-                                "coin_pub", GNUNET_JSON_from_data_auto (
-                                  coin_pub),
-                                "merchant_pub", GNUNET_JSON_from_data_auto (
-                                  &dtp.merchant),
-                                "merchant_sig", GNUNET_JSON_from_data_auto (
-                                  &merchant_sig));
-  if (NULL == deposit_wtid_obj)
   {
-    GNUNET_break (0);
-    return NULL;
+    char cpub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+    char mpub_str[sizeof (struct TALER_MerchantPublicKeyP) * 2];
+    char msig_str[sizeof (struct TALER_MerchantSignatureP) * 2];
+    char chash_str[sizeof (struct GNUNET_HashCode) * 2];
+    char whash_str[sizeof (struct GNUNET_HashCode) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (h_wire,
+                                         sizeof (struct
+                                                 GNUNET_HashCode),
+                                         whash_str,
+                                         sizeof (whash_str));
+    *end = '\0';
+    end = GNUNET_STRINGS_data_to_string (&dtp.merchant,
+                                         sizeof (struct
+                                                 TALER_MerchantPublicKeyP),
+                                         mpub_str,
+                                         sizeof (mpub_str));
+    *end = '\0';
+    end = GNUNET_STRINGS_data_to_string (h_contract_terms,
+                                         sizeof (struct
+                                                 GNUNET_HashCode),
+                                         chash_str,
+                                         sizeof (chash_str));
+    *end = '\0';
+    end = GNUNET_STRINGS_data_to_string (coin_pub,
+                                         sizeof (struct
+                                                 TALER_CoinSpendPublicKeyP),
+                                         cpub_str,
+                                         sizeof (cpub_str));
+    *end = '\0';
+    end = GNUNET_STRINGS_data_to_string (&merchant_sig,
+                                         sizeof (struct
+                                                 TALER_MerchantSignatureP),
+                                         msig_str,
+                                         sizeof (msig_str));
+    *end = '\0';
+
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/deposits/%s/%s/%s/%s?merchant_sig=%s",
+                     whash_str,
+                     mpub_str,
+                     chash_str,
+                     cpub_str,
+                     msig_str);
   }
 
   dwh = GNUNET_new (struct TALER_EXCHANGE_TrackTransactionHandle);
   dwh->exchange = exchange;
   dwh->cb = cb;
   dwh->cb_cls = cb_cls;
-  dwh->url = TEAH_path_to_url (exchange, "/track/transaction");
+  dwh->url = TEAH_path_to_url (exchange,
+                               arg_str);
   dwh->depconf.purpose.size = htonl (sizeof (struct TALER_ConfirmWirePS));
   dwh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE);
   dwh->depconf.h_wire = *h_wire;
@@ -331,25 +367,12 @@ TALER_EXCHANGE_track_transaction (struct TALER_EXCHANGE_Handle *exchange,
   dwh->depconf.coin_pub = *coin_pub;
 
   eh = TEL_curl_easy_get (dwh->url);
-  if (GNUNET_OK !=
-      TALER_curl_easy_post (&dwh->ctx,
-                            eh,
-                            deposit_wtid_obj))
-  {
-    GNUNET_break (0);
-    curl_easy_cleanup (eh);
-    json_decref (deposit_wtid_obj);
-    GNUNET_free (dwh->url);
-    GNUNET_free (dwh);
-    return NULL;
-  }
-  json_decref (deposit_wtid_obj);
   ctx = TEAH_handle_to_context (exchange);
-  dwh->job = GNUNET_CURL_job_add2 (ctx,
-                                   eh,
-                                   dwh->ctx.headers,
-                                   &handle_deposit_wtid_finished,
-                                   dwh);
+  dwh->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  GNUNET_NO,
+                                  &handle_deposit_wtid_finished,
+                                  dwh);
   return dwh;
 }
 
diff --git a/src/lib/exchange_api_track_transfer.c b/src/lib/exchange_api_track_transfer.c
index ba8948fe..2fdfdde1 100644
--- a/src/lib/exchange_api_track_transfer.c
+++ b/src/lib/exchange_api_track_transfer.c
@@ -334,9 +334,8 @@ TALER_EXCHANGE_track_transfer (struct TALER_EXCHANGE_Handle *exchange,
 {
   struct TALER_EXCHANGE_TrackTransferHandle *wdh;
   struct GNUNET_CURL_Context *ctx;
-  char *buf;
-  char *path;
   CURL *eh;
+  char arg_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2 + 32];
 
   if (GNUNET_YES !=
       TEAH_handle_is_ready (exchange))
@@ -350,17 +349,23 @@ TALER_EXCHANGE_track_transfer (struct TALER_EXCHANGE_Handle *exchange,
   wdh->cb = cb;
   wdh->cb_cls = cb_cls;
 
-  buf = GNUNET_STRINGS_data_to_string_alloc (wtid,
-                                             sizeof (struct
-                                                     TALER_WireTransferIdentifierRawP));
-  GNUNET_asprintf (&path,
-                   "/track/transfer?wtid=%s",
-                   buf);
-  wdh->url = TEAH_path_to_url (wdh->exchange,
-                               path);
-  GNUNET_free (buf);
-  GNUNET_free (path);
+  {
+    char wtid_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2];
+    char *end;
 
+    end = GNUNET_STRINGS_data_to_string (wtid,
+                                         sizeof (struct
+                                                 TALER_WireTransferIdentifierRawP),
+                                         wtid_str,
+                                         sizeof (wtid_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/transfers/%s",
+                     wtid_str);
+  }
+  wdh->url = TEAH_path_to_url (wdh->exchange,
+                               arg_str);
   eh = TEL_curl_easy_get (wdh->url);
   ctx = TEAH_handle_to_context (exchange);
   wdh->job = GNUNET_CURL_job_add (ctx,
stage2.diff (27,914 bytes)

Christian Grothoff

2020-02-27 00:06

manager   ~0015413

I now have a full diff that implements the entire new REST API (attached, not committed).

The tests are passing again (incl. merchant, exclusive auditor-generation with wallet-core), but given that the wallet doesn't implement this yet, I'm not yet pushing this to master.

Things to do:
1) Some documentation / file / function names should still see updates to make the names better match the endpoint names.
    I will do this locally 'soon'.
2) Wallet needs to be updated to support the new API. This is semi-urgent, as we need this to be done for CodeBlau.

full.diff (130,710 bytes)
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c
index 6f021d72..b2250fde 100644
--- a/src/exchange/taler-exchange-httpd.c
+++ b/src/exchange/taler-exchange-httpd.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014, 2015, 2016, 2019 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free Software
@@ -147,6 +147,92 @@ static unsigned long long req_count;
 static unsigned long long req_max;
 
 
+/**
+ * Handle a "/coins/$COIN_PUB/$OP" POST request.  Parses the "coin_pub"
+ * EdDSA key of the coin and demultiplexes based on $OP.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param root uploaded JSON data
+ * @param args array of additional options (first must be the
+ *         reserve public key, the second one should be "withdraw")
+ * @return MHD result code
+ */
+static int
+handle_post_coins (const struct TEH_RequestHandler *rh,
+                   struct MHD_Connection *connection,
+                   const json_t *root,
+                   const char *const args[2])
+{
+  struct TALER_CoinSpendPublicKeyP coin_pub;
+  static const struct
+  {
+    /**
+     * Name of the operation (args[1])
+     */
+    const char *op;
+
+    /**
+     * Function to call to perform the operation.
+     *
+     * @param connection the MHD connection to handle
+     * @param coin_pub the public key of the coin
+     * @param root uploaded JSON data
+     * @return MHD result code
+     *///
+    int
+    (*handler)(struct MHD_Connection *connection,
+               const struct TALER_CoinSpendPublicKeyP *coin_pub,
+               const json_t *root);
+  } h[] = {
+    {
+      .op = "deposit",
+      .handler = &TEH_DEPOSIT_handler_deposit
+    },
+    {
+      .op = "melt",
+      .handler = &TEH_REFRESH_handler_melt
+    },
+    {
+      .op = "recoup",
+      .handler = &TEH_RECOUP_handler_recoup
+    },
+    {
+      .op = "refund",
+      .handler = &TEH_REFUND_handler_refund
+    },
+    {
+      .op = NULL,
+      .handler = NULL
+    },
+  };
+
+  (void) rh;
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &coin_pub,
+                                     sizeof (coin_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_COINS_INVALID_COIN_PUB,
+                                       "coin public key malformed");
+  }
+  for (unsigned int i = 0; NULL != h[i].op; i++)
+    if (0 == strcmp (h[i].op,
+                     args[1]))
+      return h[i].handler (connection,
+                           &coin_pub,
+                           root);
+  return TALER_MHD_reply_with_error (connection,
+                                     MHD_HTTP_NOT_FOUND,
+                                     TALER_EC_OPERATION_INVALID,
+                                     "requested operation on coin unknown");
+}
+
+
 /**
  * Function called whenever MHD is done with a request.  If the
  * request was a POST, we may have stored a `struct Buffer *` in the
@@ -205,6 +291,132 @@ is_valid_correlation_id (const char *correlation_id)
 }
 
 
+/**
+ * We found @a rh responsible for handling a request. Parse the
+ * @a upload_data (if applicable) and the @a url and call the
+ * handler.
+ *
+ * @param rh request handler to call
+ * @param connection connection being handled
+ * @param url rest of the URL to parse
+ * @param inner_cls closure for the handler, if needed
+ * @param upload_data upload data to parse (if available)
+ * @param upload_data_size[in,out] number of bytes in @a upload_data
+ * @return MHD result code
+ */
+static int
+proceed_with_handler (const struct TEH_RequestHandler *rh,
+                      struct MHD_Connection *connection,
+                      const char *url,
+                      void **inner_cls,
+                      const char *upload_data,
+                      size_t *upload_data_size)
+{
+  const char *args[rh->nargs + 1];
+  size_t ulen = strlen (url) + 1;
+  json_t *root = NULL;
+  int ret;
+
+  /* We do check for "ulen" here, because we'll later stack-allocate a buffer
+     of that size and don't want to enable malicious clients to cause us
+     huge stack allocations. */
+  if (ulen > 512)
+  {
+    /* 512 is simply "big enough", as it is bigger than "6 * 54",
+       which is the longest URL format we ever get (for
+       /deposits/).  The value should be adjusted if we ever define protocol
+       endpoints with plausibly longer inputs.  */
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_URI_TOO_LONG,
+                                       TALER_EC_URI_TOO_LONG,
+                                       "The URI given is too long");
+  }
+
+  /* All POST endpoints come with a body in JSON format. So we parse
+     the JSON here. */
+  if (0 == strcasecmp (rh->method,
+                       MHD_HTTP_METHOD_POST))
+  {
+    int res;
+
+    res = TALER_MHD_parse_post_json (connection,
+                                     inner_cls,
+                                     upload_data,
+                                     upload_data_size,
+                                     &root);
+    if (GNUNET_SYSERR == res)
+      return MHD_NO;
+    if ( (GNUNET_NO == res) || (NULL == root) )
+      return MHD_YES;
+  }
+
+  {
+    char d[ulen];
+
+    /* Parse command-line arguments, if applicable */
+    if (rh->nargs > 0)
+    {
+      unsigned int i;
+      const char *fin;
+
+      /* make a copy of 'url' because 'strtok()' will modify */
+      memcpy (d,
+              url,
+              ulen);
+      i = 0;
+      args[i++] = strtok (d, "/");
+      while ( (NULL != args[i - 1]) &&
+              (i < rh->nargs) )
+        args[i++] = strtok (NULL, "/");
+      /* make sure above loop ran nicely until completion, and also
+         that there is no excess data in 'd' afterwards */
+      if ( (i != rh->nargs) ||
+           (NULL == args[i - 1]) ||
+           (NULL != (fin = strtok (NULL, "/"))) )
+      {
+        char emsg[128 + 512];
+
+        GNUNET_snprintf (emsg,
+                         sizeof (emsg),
+                         "Got %u/%u segments for %s request ('%s')",
+                         (NULL == args[i - 1])
+                         ? i - 1
+                         : i + ((NULL != fin) ? 1 : 0),
+                         rh->nargs,
+                         rh->url,
+                         url);
+        GNUNET_break_op (0);
+        return TALER_MHD_reply_with_error (connection,
+                                           MHD_HTTP_NOT_FOUND,
+                                           TALER_EC_WRONG_NUMBER_OF_SEGMENTS,
+                                           emsg);
+      }
+    }
+
+    /* just to be safe(r), we always terminate the array with a NULL
+       (which handlers should not read, but at least if they do, they'll
+       crash pretty reliably... */
+    args[rh->nargs] = NULL;
+
+    /* Above logic ensures that 'root' is exactly non-NULL for POST operations */
+    if (NULL != root)
+      ret = rh->handler.post (rh,
+                              connection,
+                              root,
+                              args);
+    else /* and we only have "POST" or "GET" in the API for at this point
+            (OPTIONS/HEAD are taken care of earlier) */
+      ret = rh->handler.get (rh,
+                             connection,
+                             args);
+  }
+  if (NULL != root)
+    json_decref (root);
+  return ret;
+}
+
+
 /**
  * Handle incoming HTTP request.
  *
@@ -229,134 +441,111 @@ handle_mhd_request (void *cls,
                     void **con_cls)
 {
   static struct TEH_RequestHandler handlers[] = {
-    /* Landing page, tell humans to go away. */
-    { "/", MHD_HTTP_METHOD_GET, "text/plain",
-      "Hello, I'm the Taler exchange. This HTTP server is not for humans.\n", 0,
-      &TEH_MHD_handler_static_response, MHD_HTTP_OK },
     /* /robots.txt: disallow everything */
-    { "/robots.txt", MHD_HTTP_METHOD_GET, "text/plain",
-      "User-agent: *\nDisallow: /\n", 0,
-      &TEH_MHD_handler_static_response, MHD_HTTP_OK },
+    {
+      .url = "robots.txt",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_MHD_handler_static_response,
+      .mime_type = "text/plain",
+      .data = "User-agent: *\nDisallow: /\n",
+      .response_code = MHD_HTTP_OK
+    },
+    /* Landing page, tell humans to go away. */
+    {
+      .url = "",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = TEH_MHD_handler_static_response,
+      .mime_type = "text/plain",
+      .data =
+        "Hello, I'm the Taler exchange. This HTTP server is not for humans.\n",
+      .response_code = MHD_HTTP_OK
+    },
     /* AGPL licensing page, redirect to source. As per the AGPL-license,
        every deployment is required to offer the user a download of the
        source. We make this easy by including a redirect to the source
        here. */
-    { "/agpl", MHD_HTTP_METHOD_GET, "text/plain",
-      NULL, 0,
-      &TEH_MHD_handler_agpl_redirect, MHD_HTTP_FOUND },
+    {
+      .url = "agpl",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_MHD_handler_agpl_redirect
+    },
     /* Terms of service */
-    { "/terms", MHD_HTTP_METHOD_GET, NULL,
-      NULL, 0,
-      &TEH_handler_terms, MHD_HTTP_OK },
+    {
+      .url = "terms",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_terms
+    },
     /* Privacy policy */
-    { "/privacy", MHD_HTTP_METHOD_GET, NULL,
-      NULL, 0,
-      &TEH_handler_privacy, MHD_HTTP_OK },
+    {
+      .url = "privacy",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_handler_privacy
+    },
     /* Return key material and fundamental properties for this exchange */
-    { "/keys", MHD_HTTP_METHOD_GET, "application/json",
-      NULL, 0,
-      &TEH_KS_handler_keys, MHD_HTTP_OK },
-    { "/keys", NULL, "text/plain",
-      "Only GET is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
+    {
+      .url = "keys",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_KS_handler_keys,
+    },
     /* Requests for wiring information */
-    { "/wire", MHD_HTTP_METHOD_GET, "application/json",
-      NULL, 0,
-      &TEH_WIRE_handler_wire, MHD_HTTP_OK },
-    { "/wire", NULL, "text/plain",
-      "Only GET is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
+    {
+      .url = "wire",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_WIRE_handler_wire
+    },
     /* Withdrawing coins / interaction with reserves */
-    { "/reserve/status", MHD_HTTP_METHOD_GET, "application/json",
-      NULL, 0,
-      &TEH_RESERVE_handler_reserve_status, MHD_HTTP_OK },
-    { "/reserve/status", NULL, "text/plain",
-      "Only GET is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    { "/reserve/withdraw", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &TEH_RESERVE_handler_reserve_withdraw, MHD_HTTP_OK },
-    { "/reserve/withdraw", NULL, "text/plain",
-      "Only POST is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    /* Depositing coins */
-    { "/deposit", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &TEH_DEPOSIT_handler_deposit, MHD_HTTP_OK },
-    { "/deposit", NULL, "text/plain",
-      "Only POST is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    /* Refunding coins */
-    { "/refund", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &TEH_REFUND_handler_refund, MHD_HTTP_OK },
-    { "/refund", NULL, "text/plain",
-      "Only POST is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    /* Dealing with change */
-    { "/refresh/melt", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &TEH_REFRESH_handler_refresh_melt, MHD_HTTP_OK },
-    { "/refresh/melt", NULL, "text/plain",
-      "Only POST is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    { "/refresh/reveal", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &TEH_REFRESH_handler_refresh_reveal, MHD_HTTP_OK },
-    { "/refresh/reveal", NULL, "text/plain",
-      "Only POST is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    { "/refresh/reveal", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &TEH_REFRESH_handler_refresh_reveal, MHD_HTTP_OK },
-    { "/refresh/reveal", NULL, "text/plain",
-      "Only POST is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    { "/refresh/link", MHD_HTTP_METHOD_GET, "application/json",
-      NULL, 0,
-      &TEH_REFRESH_handler_refresh_link, MHD_HTTP_OK },
-    { "/refresh/link", NULL, "text/plain",
-      "Only GET is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    { "/track/transfer", MHD_HTTP_METHOD_GET, "application/json",
-      NULL, 0,
-      &TEH_TRACKING_handler_track_transfer, MHD_HTTP_OK },
-    { "/track/transfer", NULL, "text/plain",
-      "Only GET is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-    { "/track/transaction", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &TEH_TRACKING_handler_track_transaction, MHD_HTTP_OK },
-    { "/track/transaction", NULL, "text/plain",
-      "Only POST is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    { "/recoup", MHD_HTTP_METHOD_POST, "application/json",
-      NULL, 0,
-      &TEH_RECOUP_handler_recoup, MHD_HTTP_OK },
-    { "/refresh/link", NULL, "text/plain",
-      "Only GET is allowed", 0,
-      &TEH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
-
-    { NULL, NULL, NULL, NULL, 0, NULL, 0 }
-  };
-  static struct TEH_RequestHandler h404 = {
-    "", NULL, "text/html",
-    "<html><title>404: not found</title></html>", 0,
-    &TEH_MHD_handler_static_response, MHD_HTTP_NOT_FOUND
+    {
+      .url = "reserves",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_RESERVE_handler_reserve_status,
+      .nargs = 1
+    },
+    {
+      .url = "reserves",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &TEH_RESERVE_handler_reserve_withdraw,
+      .nargs = 2
+    },
+    /* coins */
+    {
+      .url = "coins",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &handle_post_coins,
+      .nargs = 2
+    },
+    {
+      .url = "coins",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = TEH_REFRESH_handler_link,
+      .nargs = 2,
+    },
+    /* refreshing */
+    {
+      .url = "refreshes",
+      .method = MHD_HTTP_METHOD_POST,
+      .handler.post = &TEH_REFRESH_handler_reveal,
+      .nargs = 2
+    },
+    /* tracking transfers */
+    {
+      .url = "transfers",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_TRACKING_handler_track_transfer,
+      .nargs = 1
+    },
+    /* tracking deposits */
+    {
+      .url = "deposits",
+      .method = MHD_HTTP_METHOD_GET,
+      .handler.get = &TEH_TRACKING_handler_track_transaction,
+      .nargs = 4
+    },
+    /* mark end of list */
+    {
+      .url = NULL
+    }
   };
   struct ExchangeHttpRequestClosure *ecls = *con_cls;
-  int ret;
   void **inner_cls;
   struct GNUNET_AsyncScopeSave old_scope;
   const char *correlation_id = NULL;
@@ -367,7 +556,8 @@ handle_mhd_request (void *cls,
   {
     unsigned long long cnt;
 
-    GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Handling new request\n");
+    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+                "Handling new request\n");
     cnt = __sync_add_and_fetch (&req_count, 1LLU);
     if (req_max == cnt)
     {
@@ -395,7 +585,8 @@ handle_mhd_request (void *cls,
   }
 
   inner_cls = &ecls->opaque_post_parsing_context;
-  GNUNET_async_scope_enter (&ecls->async_scope_id, &old_scope);
+  GNUNET_async_scope_enter (&ecls->async_scope_id,
+                            &old_scope);
   if (NULL != correlation_id)
     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                 "Handling request (%s) for URL '%s', correlation_id=%s\n",
@@ -410,55 +601,105 @@ handle_mhd_request (void *cls,
   /* on repeated requests, check our cache first */
   if (NULL != ecls->rh)
   {
-    ret = ecls->rh->handler (ecls->rh,
-                             connection,
-                             inner_cls,
-                             upload_data,
-                             upload_data_size);
+    int ret;
+    const char *start;
+
+    if ('\0' == url[0])
+      /* strange, should start with '/', treat as just "/" */
+      url = "/";
+    start = strchr (url + 1, '/');
+    ret = proceed_with_handler (ecls->rh,
+                                connection,
+                                start,
+                                inner_cls,
+                                upload_data,
+                                upload_data_size);
     GNUNET_async_scope_restore (&old_scope);
     return ret;
   }
+
   if (0 == strcasecmp (method,
                        MHD_HTTP_METHOD_HEAD))
-    method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */
-  for (unsigned int i = 0; NULL != handlers[i].url; i++)
-  {
-    struct TEH_RequestHandler *rh = &handlers[i];
-
-    if (0 != strcmp (url, rh->url))
-      continue;
+    method = MHD_HTTP_METHOD_GET;   /* treat HEAD as GET here, MHD will do the rest */
 
-    /* The URL is a match!  What we now do depends on the method. */
-    if (0 == strcasecmp (method, MHD_HTTP_METHOD_OPTIONS))
+  /* parse first part of URL */
+  {
+    int found = GNUNET_NO;
+    size_t tok_size;
+    const char *tok;
+    const char *rest;
+
+    if ('\0' == url[0])
+      /* strange, should start with '/', treat as just "/" */
+      url = "/";
+    tok = url + 1;
+    rest = strchr (tok, '/');
+    if (NULL == rest)
+    {
+      tok_size = strlen (tok);
+    }
+    else
+    {
+      tok_size = rest - tok;
+      rest++; /* skip over '/' */
+    }
+    for (unsigned int i = 0; NULL != handlers[i].url; i++)
     {
-      GNUNET_async_scope_restore (&old_scope);
-      return TALER_MHD_reply_cors_preflight (connection);
+      struct TEH_RequestHandler *rh = &handlers[i];
+
+      if (0 != strncmp (tok,
+                        rh->url,
+                        tok_size))
+        continue;
+      found = GNUNET_YES;
+      /* The URL is a match!  What we now do depends on the method. */
+      if (0 == strcasecmp (method, MHD_HTTP_METHOD_OPTIONS))
+      {
+        GNUNET_async_scope_restore (&old_scope);
+        return TALER_MHD_reply_cors_preflight (connection);
+      }
+      GNUNET_assert (NULL != rh->method);
+      if (0 == strcasecmp (method,
+                           rh->method))
+      {
+        int ret;
+
+        /* cache to avoid the loop next time */
+        ecls->rh = rh;
+        /* run handler */
+        ret = proceed_with_handler (rh,
+                                    connection,
+                                    url + tok_size + 1,
+                                    inner_cls,
+                                    upload_data,
+                                    upload_data_size);
+        GNUNET_async_scope_restore (&old_scope);
+        return ret;
+      }
     }
 
-    if ( (NULL == rh->method) ||
-         (0 == strcasecmp (method,
-                           rh->method)) )
+    if (GNUNET_YES == found)
     {
-      /* cache to avoid the loop next time */
-      ecls->rh = rh;
-      /* run handler */
-      ret = rh->handler (rh,
-                         connection,
-                         inner_cls,
-                         upload_data,
-                         upload_data_size);
-      GNUNET_async_scope_restore (&old_scope);
-      return ret;
+      /* we found a matching address, but the method is wrong */
+      GNUNET_break_op (0);
+      return TALER_MHD_reply_with_error (connection,
+                                         MHD_HTTP_METHOD_NOT_ALLOWED,
+                                         TALER_EC_METHOD_INVALID,
+                                         "The HTTP method used is invalid for this URL");
     }
   }
+
   /* No handler matches, generate not found */
-  ret = TEH_MHD_handler_static_response (&h404,
-                                         connection,
-                                         inner_cls,
-                                         upload_data,
-                                         upload_data_size);
-  GNUNET_async_scope_restore (&old_scope);
-  return ret;
+  {
+    int ret;
+
+    ret = TALER_MHD_reply_with_error (connection,
+                                      MHD_HTTP_NOT_FOUND,
+                                      TALER_EC_ENDPOINT_UNKNOWN,
+                                      "No handler found for the given URL");
+    GNUNET_async_scope_restore (&old_scope);
+    return ret;
+  }
 }
 
 
diff --git a/src/exchange/taler-exchange-httpd.h b/src/exchange/taler-exchange-httpd.h
index 38c611c6..8489d179 100644
--- a/src/exchange/taler-exchange-httpd.h
+++ b/src/exchange/taler-exchange-httpd.h
@@ -24,6 +24,8 @@
 #define TALER_EXCHANGE_HTTPD_H
 
 #include <microhttpd.h>
+#include "taler_json_lib.h"
+#include "taler_crypto_lib.h"
 
 
 /**
@@ -65,51 +67,77 @@ struct TEH_RequestHandler
 {
 
   /**
-   * URL the handler is for.
+   * URL the handler is for (first part only).
    */
   const char *url;
 
   /**
-   * Method the handler is for, NULL for "all".
+   * Method the handler is for.
    */
   const char *method;
 
+  /**
+   * Callbacks for handling of the request. Which one is used
+   * depends on @e method.
+   */
+  union
+  {
+    /**
+     * Function to call to handle a GET requests (and those
+     * with @e method NULL).
+     *
+     * @param rh this struct
+     * @param mime_type the @e mime_type for the reply (hint, can be NULL)
+     * @param connection the MHD connection to handle
+     * @param args array of arguments, needs to be of length @e args_expected
+     * @return MHD result code
+     */
+    int (*get)(const struct TEH_RequestHandler *rh,
+               struct MHD_Connection *connection,
+               const char *const args[]);
+
+
+    /**
+     * Function to call to handle a POST request.
+     *
+     * @param rh this struct
+     * @param mime_type the @e mime_type for the reply (hint, can be NULL)
+     * @param connection the MHD connection to handle
+     * @param json uploaded JSON data
+     * @param args array of arguments, needs to be of length @e args_expected
+     * @return MHD result code
+     */
+    int (*post)(const struct TEH_RequestHandler *rh,
+                struct MHD_Connection *connection,
+                const json_t *root,
+                const char *const args[]);
+
+  } handler;
+
+  /**
+   * Number of arguments this handler expects in the @a args array.
+   */
+  unsigned int nargs;
+
   /**
    * Mime type to use in reply (hint, can be NULL).
    */
   const char *mime_type;
 
   /**
-   * Raw data for the @e handler
+   * Raw data for the @e handler, can be NULL for none provided.
    */
   const void *data;
 
   /**
-   * Number of bytes in @e data, 0 for 0-terminated.
+   * Number of bytes in @e data, 0 for data is 0-terminated (!).
    */
   size_t data_size;
 
   /**
-   * Function to call to handle the request.
-   *
-   * @param rh this struct
-   * @param mime_type the @e mime_type for the reply (hint, can be NULL)
-   * @param connection the MHD connection to handle
-   * @param[in,out] connection_cls the connection's closure (can be updated)
-   * @param upload_data upload data
-   * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
-   * @return MHD result code
-   */
-  int (*handler)(struct TEH_RequestHandler *rh,
-                 struct MHD_Connection *connection,
-                 void **connection_cls,
-                 const char *upload_data,
-                 size_t *upload_data_size);
-
-  /**
-   * Default response code.
+   * Default response code. 0 for none provided.
    */
-  int response_code;
+  unsigned int response_code;
 };
 
 
diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c
index 49b9cc2f..da89ff47 100644
--- a/src/exchange/taler-exchange-httpd_deposit.c
+++ b/src/exchange/taler-exchange-httpd_deposit.c
@@ -381,27 +381,22 @@ check_timestamp_current (struct GNUNET_TIME_Absolute ts)
 
 
 /**
- * Handle a "/deposit" request.  Parses the JSON, and, if successful,
- * passes the JSON data to #verify_and_execute_deposit() to further
- * check the details of the operation specified.  If everything checks
+ * Handle a "/coins/$COIN_PUB/deposit" request.  Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_deposit() to
+ * further check the details of the operation specified.  If everything checks
  * out, this will ultimately lead to the "/deposit" being executed, or
  * rejected.
  *
- * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
  * @return MHD result code
   */
 int
-TEH_DEPOSIT_handler_deposit (struct TEH_RequestHandler *rh,
-                             struct MHD_Connection *connection,
-                             void **connection_cls,
-                             const char *upload_data,
-                             size_t *upload_data_size)
+TEH_DEPOSIT_handler_deposit (struct MHD_Connection *connection,
+                             const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                             const json_t *root)
 {
-  json_t *json;
   int res;
   json_t *wire;
   enum TALER_ErrorCode ec;
@@ -415,7 +410,6 @@ TEH_DEPOSIT_handler_deposit (struct TEH_RequestHandler *rh,
     GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
                                  &deposit.coin.denom_pub_hash),
     TALER_JSON_spec_denomination_signature ("ub_sig", &deposit.coin.denom_sig),
-    GNUNET_JSON_spec_fixed_auto ("coin_pub", &deposit.coin.coin_pub),
     GNUNET_JSON_spec_fixed_auto ("merchant_pub", &deposit.merchant_pub),
     GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &deposit.h_contract_terms),
     GNUNET_JSON_spec_fixed_auto ("h_wire", &deposit.h_wire),
@@ -428,27 +422,13 @@ TEH_DEPOSIT_handler_deposit (struct TEH_RequestHandler *rh,
     GNUNET_JSON_spec_end ()
   };
 
-  (void) rh;
-  res = TALER_MHD_parse_post_json (connection,
-                                   connection_cls,
-                                   upload_data,
-                                   upload_data_size,
-                                   &json);
-  if (GNUNET_SYSERR == res)
-  {
-    GNUNET_break (0);
-    return MHD_NO;
-  }
-  if ( (GNUNET_NO == res) ||
-       (NULL == json) )
-    return MHD_YES;
   memset (&deposit,
           0,
           sizeof (deposit));
+  deposit.coin.coin_pub = *coin_pub;
   res = TALER_MHD_parse_json_data (connection,
-                                   json,
+                                   root,
                                    spec);
-  json_decref (json);
   if (GNUNET_SYSERR == res)
   {
     GNUNET_break (0);
diff --git a/src/exchange/taler-exchange-httpd_deposit.h b/src/exchange/taler-exchange-httpd_deposit.h
index ed1f87d5..23c46c28 100644
--- a/src/exchange/taler-exchange-httpd_deposit.h
+++ b/src/exchange/taler-exchange-httpd_deposit.h
@@ -29,22 +29,21 @@
 
 
 /**
- * Handle a "/deposit" request.  Parses the JSON, and, if successful,
- * checks the signatures.  If everything checks out, this will
- * ultimately lead to the "/deposit" being executed, or rejected.
+ * Handle a "/coins/$COIN_PUB/deposit" request.  Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_deposit() to
+ * further check the details of the operation specified.  If everything checks
+ * out, this will ultimately lead to the "/deposit" being executed, or
+ * rejected.
  *
- * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
  * @return MHD result code
   */
 int
-TEH_DEPOSIT_handler_deposit (struct TEH_RequestHandler *rh,
-                             struct MHD_Connection *connection,
-                             void **connection_cls,
-                             const char *upload_data,
-                             size_t *upload_data_size);
+TEH_DEPOSIT_handler_deposit (struct MHD_Connection *connection,
+                             const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                             const json_t *root);
+
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_keystate.c b/src/exchange/taler-exchange-httpd_keystate.c
index 27f22925..f0ab2a0d 100644
--- a/src/exchange/taler-exchange-httpd_keystate.c
+++ b/src/exchange/taler-exchange-httpd_keystate.c
@@ -2381,17 +2381,13 @@ krd_search_comparator (const void *key,
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_KS_handler_keys (struct TEH_RequestHandler *rh,
+TEH_KS_handler_keys (const struct TEH_RequestHandler *rh,
                      struct MHD_Connection *connection,
-                     void **connection_cls,
-                     const char *upload_data,
-                     size_t *upload_data_size)
+                     const char *const args[])
 {
   int ret;
   const char *have_cherrypick;
@@ -2400,9 +2396,8 @@ TEH_KS_handler_keys (struct TEH_RequestHandler *rh,
   struct GNUNET_TIME_Absolute now;
   const struct KeysResponseData *krd;
 
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
+  (void) rh;
+  (void) args;
   have_cherrypick = MHD_lookup_connection_value (connection,
                                                  MHD_GET_ARGUMENT_KIND,
                                                  "last_issue_date");
@@ -2493,7 +2488,7 @@ TEH_KS_handler_keys (struct TEH_RequestHandler *rh,
                                          "no key response found");
     }
     ret = MHD_queue_response (connection,
-                              rh->response_code,
+                              MHD_HTTP_OK,
                               (MHD_YES == TALER_MHD_can_compress (connection))
                               ? krd->response_compressed
                               : krd->response_uncompressed);
diff --git a/src/exchange/taler-exchange-httpd_keystate.h b/src/exchange/taler-exchange-httpd_keystate.h
index ebcefa08..a6906096 100644
--- a/src/exchange/taler-exchange-httpd_keystate.h
+++ b/src/exchange/taler-exchange-httpd_keystate.h
@@ -188,17 +188,13 @@ TEH_KS_sign (const struct GNUNET_CRYPTO_EccSignaturePurpose *purpose,
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
-  */
+ */
 int
-TEH_KS_handler_keys (struct TEH_RequestHandler *rh,
+TEH_KS_handler_keys (const struct TEH_RequestHandler *rh,
                      struct MHD_Connection *connection,
-                     void **connection_cls,
-                     const char *upload_data,
-                     size_t *upload_data_size);
+                     const char *const args[]);
 
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_mhd.c b/src/exchange/taler-exchange-httpd_mhd.c
index 0f2ce033..0d59fad1 100644
--- a/src/exchange/taler-exchange-httpd_mhd.c
+++ b/src/exchange/taler-exchange-httpd_mhd.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free Software
@@ -39,27 +39,23 @@
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_MHD_handler_static_response (struct TEH_RequestHandler *rh,
+TEH_MHD_handler_static_response (const struct TEH_RequestHandler *rh,
                                  struct MHD_Connection *connection,
-                                 void **connection_cls,
-                                 const char *upload_data,
-                                 size_t *upload_data_size)
+                                 const char *const args[])
 {
   struct MHD_Response *response;
   int ret;
+  size_t dlen;
 
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
-  if (0 == rh->data_size)
-    rh->data_size = strlen ((const char *) rh->data);
-  response = MHD_create_response_from_buffer (rh->data_size,
+  (void) args;
+  dlen = (0 == rh->data_size)
+         ? strlen ((const char *) rh->data)
+         : rh->data_size;
+  response = MHD_create_response_from_buffer (dlen,
                                               (void *) rh->data,
                                               MHD_RESPMEM_PERSISTENT);
   if (NULL == response)
@@ -86,22 +82,16 @@ TEH_MHD_handler_static_response (struct TEH_RequestHandler *rh,
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_MHD_handler_agpl_redirect (struct TEH_RequestHandler *rh,
+TEH_MHD_handler_agpl_redirect (const struct TEH_RequestHandler *rh,
                                struct MHD_Connection *connection,
-                               void **connection_cls,
-                               const char *upload_data,
-                               size_t *upload_data_size)
+                               const char *const args[])
 {
   (void) rh;
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
+  (void) args;
   return TALER_MHD_reply_agpl (connection,
                                "http://www.git.taler.net/?p=exchange.git");
 }
@@ -113,21 +103,15 @@ TEH_MHD_handler_agpl_redirect (struct TEH_RequestHandler *rh,
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_MHD_handler_send_json_pack_error (struct TEH_RequestHandler *rh,
+TEH_MHD_handler_send_json_pack_error (const struct TEH_RequestHandler *rh,
                                       struct MHD_Connection *connection,
-                                      void **connection_cls,
-                                      const char *upload_data,
-                                      size_t *upload_data_size)
+                                      const char *const args[])
 {
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
+  (void) args;
   return TALER_MHD_reply_with_error (connection,
                                      rh->response_code,
                                      TALER_EC_METHOD_INVALID,
diff --git a/src/exchange/taler-exchange-httpd_mhd.h b/src/exchange/taler-exchange-httpd_mhd.h
index 16fc5a01..0364f046 100644
--- a/src/exchange/taler-exchange-httpd_mhd.h
+++ b/src/exchange/taler-exchange-httpd_mhd.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free Software
@@ -34,17 +34,13 @@
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_MHD_handler_static_response (struct TEH_RequestHandler *rh,
+TEH_MHD_handler_static_response (const struct TEH_RequestHandler *rh,
                                  struct MHD_Connection *connection,
-                                 void **connection_cls,
-                                 const char *upload_data,
-                                 size_t *upload_data_size);
+                                 const char *const args[]);
 
 
 /**
@@ -53,40 +49,13 @@ TEH_MHD_handler_static_response (struct TEH_RequestHandler *rh,
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_MHD_handler_agpl_redirect (struct TEH_RequestHandler *rh,
+TEH_MHD_handler_agpl_redirect (const struct TEH_RequestHandler *rh,
                                struct MHD_Connection *connection,
-                               void **connection_cls,
-                               const char *upload_data,
-                               size_t *upload_data_size);
-
-
-/**
- * Function to call to handle the request by building a JSON
- * reply from varargs.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param response_code HTTP response code to use
- * @param do_cache can the response be cached? (0: no, 1: yes)
- * @param fmt format string for pack
- * @param ... varargs
- * @return MHD result code
- */
-int
-TEH_MHD_helper_send_json_pack (struct TEH_RequestHandler *rh,
-                               struct MHD_Connection *connection,
-                               void *connection_cls,
-                               int response_code,
-                               int do_cache,
-                               const char *fmt,
-                               ...);
+                               const char *const args[]);
 
 
 /**
@@ -95,17 +64,13 @@ TEH_MHD_helper_send_json_pack (struct TEH_RequestHandler *rh,
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_MHD_handler_send_json_pack_error (struct TEH_RequestHandler *rh,
+TEH_MHD_handler_send_json_pack_error (const struct TEH_RequestHandler *rh,
                                       struct MHD_Connection *connection,
-                                      void **connection_cls,
-                                      const char *upload_data,
-                                      size_t *upload_data_size);
+                                      const char *const args[]);
 
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_recoup.c b/src/exchange/taler-exchange-httpd_recoup.c
index 26bd65b4..f4cd9915 100644
--- a/src/exchange/taler-exchange-httpd_recoup.c
+++ b/src/exchange/taler-exchange-httpd_recoup.c
@@ -562,27 +562,21 @@ verify_and_execute_recoup (struct MHD_Connection *connection,
 
 
 /**
- * Handle a "/recoup" request.  Parses the JSON, and, if successful,
- * passes the JSON data to #verify_and_execute_recoup() to
- * further check the details of the operation specified.  If
- * everything checks out, this will ultimately lead to the "/refund"
- * being executed, or rejected.
+ * Handle a "/coins/$COIN_PUB/recoup" request.  Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_recoup() to further
+ * check the details of the operation specified.  If everything checks out,
+ * this will ultimately lead to the refund being executed, or rejected.
  *
- * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
  * @return MHD result code
   */
 int
-TEH_RECOUP_handler_recoup (struct TEH_RequestHandler *rh,
-                           struct MHD_Connection *connection,
-                           void **connection_cls,
-                           const char *upload_data,
-                           size_t *upload_data_size)
+TEH_RECOUP_handler_recoup (struct MHD_Connection *connection,
+                           const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                           const json_t *root)
 {
-  json_t *json;
   int res;
   struct TALER_CoinPublicInfo coin;
   struct TALER_DenominationBlindingKeyP coin_bks;
@@ -593,8 +587,6 @@ TEH_RECOUP_handler_recoup (struct TEH_RequestHandler *rh,
                                  &coin.denom_pub_hash),
     TALER_JSON_spec_denomination_signature ("denom_sig",
                                             &coin.denom_sig),
-    GNUNET_JSON_spec_fixed_auto ("coin_pub",
-                                 &coin.coin_pub),
     GNUNET_JSON_spec_fixed_auto ("coin_blind_key_secret",
                                  &coin_bks),
     GNUNET_JSON_spec_fixed_auto ("coin_sig",
@@ -605,20 +597,10 @@ TEH_RECOUP_handler_recoup (struct TEH_RequestHandler *rh,
     GNUNET_JSON_spec_end ()
   };
 
-  (void) rh;
-  res = TALER_MHD_parse_post_json (connection,
-                                   connection_cls,
-                                   upload_data,
-                                   upload_data_size,
-                                   &json);
-  if (GNUNET_SYSERR == res)
-    return MHD_NO;
-  if ( (GNUNET_NO == res) || (NULL == json) )
-    return MHD_YES;
+  coin.coin_pub = *coin_pub;
   res = TALER_MHD_parse_json_data (connection,
-                                   json,
+                                   root,
                                    spec);
-  json_decref (json);
   if (GNUNET_SYSERR == res)
     return MHD_NO; /* hard failure */
   if (GNUNET_NO == res)
diff --git a/src/exchange/taler-exchange-httpd_recoup.h b/src/exchange/taler-exchange-httpd_recoup.h
index 1baefc8e..f86bf60e 100644
--- a/src/exchange/taler-exchange-httpd_recoup.h
+++ b/src/exchange/taler-exchange-httpd_recoup.h
@@ -27,25 +27,20 @@
 
 
 /**
- * Handle a "/recoup" request.  Parses the JSON, and, if successful,
- * passes the JSON data to #verify_and_execute_recoup() to
- * further check the details of the operation specified.  If
- * everything checks out, this will ultimately lead to the "/refund"
- * being executed, or rejected.
+ * Handle a "/coins/$COIN_PUB/recoup" request.  Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_recoup() to further
+ * check the details of the operation specified.  If everything checks out,
+ * this will ultimately lead to the refund being executed, or rejected.
  *
- * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
  * @return MHD result code
   */
 int
-TEH_RECOUP_handler_recoup (struct TEH_RequestHandler *rh,
-                           struct MHD_Connection *connection,
-                           void **connection_cls,
-                           const char *upload_data,
-                           size_t *upload_data_size);
+TEH_RECOUP_handler_recoup (struct MHD_Connection *connection,
+                           const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                           const json_t *root);
 
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_refresh_link.c b/src/exchange/taler-exchange-httpd_refresh_link.c
index 5e436091..6dbaed49 100644
--- a/src/exchange/taler-exchange-httpd_refresh_link.c
+++ b/src/exchange/taler-exchange-httpd_refresh_link.c
@@ -172,43 +172,37 @@ refresh_link_transaction (void *cls,
 
 
 /**
- * Handle a "/refresh/link" request.  Note that for "/refresh/link"
- * we do use a simple HTTP GET, and a HTTP POST!
+ * Handle a "/coins/$COIN_PUB/link" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (length: 2, first is the coin_pub, second must be "link")
  * @return MHD result code
   */
 int
-TEH_REFRESH_handler_refresh_link (struct TEH_RequestHandler *rh,
-                                  struct MHD_Connection *connection,
-                                  void **connection_cls,
-                                  const char *upload_data,
-                                  size_t *upload_data_size)
+TEH_REFRESH_handler_link (const struct TEH_RequestHandler *rh,
+                          struct MHD_Connection *connection,
+                          const char *const args[2])
 {
-  int mhd_ret;
-  int res;
   struct HTD_Context ctx;
+  int mhd_ret;
 
   (void) rh;
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
   memset (&ctx,
           0,
           sizeof (ctx));
-  res = TALER_MHD_parse_request_arg_data (connection,
-                                          "coin_pub",
-                                          &ctx.coin_pub,
-                                          sizeof (struct
-                                                  TALER_CoinSpendPublicKeyP));
-  if (GNUNET_SYSERR == res)
-    return MHD_NO;
-  if (GNUNET_OK != res)
-    return MHD_YES;
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &ctx.coin_pub,
+                                     sizeof (ctx.coin_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_COINS_INVALID_COIN_PUB,
+                                       "coin public key malformed");
+  }
   ctx.mlist = json_array ();
   if (GNUNET_OK !=
       TEH_DB_run_transaction (connection,
diff --git a/src/exchange/taler-exchange-httpd_refresh_link.h b/src/exchange/taler-exchange-httpd_refresh_link.h
index d0fcff33..9469c471 100644
--- a/src/exchange/taler-exchange-httpd_refresh_link.h
+++ b/src/exchange/taler-exchange-httpd_refresh_link.h
@@ -29,21 +29,17 @@
 
 
 /**
- * Handle a "/refresh/link" request
+ * Handle a "/coins/$COIN_PUB/link" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (length: 2, first is the coin_pub, second must be "link")
  * @return MHD result code
   */
 int
-TEH_REFRESH_handler_refresh_link (struct TEH_RequestHandler *rh,
-                                  struct MHD_Connection *connection,
-                                  void **connection_cls,
-                                  const char *upload_data,
-                                  size_t *upload_data_size);
+TEH_REFRESH_handler_link (const struct TEH_RequestHandler *rh,
+                          struct MHD_Connection *connection,
+                          const char *const args[2]);
 
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_refresh_melt.c b/src/exchange/taler-exchange-httpd_refresh_melt.c
index c7dc700f..9d92a4ce 100644
--- a/src/exchange/taler-exchange-httpd_refresh_melt.c
+++ b/src/exchange/taler-exchange-httpd_refresh_melt.c
@@ -577,31 +577,24 @@ check_for_denomination_key (struct MHD_Connection *connection,
 
 
 /**
- * Handle a "/refresh/melt" request.  Parses the request into the JSON
- * components and then hands things of to #check_for_denomination_key()
- * to validate the melted coins, the signature and execute the melt
- * using handle_refresh_melt().
- *
- * @param rh context of the handler
+ * Handle a "/coins/$COIN_PUB/melt" request.  Parses the request into the JSON
+ * components and then hands things of to #check_for_denomination_key() to
+ * validate the melted coins, the signature and execute the melt using
+ * handle_refresh_melt().
+
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
  * @return MHD result code
  */
 int
-TEH_REFRESH_handler_refresh_melt (struct TEH_RequestHandler *rh,
-                                  struct MHD_Connection *connection,
-                                  void **connection_cls,
-                                  const char *upload_data,
-                                  size_t *upload_data_size)
+TEH_REFRESH_handler_melt (struct MHD_Connection *connection,
+                          const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                          const json_t *root)
 {
-  json_t *root;
   struct RefreshMeltContext rmc;
   int res;
   struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("coin_pub",
-                                 &rmc.refresh_session.coin.coin_pub),
     TALER_JSON_spec_denomination_signature ("denom_sig",
                                             &rmc.refresh_session.coin.denom_sig),
     GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
@@ -615,25 +608,13 @@ TEH_REFRESH_handler_refresh_melt (struct TEH_RequestHandler *rh,
     GNUNET_JSON_spec_end ()
   };
 
-  (void) rh;
-  res = TALER_MHD_parse_post_json (connection,
-                                   connection_cls,
-                                   upload_data,
-                                   upload_data_size,
-                                   &root);
-  if (GNUNET_SYSERR == res)
-    return MHD_NO;
-  if ( (GNUNET_NO == res) ||
-       (NULL == root) )
-    return MHD_YES;
-
   memset (&rmc,
           0,
           sizeof (rmc));
+  rmc.refresh_session.coin.coin_pub = *coin_pub;
   res = TALER_MHD_parse_json_data (connection,
                                    root,
                                    spec);
-  json_decref (root);
   if (GNUNET_OK != res)
     return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
 
diff --git a/src/exchange/taler-exchange-httpd_refresh_melt.h b/src/exchange/taler-exchange-httpd_refresh_melt.h
index c50fdcb4..41488c81 100644
--- a/src/exchange/taler-exchange-httpd_refresh_melt.h
+++ b/src/exchange/taler-exchange-httpd_refresh_melt.h
@@ -29,25 +29,20 @@
 
 
 /**
- * Handle a "/refresh/melt" request after the first parsing has
- * happened.  We now need to validate the coins being melted and the
- * session signature and then hand things of to execute the melt
- * operation.  This function parses the JSON arrays and then passes
- * processing on to #refresh_melt_transaction().
- *
- * @param rh context of the handler
+ * Handle a "/coins/$COIN_PUB/melt" request.  Parses the request into the JSON
+ * components and then hands things of to #check_for_denomination_key() to
+ * validate the melted coins, the signature and execute the melt using
+ * handle_refresh_melt().
+
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
  * @return MHD result code
  */
 int
-TEH_REFRESH_handler_refresh_melt (struct TEH_RequestHandler *rh,
-                                  struct MHD_Connection *connection,
-                                  void **connection_cls,
-                                  const char *upload_data,
-                                  size_t *upload_data_size);
+TEH_REFRESH_handler_melt (struct MHD_Connection *connection,
+                          const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                          const json_t *root);
 
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_refresh_reveal.c b/src/exchange/taler-exchange-httpd_refresh_reveal.c
index 1e03c8e7..802f0a8a 100644
--- a/src/exchange/taler-exchange-httpd_refresh_reveal.c
+++ b/src/exchange/taler-exchange-httpd_refresh_reveal.c
@@ -884,37 +884,33 @@ handle_refresh_reveal_json (struct MHD_Connection *connection,
 
 
 /**
- * Handle a "/refresh/reveal" request. This time, the client reveals the
+ * Handle a "/refreshes/$RCH/reveal" request. This time, the client reveals the
  * private transfer keys except for the cut-and-choose value returned from
- * "/refresh/melt".  This function parses the revealed keys and secrets and
+ * "/coins/$COIN_PUB/melt".  This function parses the revealed keys and secrets and
  * ultimately passes everything to #resolve_refresh_reveal_denominations()
  * which will verify that the revealed information is valid then runs the
  * transaction in #refresh_reveal_transaction() and finally returns the signed
  * refreshed coins.
  *
  * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
+ * @param args array of additional options (length: 2, session hash and the string "reveal")
  * @return MHD result code
-  */
+ */
 int
-TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh,
-                                    struct MHD_Connection *connection,
-                                    void **connection_cls,
-                                    const char *upload_data,
-                                    size_t *upload_data_size)
+TEH_REFRESH_handler_reveal (const struct TEH_RequestHandler *rh,
+                            struct MHD_Connection *connection,
+                            const json_t *root,
+                            const char *const args[2])
 {
   int res;
-  json_t *root;
   json_t *coin_evs;
   json_t *transfer_privs;
   json_t *link_sigs;
   json_t *new_denoms_h;
   struct RevealContext rctx;
   struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("rc", &rctx.rc),
     GNUNET_JSON_spec_fixed_auto ("transfer_pub", &rctx.gamma_tp),
     GNUNET_JSON_spec_json ("transfer_privs", &transfer_privs),
     GNUNET_JSON_spec_json ("link_sigs", &link_sigs),
@@ -924,24 +920,34 @@ TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh,
   };
 
   (void) rh;
-  res = TALER_MHD_parse_post_json (connection,
-                                   connection_cls,
-                                   upload_data,
-                                   upload_data_size,
-                                   &root);
-  if (GNUNET_SYSERR == res)
-    return MHD_NO;
-  if ( (GNUNET_NO == res) ||
-       (NULL == root) )
-    return MHD_YES;
-
   memset (&rctx,
           0,
           sizeof (rctx));
+
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &rctx.rc,
+                                     sizeof (rctx.rc)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_REFRESHES_INVALID_RCH,
+                                       "refresh commitment hash malformed");
+  }
+  if (0 != strcmp (args[1],
+                   "reveal"))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_OPERATION_INVALID,
+                                       "expected 'reveal' operation");
+  }
   res = TALER_MHD_parse_json_data (connection,
                                    root,
                                    spec);
-  json_decref (root);
   if (GNUNET_OK != res)
   {
     GNUNET_break_op (0);
diff --git a/src/exchange/taler-exchange-httpd_refresh_reveal.h b/src/exchange/taler-exchange-httpd_refresh_reveal.h
index 0b0c29b7..afc9adce 100644
--- a/src/exchange/taler-exchange-httpd_refresh_reveal.h
+++ b/src/exchange/taler-exchange-httpd_refresh_reveal.h
@@ -29,26 +29,25 @@
 
 
 /**
- * Handle a "/refresh/reveal" request. This time, the client reveals the
+ * Handle a "/refreshes/$RCH/reveal" request. This time, the client reveals the
  * private transfer keys except for the cut-and-choose value returned from
- * "/refresh/melt".  This function parses the revealed keys and secrets and
+ * "/coins/$COIN_PUB/melt".  This function parses the revealed keys and secrets and
  * ultimately passes everything to #resolve_refresh_reveal_denominations()
  * which will verify that the revealed information is valid then runs the
  * transaction in #refresh_reveal_transaction() and finally returns the signed
  * refreshed coins.
  *
  * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
+ * @param args array of additional options (length: 2, session hash and the string "reveal")
  * @return MHD result code
-  */
+ */
 int
-TEH_REFRESH_handler_refresh_reveal (struct TEH_RequestHandler *rh,
-                                    struct MHD_Connection *connection,
-                                    void **connection_cls,
-                                    const char *upload_data,
-                                    size_t *upload_data_size);
+TEH_REFRESH_handler_reveal (const struct TEH_RequestHandler *rh,
+                            struct MHD_Connection *connection,
+                            const json_t *root,
+                            const char *const args[2]);
+
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_refund.c b/src/exchange/taler-exchange-httpd_refund.c
index 8e24b9b4..e7e34e0b 100644
--- a/src/exchange/taler-exchange-httpd_refund.c
+++ b/src/exchange/taler-exchange-httpd_refund.c
@@ -532,27 +532,21 @@ verify_and_execute_refund (struct MHD_Connection *connection,
 
 
 /**
- * Handle a "/refund" request.  Parses the JSON, and, if successful,
- * passes the JSON data to #verify_and_execute_refund() to
- * further check the details of the operation specified.  If
- * everything checks out, this will ultimately lead to the "/refund"
- * being executed, or rejected.
+ * Handle a "/coins/$COIN_PUB/refund" request.  Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_refund() to further
+ * check the details of the operation specified.  If everything checks out,
+ * this will ultimately lead to the refund being executed, or rejected.
  *
- * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
  * @return MHD result code
   */
 int
-TEH_REFUND_handler_refund (struct TEH_RequestHandler *rh,
-                           struct MHD_Connection *connection,
-                           void **connection_cls,
-                           const char *upload_data,
-                           size_t *upload_data_size)
+TEH_REFUND_handler_refund (struct MHD_Connection *connection,
+                           const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                           const json_t *root)
 {
-  json_t *json;
   int res;
   struct TALER_EXCHANGEDB_Refund refund;
   struct GNUNET_JSON_Specification spec[] = {
@@ -560,7 +554,6 @@ TEH_REFUND_handler_refund (struct TEH_RequestHandler *rh,
     TALER_JSON_spec_amount ("refund_fee", &refund.details.refund_fee),
     GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
                                  &refund.details.h_contract_terms),
-    GNUNET_JSON_spec_fixed_auto ("coin_pub", &refund.coin.coin_pub),
     GNUNET_JSON_spec_fixed_auto ("merchant_pub", &refund.details.merchant_pub),
     GNUNET_JSON_spec_uint64 ("rtransaction_id",
                              &refund.details.rtransaction_id),
@@ -568,20 +561,10 @@ TEH_REFUND_handler_refund (struct TEH_RequestHandler *rh,
     GNUNET_JSON_spec_end ()
   };
 
-  (void) rh;
-  res = TALER_MHD_parse_post_json (connection,
-                                   connection_cls,
-                                   upload_data,
-                                   upload_data_size,
-                                   &json);
-  if (GNUNET_SYSERR == res)
-    return MHD_NO;
-  if ( (GNUNET_NO == res) || (NULL == json) )
-    return MHD_YES;
+  refund.coin.coin_pub = *coin_pub;
   res = TALER_MHD_parse_json_data (connection,
-                                   json,
+                                   root,
                                    spec);
-  json_decref (json);
   if (GNUNET_SYSERR == res)
     return MHD_NO; /* hard failure */
   if (GNUNET_NO == res)
diff --git a/src/exchange/taler-exchange-httpd_refund.h b/src/exchange/taler-exchange-httpd_refund.h
index 4f2b868e..b79419f1 100644
--- a/src/exchange/taler-exchange-httpd_refund.h
+++ b/src/exchange/taler-exchange-httpd_refund.h
@@ -29,24 +29,19 @@
 
 
 /**
- * Handle a "/refund" request.  Parses the JSON, and, if successful,
- * passes the JSON data to #verify_and_execute_refund() to
- * further check the details of the operation specified.  If
- * everything checks out, this will ultimately lead to the "/refund"
- * being executed, or rejected.
+ * Handle a "/coins/$COIN_PUB/refund" request.  Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_refund() to further
+ * check the details of the operation specified.  If everything checks out,
+ * this will ultimately lead to the refund being executed, or rejected.
  *
- * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
  * @return MHD result code
   */
 int
-TEH_REFUND_handler_refund (struct TEH_RequestHandler *rh,
-                           struct MHD_Connection *connection,
-                           void **connection_cls,
-                           const char *upload_data,
-                           size_t *upload_data_size);
+TEH_REFUND_handler_refund (struct MHD_Connection *connection,
+                           const struct TALER_CoinSpendPublicKeyP *coin_pub,
+                           const json_t *root);
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_reserve_status.c b/src/exchange/taler-exchange-httpd_reserve_status.c
index e2d35aae..25127125 100644
--- a/src/exchange/taler-exchange-httpd_reserve_status.c
+++ b/src/exchange/taler-exchange-httpd_reserve_status.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2017 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free Software
@@ -15,7 +15,7 @@
 */
 /**
  * @file taler-exchange-httpd_reserve_status.c
- * @brief Handle /reserve/status requests
+ * @brief Handle /reserves/$RESERVE_PUB GET requests
  * @author Florian Dold
  * @author Benedikt Mueller
  * @author Christian Grothoff
@@ -114,42 +114,37 @@ reserve_status_transaction (void *cls,
 
 
 /**
- * Handle a "/reserve/status" request.  Parses the
- * given "reserve_pub" argument (which should contain the
+ * Handle a GET "/reserves/" request.  Parses the
+ * given "reserve_pub" in @a args (which should contain the
  * EdDSA public key of a reserve) and then respond with the
  * status of the reserve.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (length: 1, just the reserve_pub)
  * @return MHD result code
  */
 int
-TEH_RESERVE_handler_reserve_status (struct TEH_RequestHandler *rh,
+TEH_RESERVE_handler_reserve_status (const struct TEH_RequestHandler *rh,
                                     struct MHD_Connection *connection,
-                                    void **connection_cls,
-                                    const char *upload_data,
-                                    size_t *upload_data_size)
+                                    const char *const args[1])
 {
   struct ReserveStatusContext rsc;
-  int res;
   int mhd_ret;
 
   (void) rh;
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
-  res = TALER_MHD_parse_request_arg_data (connection,
-                                          "reserve_pub",
-                                          &rsc.reserve_pub,
-                                          sizeof (struct
-                                                  TALER_ReservePublicKeyP));
-  if (GNUNET_SYSERR == res)
-    return MHD_NO; /* internal error */
-  if (GNUNET_NO == res)
-    return MHD_YES; /* parse error */
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &rsc.reserve_pub,
+                                     sizeof (rsc.reserve_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_RESERVES_INVALID_RESERVE_PUB,
+                                       "reserve public key malformed");
+  }
   rsc.rh = NULL;
   if (GNUNET_OK !=
       TEH_DB_run_transaction (connection,
diff --git a/src/exchange/taler-exchange-httpd_reserve_status.h b/src/exchange/taler-exchange-httpd_reserve_status.h
index 67eba230..584bd3dc 100644
--- a/src/exchange/taler-exchange-httpd_reserve_status.h
+++ b/src/exchange/taler-exchange-httpd_reserve_status.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2017 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free Software
@@ -15,7 +15,7 @@
 */
 /**
  * @file taler-exchange-httpd_reserve_status.h
- * @brief Handle /reserve/status requests
+ * @brief Handle /reserves/$RESERVE_PUB GET requests
  * @author Florian Dold
  * @author Benedikt Mueller
  * @author Christian Grothoff
@@ -26,24 +26,21 @@
 #include <microhttpd.h>
 #include "taler-exchange-httpd.h"
 
+
 /**
- * Handle a "/reserve/status" request.  Parses the
- * given "reserve_pub" argument (which should contain the
+ * Handle a GET "/reserves/" request.  Parses the
+ * given "reserve_pub" in @a args (which should contain the
  * EdDSA public key of a reserve) and then respond with the
  * status of the reserve.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (length: 1, just the reserve_pub)
  * @return MHD result code
-  */
+ */
 int
-TEH_RESERVE_handler_reserve_status (struct TEH_RequestHandler *rh,
+TEH_RESERVE_handler_reserve_status (const struct TEH_RequestHandler *rh,
                                     struct MHD_Connection *connection,
-                                    void **connection_cls,
-                                    const char *upload_data,
-                                    size_t *upload_data_size);
+                                    const char *const args[1]);
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_reserve_withdraw.c b/src/exchange/taler-exchange-httpd_reserve_withdraw.c
index 9daad0a0..25747eff 100644
--- a/src/exchange/taler-exchange-httpd_reserve_withdraw.c
+++ b/src/exchange/taler-exchange-httpd_reserve_withdraw.c
@@ -335,30 +335,27 @@ withdraw_transaction (void *cls,
 
 
 /**
- * Handle a "/reserve/withdraw" request.  Parses the "reserve_pub"
- * EdDSA key of the reserve and the requested "denom_pub" which
- * specifies the key/value of the coin to be withdrawn, and checks
- * that the signature "reserve_sig" makes this a valid withdrawal
- * request from the specified reserve.  If so, the envelope
- * with the blinded coin "coin_ev" is passed down to execute the
- * withdrawl operation.
+ * Handle a "/reserves/$RESERVE_PUB/withdraw" request.  Parses the
+ * "reserve_pub" EdDSA key of the reserve and the requested "denom_pub" which
+ * specifies the key/value of the coin to be withdrawn, and checks that the
+ * signature "reserve_sig" makes this a valid withdrawal request from the
+ * specified reserve.  If so, the envelope with the blinded coin "coin_ev" is
+ * passed down to execute the withdrawl operation.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param root uploaded JSON data
+ * @param args array of additional options (first must be the
+ *         reserve public key, the second one should be "withdraw")
  * @return MHD result code
  */
 int
-TEH_RESERVE_handler_reserve_withdraw (struct TEH_RequestHandler *rh,
+TEH_RESERVE_handler_reserve_withdraw (const struct TEH_RequestHandler *rh,
                                       struct MHD_Connection *connection,
-                                      void **connection_cls,
-                                      const char *upload_data,
-                                      size_t *upload_data_size)
+                                      const json_t *root,
+                                      const char *const args[2])
 {
   struct WithdrawContext wc;
-  json_t *root;
   int res;
   int mhd_ret;
   unsigned int hc;
@@ -369,8 +366,6 @@ TEH_RESERVE_handler_reserve_withdraw (struct TEH_RequestHandler *rh,
     GNUNET_JSON_spec_varsize ("coin_ev",
                               (void **) &wc.blinded_msg,
                               &wc.blinded_msg_len),
-    GNUNET_JSON_spec_fixed_auto ("reserve_pub",
-                                 &wc.wsrd.reserve_pub),
     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
                                  &wc.signature),
     GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
@@ -379,19 +374,22 @@ TEH_RESERVE_handler_reserve_withdraw (struct TEH_RequestHandler *rh,
   };
 
   (void) rh;
-  res = TALER_MHD_parse_post_json (connection,
-                                   connection_cls,
-                                   upload_data,
-                                   upload_data_size,
-                                   &root);
-  if (GNUNET_SYSERR == res)
-    return MHD_NO;
-  if ( (GNUNET_NO == res) || (NULL == root) )
-    return MHD_YES;
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &wc.wsrd.reserve_pub,
+                                     sizeof (wc.wsrd.reserve_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_RESERVES_INVALID_RESERVE_PUB,
+                                       "reserve public key malformed");
+  }
+
   res = TALER_MHD_parse_json_data (connection,
                                    root,
                                    spec);
-  json_decref (root);
   if (GNUNET_OK != res)
     return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
   wc.key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ());
diff --git a/src/exchange/taler-exchange-httpd_reserve_withdraw.h b/src/exchange/taler-exchange-httpd_reserve_withdraw.h
index 67b4cad0..c3e56eaa 100644
--- a/src/exchange/taler-exchange-httpd_reserve_withdraw.h
+++ b/src/exchange/taler-exchange-httpd_reserve_withdraw.h
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014-2017 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU Affero General Public License as published by the Free Software
@@ -28,26 +28,24 @@
 
 
 /**
- * Handle a "/reserve/withdraw" request.  Parses the "reserve_pub"
- * EdDSA key of the reserve and the requested "denom_pub" which
- * specifies the key/value of the coin to be withdrawn, and checks
- * that the signature "reserve_sig" makes this a valid withdrawl
- * request from the specified reserve.  If so, the envelope
- * with the blinded coin "coin_ev" is passed down to execute the
- * withdrawl operation.
+ * Handle a "/reserves/$RESERVE_PUB/withdraw" request.  Parses the
+ * "reserve_pub" EdDSA key of the reserve and the requested "denom_pub" which
+ * specifies the key/value of the coin to be withdrawn, and checks that the
+ * signature "reserve_sig" makes this a valid withdrawl request from the
+ * specified reserve.  If so, the envelope with the blinded coin "coin_ev" is
+ * passed down to execute the withdrawl operation.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param root uploaded JSON data
+ * @param args array of additional options (first must be the
+ *         reserve public key, the second one should be "withdraw")
  * @return MHD result code
   */
 int
-TEH_RESERVE_handler_reserve_withdraw (struct TEH_RequestHandler *rh,
+TEH_RESERVE_handler_reserve_withdraw (const struct TEH_RequestHandler *rh,
                                       struct MHD_Connection *connection,
-                                      void **connection_cls,
-                                      const char *upload_data,
-                                      size_t *upload_data_size);
+                                      const json_t *root,
+                                      const char *const args[2]);
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_terms.c b/src/exchange/taler-exchange-httpd_terms.c
index 47905f60..121e1c78 100644
--- a/src/exchange/taler-exchange-httpd_terms.c
+++ b/src/exchange/taler-exchange-httpd_terms.c
@@ -43,22 +43,16 @@ static struct TALER_MHD_Legal *pp;
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_handler_terms (struct TEH_RequestHandler *rh,
+TEH_handler_terms (const struct TEH_RequestHandler *rh,
                    struct MHD_Connection *connection,
-                   void **connection_cls,
-                   const char *upload_data,
-                   size_t *upload_data_size)
+                   const char *const args[])
 {
   (void) rh;
-  (void) upload_data;
-  (void) upload_data_size;
-  (void) connection_cls;
+  (void) args;
   return TALER_MHD_reply_legal (connection,
                                 tos);
 }
@@ -69,22 +63,16 @@ TEH_handler_terms (struct TEH_RequestHandler *rh,
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_handler_privacy (struct TEH_RequestHandler *rh,
+TEH_handler_privacy (const struct TEH_RequestHandler *rh,
                      struct MHD_Connection *connection,
-                     void **connection_cls,
-                     const char *upload_data,
-                     size_t *upload_data_size)
+                     const char *const args[])
 {
   (void) rh;
-  (void) upload_data;
-  (void) upload_data_size;
-  (void) connection_cls;
+  (void) args;
   return TALER_MHD_reply_legal (connection,
                                 pp);
 }
diff --git a/src/exchange/taler-exchange-httpd_terms.h b/src/exchange/taler-exchange-httpd_terms.h
index 75909df9..7fe7774a 100644
--- a/src/exchange/taler-exchange-httpd_terms.h
+++ b/src/exchange/taler-exchange-httpd_terms.h
@@ -34,34 +34,27 @@
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_handler_terms (struct TEH_RequestHandler *rh,
+TEH_handler_terms (const struct TEH_RequestHandler *rh,
                    struct MHD_Connection *connection,
-                   void **connection_cls,
-                   const char *upload_data,
-                   size_t *upload_data_size);
+                   const char *const args[]);
+
 
 /**
  * Handle a "/privacy" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
  */
 int
-TEH_handler_privacy (struct TEH_RequestHandler *rh,
+TEH_handler_privacy (const struct TEH_RequestHandler *rh,
                      struct MHD_Connection *connection,
-                     void **connection_cls,
-                     const char *upload_data,
-                     size_t *upload_data_size);
+                     const char *const args[]);
 
 
 /**
diff --git a/src/exchange/taler-exchange-httpd_track_transaction.c b/src/exchange/taler-exchange-httpd_track_transaction.c
index e8143213..d0f1d0aa 100644
--- a/src/exchange/taler-exchange-httpd_track_transaction.c
+++ b/src/exchange/taler-exchange-httpd_track_transaction.c
@@ -336,62 +336,86 @@ check_and_handle_track_transaction_request (struct MHD_Connection *connection,
 
 
 /**
- * Handle a "/track/transaction" request.
+ * Handle a "/deposits/$H_WIRE/$MERCHANT_PUB/$H_CONTRACT_TERMS/$COIN_PUB"
+ * request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (length: 4, contains:
+ *      h_wire, merchant_pub, h_contract_terms and coin_pub)
  * @return MHD result code
- */
+  */
 int
-TEH_TRACKING_handler_track_transaction (struct TEH_RequestHandler *rh,
+TEH_TRACKING_handler_track_transaction (const struct TEH_RequestHandler *rh,
                                         struct MHD_Connection *connection,
-                                        void **connection_cls,
-                                        const char *upload_data,
-                                        size_t *upload_data_size)
+                                        const char *const args[4])
 {
   int res;
-  json_t *json;
   struct TALER_DepositTrackPS tps;
   struct TALER_MerchantSignatureP merchant_sig;
-  struct GNUNET_JSON_Specification spec[] = {
-    GNUNET_JSON_spec_fixed_auto ("h_wire", &tps.h_wire),
-    GNUNET_JSON_spec_fixed_auto ("h_contract_terms", &tps.h_contract_terms),
-    GNUNET_JSON_spec_fixed_auto ("coin_pub", &tps.coin_pub),
-    GNUNET_JSON_spec_fixed_auto ("merchant_pub", &tps.merchant),
-    GNUNET_JSON_spec_fixed_auto ("merchant_sig", &merchant_sig),
-    GNUNET_JSON_spec_end ()
-  };
-
-  (void) rh;
-  res = TALER_MHD_parse_post_json (connection,
-                                   connection_cls,
-                                   upload_data,
-                                   upload_data_size,
-                                   &json);
-  if (GNUNET_SYSERR == res)
-    return MHD_NO;
-  if ( (GNUNET_NO == res) || (NULL == json) )
-    return MHD_YES;
-  res = TALER_MHD_parse_json_data (connection,
-                                   json,
-                                   spec);
-  if (GNUNET_OK != res)
+
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &tps.h_wire,
+                                     sizeof (tps.h_wire)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_DEPOSITS_INVALID_H_WIRE,
+                                       "wire hash malformed");
+  }
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[1],
+                                     strlen (args[1]),
+                                     &tps.merchant,
+                                     sizeof (tps.merchant)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_DEPOSITS_INVALID_MERCHANT_PUB,
+                                       "merchant public key malformed");
+  }
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[2],
+                                     strlen (args[2]),
+                                     &tps.h_contract_terms,
+                                     sizeof (tps.h_contract_terms)))
   {
-    json_decref (json);
-    return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_DEPOSITS_INVALID_H_CONTRACT_TERMS,
+                                       "contract terms hash malformed");
   }
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[3],
+                                     strlen (args[3]),
+                                     &tps.coin_pub,
+                                     sizeof (tps.coin_pub)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_DEPOSITS_INVALID_COIN_PUB,
+                                       "coin public key malformed");
+  }
+  res = TALER_MHD_parse_request_arg_data (connection,
+                                          "merchant_sig",
+                                          &merchant_sig,
+                                          sizeof (merchant_sig));
+  if (GNUNET_SYSERR == res)
+    return MHD_NO; /* internal error */
+  if (GNUNET_NO == res)
+    return MHD_YES; /* parse error */
   tps.purpose.size = htonl (sizeof (struct TALER_DepositTrackPS));
   tps.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_TRACK_TRANSACTION);
-  res = check_and_handle_track_transaction_request (connection,
-                                                    &tps,
-                                                    &tps.merchant,
-                                                    &merchant_sig);
-  GNUNET_JSON_parse_free (spec);
-  json_decref (json);
-  return res;
+  return check_and_handle_track_transaction_request (connection,
+                                                     &tps,
+                                                     &tps.merchant,
+                                                     &merchant_sig);
 }
 
 
diff --git a/src/exchange/taler-exchange-httpd_track_transaction.h b/src/exchange/taler-exchange-httpd_track_transaction.h
index 929ee638..5f54754f 100644
--- a/src/exchange/taler-exchange-httpd_track_transaction.h
+++ b/src/exchange/taler-exchange-httpd_track_transaction.h
@@ -27,21 +27,19 @@
 
 
 /**
- * Handle a "/track/transaction" request.
+ * Handle a "/deposits/$H_WIRE/$MERCHANT_PUB/$H_CONTRACT_TERMS/$COIN_PUB"
+ * request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (length: 4, contains:
+ *      h_wire, merchant_pub, h_contract_terms and coin_pub)
  * @return MHD result code
   */
 int
-TEH_TRACKING_handler_track_transaction (struct TEH_RequestHandler *rh,
+TEH_TRACKING_handler_track_transaction (const struct TEH_RequestHandler *rh,
                                         struct MHD_Connection *connection,
-                                        void **connection_cls,
-                                        const char *upload_data,
-                                        size_t *upload_data_size);
+                                        const char *const args[4]);
 
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_track_transfer.c b/src/exchange/taler-exchange-httpd_track_transfer.c
index 1a780c06..cff6045e 100644
--- a/src/exchange/taler-exchange-httpd_track_transfer.c
+++ b/src/exchange/taler-exchange-httpd_track_transfer.c
@@ -482,40 +482,37 @@ free_ctx (struct WtidTransactionContext *ctx)
 
 
 /**
- * Handle a "/track/transfer" request.
+ * Handle a GET "/transfers/$WTID" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (length: 1, just the wtid)
  * @return MHD result code
  */
 int
-TEH_TRACKING_handler_track_transfer (struct TEH_RequestHandler *rh,
+TEH_TRACKING_handler_track_transfer (const struct TEH_RequestHandler *rh,
                                      struct MHD_Connection *connection,
-                                     void **connection_cls,
-                                     const char *upload_data,
-                                     size_t *upload_data_size)
+                                     const char *const args[1])
 {
   struct WtidTransactionContext ctx;
-  int res;
   int mhd_ret;
 
   (void) rh;
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
-  memset (&ctx, 0, sizeof (ctx));
-  res = TALER_MHD_parse_request_arg_data (connection,
-                                          "wtid",
-                                          &ctx.wtid,
-                                          sizeof (struct
-                                                  TALER_WireTransferIdentifierRawP));
-  if (GNUNET_SYSERR == res)
-    return MHD_NO; /* internal error */
-  if (GNUNET_NO == res)
-    return MHD_YES; /* parse error */
+  memset (&ctx,
+          0,
+          sizeof (ctx));
+  if (GNUNET_OK !=
+      GNUNET_STRINGS_string_to_data (args[0],
+                                     strlen (args[0]),
+                                     &ctx.wtid,
+                                     sizeof (ctx.wtid)))
+  {
+    GNUNET_break_op (0);
+    return TALER_MHD_reply_with_error (connection,
+                                       MHD_HTTP_BAD_REQUEST,
+                                       TALER_EC_TRANSFERS_INVALID_WTID,
+                                       "wire transfer identifier malformed");
+  }
   if (GNUNET_OK !=
       TEH_DB_run_transaction (connection,
                               "run track transfer",
diff --git a/src/exchange/taler-exchange-httpd_track_transfer.h b/src/exchange/taler-exchange-httpd_track_transfer.h
index c68cb288..c6bd7c5d 100644
--- a/src/exchange/taler-exchange-httpd_track_transfer.h
+++ b/src/exchange/taler-exchange-httpd_track_transfer.h
@@ -27,20 +27,17 @@
 
 
 /**
- * Handle a "/track/transfer" request.
+ * Handle a GET "/transfers/$WTID" request.
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (length: 1, just the wtid)
  * @return MHD result code
  */
 int
-TEH_TRACKING_handler_track_transfer (struct TEH_RequestHandler *rh,
+TEH_TRACKING_handler_track_transfer (const struct TEH_RequestHandler *rh,
                                      struct MHD_Connection *connection,
-                                     void **connection_cls,
-                                     const char *upload_data,
-                                     size_t *upload_data_size);
+                                     const char *const args[1]);
+
 
 #endif
diff --git a/src/exchange/taler-exchange-httpd_validation.c b/src/exchange/taler-exchange-httpd_validation.c
index e3dd8e86..e55100e1 100644
--- a/src/exchange/taler-exchange-httpd_validation.c
+++ b/src/exchange/taler-exchange-httpd_validation.c
@@ -25,7 +25,6 @@
 #include "taler-exchange-httpd_validation.h"
 #include "taler-exchange-httpd_wire.h"
 #include "taler_exchangedb_lib.h"
-#include "taler_json_lib.h"
 
 
 /**
diff --git a/src/exchange/taler-exchange-httpd_wire.c b/src/exchange/taler-exchange-httpd_wire.c
index e4bcbec5..de4e2db4 100644
--- a/src/exchange/taler-exchange-httpd_wire.c
+++ b/src/exchange/taler-exchange-httpd_wire.c
@@ -124,22 +124,16 @@ TEH_WIRE_get_fees (const char *method)
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
   */
 int
-TEH_WIRE_handler_wire (struct TEH_RequestHandler *rh,
+TEH_WIRE_handler_wire (const struct TEH_RequestHandler *rh,
                        struct MHD_Connection *connection,
-                       void **connection_cls,
-                       const char *upload_data,
-                       size_t *upload_data_size)
+                       const char *const args[])
 {
   (void) rh;
-  (void) connection_cls;
-  (void) upload_data;
-  (void) upload_data_size;
+  (void) args;
   GNUNET_assert (NULL != wire_methods);
   return TALER_MHD_reply_json (connection,
                                wire_methods,
diff --git a/src/exchange/taler-exchange-httpd_wire.h b/src/exchange/taler-exchange-httpd_wire.h
index 75c60353..ac4ea39c 100644
--- a/src/exchange/taler-exchange-httpd_wire.h
+++ b/src/exchange/taler-exchange-httpd_wire.h
@@ -51,17 +51,13 @@ TEH_WIRE_get_fees (const char *method);
  *
  * @param rh context of the handler
  * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param args array of additional options (must be empty for this function)
  * @return MHD result code
-  */
+ */
 int
-TEH_WIRE_handler_wire (struct TEH_RequestHandler *rh,
+TEH_WIRE_handler_wire (const struct TEH_RequestHandler *rh,
                        struct MHD_Connection *connection,
-                       void **connection_cls,
-                       const char *upload_data,
-                       size_t *upload_data_size);
+                       const char *const args[]);
 
 
 #endif
diff --git a/src/include/taler_error_codes.h b/src/include/taler_error_codes.h
index 917ac36d..1c48fe33 100644
--- a/src/include/taler_error_codes.h
+++ b/src/include/taler_error_codes.h
@@ -85,6 +85,32 @@ enum TALER_ErrorCode
    */
   TALER_EC_METHOD_INVALID = 8,
 
+  /**
+   * Operation specified invalid for this URL (resulting in a "NOT
+   * FOUND" for the overall response).
+   */
+  TALER_EC_OPERATION_INVALID = 9,
+
+  /**
+   * There is no endpoint defined for the URL provided by the client
+   * (returned together with a MHD_HTTP_NOT FOUND status code).
+   */
+  TALER_EC_ENDPOINT_UNKNOWN = 10,
+
+  /**
+   * The URI is longer than the longest URI the HTTP server is willing
+   * to parse. Returned together with an HTTP status code of
+   * MHD_HTTP_URI_TOO_LONG.
+   */
+  TALER_EC_URI_TOO_LONG = 11,
+
+  /**
+   * The number of segments included in the URI does not match the
+   * number of segments expected by the endpoint. (returned together
+   * with a MHD_HTTP_NOT FOUND status code).
+   */
+  TALER_EC_WRONG_NUMBER_OF_SEGMENTS = 12,
+
   /**
    * The exchange failed to even just initialize its connection to the
    * database.  This response is provided with HTTP status code
@@ -181,6 +207,50 @@ enum TALER_ErrorCode
    */
   TALER_EC_DB_COIN_HISTORY_STORE_ERROR = 1014,
 
+  /**
+   * The public key of given to a /coins/ handler was malformed.
+   */
+  TALER_EC_COINS_INVALID_COIN_PUB = 1050,
+
+  /**
+   * The public key of given to a /reserves/ handler was malformed.
+   */
+  TALER_EC_RESERVES_INVALID_RESERVE_PUB = 1051,
+
+  /**
+   * The public key of given to a /transfers/ handler was malformed.
+   */
+  TALER_EC_TRANSFERS_INVALID_WTID = 1052,
+
+  /**
+   * The hash of the wire details of given to a /deposits/ handler was
+   * malformed.
+   */
+  TALER_EC_DEPOSITS_INVALID_H_WIRE = 1053,
+
+  /**
+   * The merchant public key given to a /deposits/ handler was
+   * malformed.
+   */
+  TALER_EC_DEPOSITS_INVALID_MERCHANT_PUB = 1054,
+
+  /**
+   * The hash of the contract given to a /deposits/ handler was
+   * malformed.
+   */
+  TALER_EC_DEPOSITS_INVALID_H_CONTRACT_TERMS = 1055,
+
+  /**
+   * The coin public key given to a /deposits/ handler was malformed.
+   */
+  TALER_EC_DEPOSITS_INVALID_COIN_PUB = 1056,
+
+  /**
+   * The hash of the refresh commitment given to a /refreshes/ handler
+   * was malformed.
+   */
+  TALER_EC_REFRESHES_INVALID_RCH = 1057,
+
   /**
    * The given reserve does not have sufficient funds to admit the
    * requested withdraw operation at this time.  The response includes
diff --git a/src/lib/exchange_api_deposit.c b/src/lib/exchange_api_deposit.c
index 20a87c33..06eeb6a2 100644
--- a/src/lib/exchange_api_deposit.c
+++ b/src/lib/exchange_api_deposit.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014, 2015, 2018, 2019 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -513,7 +513,23 @@ TALER_EXCHANGE_deposit (struct TALER_EXCHANGE_Handle *exchange,
   struct GNUNET_HashCode h_wire;
   struct GNUNET_HashCode denom_pub_hash;
   struct TALER_Amount amount_without_fee;
+  char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
 
+  {
+    char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (coin_pub,
+                                         sizeof (struct
+                                                 TALER_CoinSpendPublicKeyP),
+                                         pub_str,
+                                         sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/coins/%s/deposit",
+                     pub_str);
+  }
   (void) GNUNET_TIME_round_abs (&wire_deadline);
   (void) GNUNET_TIME_round_abs (&refund_deadline);
   GNUNET_assert (refund_deadline.abs_value_us <= wire_deadline.abs_value_us);
@@ -557,7 +573,7 @@ TALER_EXCHANGE_deposit (struct TALER_EXCHANGE_Handle *exchange,
 
   deposit_obj = json_pack ("{s:o, s:O," /* f/wire */
                            " s:o, s:o," /* h_wire, h_contract_terms */
-                           " s:o, s:o," /* coin_pub, denom_pub */
+                           " s:o," /* denom_pub */
                            " s:o, s:o," /* ub_sig, timestamp */
                            " s:o," /* merchant_pub */
                            " s:o, s:o," /* refund_deadline, wire_deadline */
@@ -567,7 +583,6 @@ TALER_EXCHANGE_deposit (struct TALER_EXCHANGE_Handle *exchange,
                            "h_wire", GNUNET_JSON_from_data_auto (&h_wire),
                            "h_contract_terms", GNUNET_JSON_from_data_auto (
                              h_contract_terms),
-                           "coin_pub", GNUNET_JSON_from_data_auto (coin_pub),
                            "denom_pub_hash", GNUNET_JSON_from_data_auto (
                              &denom_pub_hash),
                            "ub_sig", GNUNET_JSON_from_rsa_signature (
@@ -592,7 +607,8 @@ TALER_EXCHANGE_deposit (struct TALER_EXCHANGE_Handle *exchange,
   dh->exchange = exchange;
   dh->cb = cb;
   dh->cb_cls = cb_cls;
-  dh->url = TEAH_path_to_url (exchange, "/deposit");
+  dh->url = TEAH_path_to_url (exchange,
+                              arg_str);
   dh->depconf.purpose.size = htonl (sizeof (struct
                                             TALER_DepositConfirmationPS));
   dh->depconf.purpose.purpose = htonl (
diff --git a/src/lib/exchange_api_recoup.c b/src/lib/exchange_api_recoup.c
index 1a332ad3..a31d5b40 100644
--- a/src/lib/exchange_api_recoup.c
+++ b/src/lib/exchange_api_recoup.c
@@ -328,6 +328,7 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
   struct GNUNET_HashCode h_denom_pub;
   json_t *recoup_obj;
   CURL *eh;
+  char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
 
   GNUNET_assert (GNUNET_YES ==
                  TEAH_handle_is_ready (exchange));
@@ -345,14 +346,12 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
                                            &coin_sig.eddsa_signature));
 
   recoup_obj = json_pack ("{s:o, s:o," /* denom pub/sig */
-                          " s:o, s:o,"  /* coin pub/sig */
+                          " s:o,"  /* sig */
                           " s:o, s:o}",  /* coin_bks */
                           "denom_pub_hash", GNUNET_JSON_from_data_auto (
                             &h_denom_pub),
                           "denom_sig", GNUNET_JSON_from_rsa_signature (
                             denom_sig->rsa_signature),
-                          "coin_pub", GNUNET_JSON_from_data_auto (
-                            &pr.coin_pub),
                           "coin_sig", GNUNET_JSON_from_data_auto (&coin_sig),
                           "coin_blind_key_secret", GNUNET_JSON_from_data_auto (
                             &ps->blinding_key),
@@ -364,6 +363,22 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
     return NULL;
   }
 
+  {
+    char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (&pr.coin_pub,
+                                         sizeof (struct
+                                                 TALER_CoinSpendPublicKeyP),
+                                         pub_str,
+                                         sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/coins/%s/recoup",
+                     pub_str);
+  }
+
   ph = GNUNET_new (struct TALER_EXCHANGE_RecoupHandle);
   ph->coin_pub = pr.coin_pub;
   ph->exchange = exchange;
@@ -371,7 +386,8 @@ TALER_EXCHANGE_recoup (struct TALER_EXCHANGE_Handle *exchange,
   ph->pk.key.rsa_public_key = NULL; /* zero out, as lifetime cannot be warranted */
   ph->cb = recoup_cb;
   ph->cb_cls = recoup_cb_cls;
-  ph->url = TEAH_path_to_url (exchange, "/recoup");
+  ph->url = TEAH_path_to_url (exchange,
+                              arg_str);
   ph->was_refreshed = was_refreshed;
   eh = TEL_curl_easy_get (ph->url);
   if (GNUNET_OK !=
diff --git a/src/lib/exchange_api_refresh.c b/src/lib/exchange_api_refresh.c
index e097ee3f..eb26b7c1 100644
--- a/src/lib/exchange_api_refresh.c
+++ b/src/lib/exchange_api_refresh.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2015, 2016, 2017, 2019 Taler Systems SA
+  Copyright (C) 2015-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -1165,6 +1165,7 @@ TALER_EXCHANGE_refresh_melt (struct TALER_EXCHANGE_Handle *exchange,
   struct TALER_CoinSpendSignatureP confirm_sig;
   struct TALER_RefreshMeltCoinAffirmationPS melt;
   struct GNUNET_HashCode h_denom_pub;
+  char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
 
   GNUNET_assert (GNUNET_YES ==
                  TEAH_handle_is_ready (exchange));
@@ -1175,7 +1176,6 @@ TALER_EXCHANGE_refresh_melt (struct TALER_EXCHANGE_Handle *exchange,
     GNUNET_break (0);
     return NULL;
   }
-
   melt.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_MELT);
   melt.purpose.size = htonl (sizeof (struct
                                      TALER_RefreshMeltCoinAffirmationPS));
@@ -1212,6 +1212,22 @@ TALER_EXCHANGE_refresh_melt (struct TALER_EXCHANGE_Handle *exchange,
     free_melt_data (md);
     return NULL;
   }
+  {
+    char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (&melt.coin_pub,
+                                         sizeof (struct
+                                                 TALER_CoinSpendPublicKeyP),
+                                         pub_str,
+                                         sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/coins/%s/melt",
+                     pub_str);
+  }
+
   key_state = TALER_EXCHANGE_get_keys (exchange);
   dki = TALER_EXCHANGE_get_denomination_key (key_state,
                                              &md->melted_coin.pub_key);
@@ -1226,7 +1242,7 @@ TALER_EXCHANGE_refresh_melt (struct TALER_EXCHANGE_Handle *exchange,
   rmh->melt_cb_cls = melt_cb_cls;
   rmh->md = md;
   rmh->url = TEAH_path_to_url (exchange,
-                               "/refresh/melt");
+                               arg_str);
   eh = TEL_curl_easy_get (rmh->url);
   if (GNUNET_OK !=
       TALER_curl_easy_post (&rmh->ctx,
@@ -1274,11 +1290,11 @@ TALER_EXCHANGE_refresh_melt_cancel (struct
 }
 
 
-/* ********************* /refresh/reveal ***************************** */
+/* ********************* /refreshes/$RCH/reveal ***************************** */
 
 
 /**
- * @brief A /refresh/reveal Handle
+ * @brief A /refreshes/$RCH/reveal Handle
  */
 struct TALER_EXCHANGE_RefreshRevealHandle
 {
@@ -1328,7 +1344,7 @@ struct TALER_EXCHANGE_RefreshRevealHandle
 
 
 /**
- * We got a 200 OK response for the /refresh/reveal operation.
+ * We got a 200 OK response for the /refreshes/$RCH/reveal operation.
  * Extract the coin signatures and return them to the caller.
  * The signatures we get from the exchange is for the blinded value.
  * Thus, we first must unblind them and then should verify their
@@ -1433,7 +1449,7 @@ refresh_reveal_ok (struct TALER_EXCHANGE_RefreshRevealHandle *rrh,
 
 /**
  * Function called when we're done processing the
- * HTTP /refresh/reveal request.
+ * HTTP /refreshes/$RCH/reveal request.
  *
  * @param cls the `struct TALER_EXCHANGE_RefreshHandle`
  * @param response_code HTTP response code, 0 on error
@@ -1555,6 +1571,7 @@ TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange,
   struct GNUNET_CURL_Context *ctx;
   struct MeltData *md;
   struct TALER_TransferPublicKeyP transfer_pub;
+  char arg_str[sizeof (struct TALER_RefreshCommitmentP) * 2 + 32];
 
   if (noreveal_index >= TALER_CNC_KAPPA)
   {
@@ -1661,9 +1678,7 @@ TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange,
   }
 
   /* build main JSON request */
-  reveal_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o, s:o}",
-                          "rc",
-                          GNUNET_JSON_from_data_auto (&md->rc),
+  reveal_obj = json_pack ("{s:o, s:o, s:o, s:o, s:o}",
                           "transfer_pub",
                           GNUNET_JSON_from_data_auto (&transfer_pub),
                           "transfer_privs",
@@ -1680,6 +1695,21 @@ TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange,
     return NULL;
   }
 
+  {
+    char pub_str[sizeof (struct TALER_RefreshCommitmentP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (&md->rc,
+                                         sizeof (struct
+                                                 TALER_RefreshCommitmentP),
+                                         pub_str,
+                                         sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/refreshes/%s/reveal",
+                     pub_str);
+  }
   /* finally, we can actually issue the request */
   rrh = GNUNET_new (struct TALER_EXCHANGE_RefreshRevealHandle);
   rrh->exchange = exchange;
@@ -1688,7 +1718,7 @@ TALER_EXCHANGE_refresh_reveal (struct TALER_EXCHANGE_Handle *exchange,
   rrh->reveal_cb_cls = reveal_cb_cls;
   rrh->md = md;
   rrh->url = TEAH_path_to_url (rrh->exchange,
-                               "/refresh/reveal");
+                               arg_str);
 
   eh = TEL_curl_easy_get (rrh->url);
   if (GNUNET_OK !=
diff --git a/src/lib/exchange_api_refresh_link.c b/src/lib/exchange_api_refresh_link.c
index 6a747d1b..4b4b38ba 100644
--- a/src/lib/exchange_api_refresh_link.c
+++ b/src/lib/exchange_api_refresh_link.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2015, 2016, 2019 Taler Systems SA
+  Copyright (C) 2015-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -423,8 +423,7 @@ TALER_EXCHANGE_refresh_link (struct TALER_EXCHANGE_Handle *exchange,
   CURL *eh;
   struct GNUNET_CURL_Context *ctx;
   struct TALER_CoinSpendPublicKeyP coin_pub;
-  char *pub_str;
-  char *arg_str;
+  char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
 
   if (GNUNET_YES !=
       TEAH_handle_is_ready (exchange))
@@ -435,23 +434,28 @@ TALER_EXCHANGE_refresh_link (struct TALER_EXCHANGE_Handle *exchange,
 
   GNUNET_CRYPTO_eddsa_key_get_public (&coin_priv->eddsa_priv,
                                       &coin_pub.eddsa_pub);
-  pub_str = GNUNET_STRINGS_data_to_string_alloc (&coin_pub,
-                                                 sizeof (struct
-                                                         TALER_CoinSpendPublicKeyP));
-  GNUNET_asprintf (&arg_str,
-                   "/refresh/link?coin_pub=%s",
-                   pub_str);
-  GNUNET_free (pub_str);
-
+  {
+    char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (&coin_pub,
+                                         sizeof (struct
+                                                 TALER_CoinSpendPublicKeyP),
+                                         pub_str,
+                                         sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/coins/%s/link",
+                     pub_str);
+  }
   rlh = GNUNET_new (struct TALER_EXCHANGE_RefreshLinkHandle);
   rlh->exchange = exchange;
   rlh->link_cb = link_cb;
   rlh->link_cb_cls = link_cb_cls;
   rlh->coin_priv = *coin_priv;
-  rlh->url = TEAH_path_to_url (exchange, arg_str);
-  GNUNET_free (arg_str);
-
-
+  rlh->url = TEAH_path_to_url (exchange,
+                               arg_str);
   eh = TEL_curl_easy_get (rlh->url);
   ctx = TEAH_handle_to_context (exchange);
   rlh->job = GNUNET_CURL_job_add (ctx,
diff --git a/src/lib/exchange_api_refund.c b/src/lib/exchange_api_refund.c
index ef9a4359..8f2c0c4d 100644
--- a/src/lib/exchange_api_refund.c
+++ b/src/lib/exchange_api_refund.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014, 2015, 2016 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -333,16 +333,31 @@ TALER_EXCHANGE_refund2 (struct TALER_EXCHANGE_Handle *exchange,
   struct GNUNET_CURL_Context *ctx;
   json_t *refund_obj;
   CURL *eh;
+  char arg_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2 + 32];
 
+  {
+    char pub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (coin_pub,
+                                         sizeof (struct
+                                                 TALER_CoinSpendPublicKeyP),
+                                         pub_str,
+                                         sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/coins/%s/refund",
+                     pub_str);
+  }
   refund_obj = json_pack ("{s:o, s:o," /* amount/fee */
-                          " s:o, s:o," /* h_contract_terms, coin_pub */
+                          " s:o," /* h_contract_terms */
                           " s:I," /* rtransaction id */
                           " s:o, s:o}", /* merchant_pub, merchant_sig */
                           "refund_amount", TALER_JSON_from_amount (amount),
                           "refund_fee", TALER_JSON_from_amount (refund_fee),
                           "h_contract_terms", GNUNET_JSON_from_data_auto (
                             h_contract_terms),
-                          "coin_pub", GNUNET_JSON_from_data_auto (coin_pub),
                           "rtransaction_id", (json_int_t) rtransaction_id,
                           "merchant_pub", GNUNET_JSON_from_data_auto (
                             merchant_pub),
@@ -359,7 +374,8 @@ TALER_EXCHANGE_refund2 (struct TALER_EXCHANGE_Handle *exchange,
   rh->exchange = exchange;
   rh->cb = cb;
   rh->cb_cls = cb_cls;
-  rh->url = TEAH_path_to_url (exchange, "/refund");
+  rh->url = TEAH_path_to_url (exchange,
+                              arg_str);
   rh->depconf.purpose.size = htonl (sizeof (struct TALER_RefundConfirmationPS));
   rh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND);
   rh->depconf.h_contract_terms = *h_contract_terms;
diff --git a/src/lib/exchange_api_reserve.c b/src/lib/exchange_api_reserve.c
index 710cd588..c0dd66e8 100644
--- a/src/lib/exchange_api_reserve.c
+++ b/src/lib/exchange_api_reserve.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014, 2015 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -659,8 +659,7 @@ TALER_EXCHANGE_reserve_status (struct TALER_EXCHANGE_Handle *exchange,
   struct TALER_EXCHANGE_ReserveStatusHandle *rsh;
   struct GNUNET_CURL_Context *ctx;
   CURL *eh;
-  char *pub_str;
-  char *arg_str;
+  char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 16];
 
   if (GNUNET_YES !=
       TEAH_handle_is_ready (exchange))
@@ -668,13 +667,21 @@ TALER_EXCHANGE_reserve_status (struct TALER_EXCHANGE_Handle *exchange,
     GNUNET_break (0);
     return NULL;
   }
-  pub_str = GNUNET_STRINGS_data_to_string_alloc (reserve_pub,
-                                                 sizeof (struct
-                                                         TALER_ReservePublicKeyP));
-  GNUNET_asprintf (&arg_str,
-                   "/reserve/status?reserve_pub=%s",
-                   pub_str);
-  GNUNET_free (pub_str);
+  {
+    char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (reserve_pub,
+                                         sizeof (struct
+                                                 TALER_ReservePublicKeyP),
+                                         pub_str,
+                                         sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/reserves/%s",
+                     pub_str);
+  }
   rsh = GNUNET_new (struct TALER_EXCHANGE_ReserveStatusHandle);
   rsh->exchange = exchange;
   rsh->cb = cb;
@@ -682,8 +689,6 @@ TALER_EXCHANGE_reserve_status (struct TALER_EXCHANGE_Handle *exchange,
   rsh->reserve_pub = *reserve_pub;
   rsh->url = TEAH_path_to_url (exchange,
                                arg_str);
-  GNUNET_free (arg_str);
-
   eh = TEL_curl_easy_get (rsh->url);
   ctx = TEAH_handle_to_context (exchange);
   rsh->job = GNUNET_CURL_job_add (ctx,
@@ -778,11 +783,10 @@ struct TALER_EXCHANGE_ReserveWithdrawHandle
 
 
 /**
- * We got a 200 OK response for the /reserve/withdraw operation.
- * Extract the coin's signature and return it to the caller.
- * The signature we get from the exchange is for the blinded value.
- * Thus, we first must unblind it and then should verify its
- * validity against our coin's hash.
+ * We got a 200 OK response for the /reserves/$RESERVE_PUB/withdraw operation.
+ * Extract the coin's signature and return it to the caller.  The signature we
+ * get from the exchange is for the blinded value.  Thus, we first must
+ * unblind it and then should verify its validity against our coin's hash.
  *
  * If everything checks out, we return the unblinded signature
  * to the application via the callback.
@@ -838,7 +842,7 @@ reserve_withdraw_ok (struct TALER_EXCHANGE_ReserveWithdrawHandle *wsh,
 
 
 /**
- * We got a 409 CONFLICT response for the /reserve/withdraw operation.
+ * We got a 409 CONFLICT response for the /reserves/$RESERVE_PUB/withdraw operation.
  * Check the signatures on the withdraw transactions in the provided
  * history and that the balances add up.  We don't do anything directly
  * with the information, as the JSON will be returned to the application.
@@ -950,7 +954,7 @@ reserve_withdraw_payment_required (struct
 
 /**
  * Function called when we're done processing the
- * HTTP /reserve/withdraw request.
+ * HTTP /reserves/$RESERVE_PUB/withdraw request.
  *
  * @param cls the `struct TALER_EXCHANGE_ReserveWithdrawHandle`
  * @param response_code HTTP response code, 0 on error
@@ -1063,26 +1067,40 @@ reserve_withdraw_internal (struct TALER_EXCHANGE_Handle *exchange,
   json_t *withdraw_obj;
   CURL *eh;
   struct GNUNET_HashCode h_denom_pub;
+  char arg_str[sizeof (struct TALER_ReservePublicKeyP) * 2 + 32];
 
+  {
+    char pub_str[sizeof (struct TALER_ReservePublicKeyP) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (reserve_pub,
+                                         sizeof (struct
+                                                 TALER_ReservePublicKeyP),
+                                         pub_str,
+                                         sizeof (pub_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/reserves/%s/withdraw",
+                     pub_str);
+  }
   wsh = GNUNET_new (struct TALER_EXCHANGE_ReserveWithdrawHandle);
   wsh->exchange = exchange;
   wsh->cb = res_cb;
   wsh->cb_cls = res_cb_cls;
   wsh->pk = *pk;
-  wsh->pk.key.rsa_public_key = GNUNET_CRYPTO_rsa_public_key_dup (
-    pk->key.rsa_public_key);
+  wsh->pk.key.rsa_public_key
+    = GNUNET_CRYPTO_rsa_public_key_dup (pk->key.rsa_public_key);
   wsh->reserve_pub = *reserve_pub;
   wsh->c_hash = pd->c_hash;
   GNUNET_CRYPTO_rsa_public_key_hash (pk->key.rsa_public_key,
                                      &h_denom_pub);
   withdraw_obj = json_pack ("{s:o, s:o," /* denom_pub_hash and coin_ev */
-                            " s:o, s:o}",/* reserve_pub and reserve_sig */
+                            " s:o}",/* reserve_pub and reserve_sig */
                             "denom_pub_hash", GNUNET_JSON_from_data_auto (
                               &h_denom_pub),
                             "coin_ev", GNUNET_JSON_from_data (pd->coin_ev,
                                                               pd->coin_ev_size),
-                            "reserve_pub", GNUNET_JSON_from_data_auto (
-                              reserve_pub),
                             "reserve_sig", GNUNET_JSON_from_data_auto (
                               reserve_sig));
   if (NULL == withdraw_obj)
@@ -1095,9 +1113,9 @@ reserve_withdraw_internal (struct TALER_EXCHANGE_Handle *exchange,
   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
               "Attempting to withdraw from reserve %s\n",
               TALER_B2S (reserve_pub));
-
   wsh->ps = *ps;
-  wsh->url = TEAH_path_to_url (exchange, "/reserve/withdraw");
+  wsh->url = TEAH_path_to_url (exchange,
+                               arg_str);
   eh = TEL_curl_easy_get (wsh->url);
   if (GNUNET_OK !=
       TALER_curl_easy_post (&wsh->ctx,
diff --git a/src/lib/exchange_api_track_transaction.c b/src/lib/exchange_api_track_transaction.c
index adf9373b..503ceea5 100644
--- a/src/lib/exchange_api_track_transaction.c
+++ b/src/lib/exchange_api_track_transaction.c
@@ -1,6 +1,6 @@
 /*
   This file is part of TALER
-  Copyright (C) 2014, 2015, 2016 Taler Systems SA
+  Copyright (C) 2014-2020 Taler Systems SA
 
   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU General Public License as published by the Free Software
@@ -280,8 +280,12 @@ TALER_EXCHANGE_track_transaction (struct TALER_EXCHANGE_Handle *exchange,
   struct TALER_MerchantSignatureP merchant_sig;
   struct TALER_EXCHANGE_TrackTransactionHandle *dwh;
   struct GNUNET_CURL_Context *ctx;
-  json_t *deposit_wtid_obj;
   CURL *eh;
+  char arg_str[(sizeof (struct TALER_CoinSpendPublicKeyP)
+                + sizeof (struct GNUNET_HashCode)
+                + sizeof (struct TALER_MerchantPublicKeyP)
+                + sizeof (struct GNUNET_HashCode)
+                + sizeof (struct TALER_MerchantSignatureP)) * 2 + 48];
 
   if (GNUNET_YES !=
       TEAH_handle_is_ready (exchange))
@@ -301,29 +305,61 @@ TALER_EXCHANGE_track_transaction (struct TALER_EXCHANGE_Handle *exchange,
                  GNUNET_CRYPTO_eddsa_sign (&merchant_priv->eddsa_priv,
                                            &dtp.purpose,
                                            &merchant_sig.eddsa_sig));
-  deposit_wtid_obj = json_pack ("{s:o, s:o," /* h_wire, h_contract_terms */
-                                " s:o," /* coin_pub */
-                                " s:o, s:o}", /* merchant_pub, merchant_sig */
-                                "h_wire", GNUNET_JSON_from_data_auto (h_wire),
-                                "h_contract_terms", GNUNET_JSON_from_data_auto (
-                                  h_contract_terms),
-                                "coin_pub", GNUNET_JSON_from_data_auto (
-                                  coin_pub),
-                                "merchant_pub", GNUNET_JSON_from_data_auto (
-                                  &dtp.merchant),
-                                "merchant_sig", GNUNET_JSON_from_data_auto (
-                                  &merchant_sig));
-  if (NULL == deposit_wtid_obj)
   {
-    GNUNET_break (0);
-    return NULL;
+    char cpub_str[sizeof (struct TALER_CoinSpendPublicKeyP) * 2];
+    char mpub_str[sizeof (struct TALER_MerchantPublicKeyP) * 2];
+    char msig_str[sizeof (struct TALER_MerchantSignatureP) * 2];
+    char chash_str[sizeof (struct GNUNET_HashCode) * 2];
+    char whash_str[sizeof (struct GNUNET_HashCode) * 2];
+    char *end;
+
+    end = GNUNET_STRINGS_data_to_string (h_wire,
+                                         sizeof (struct
+                                                 GNUNET_HashCode),
+                                         whash_str,
+                                         sizeof (whash_str));
+    *end = '\0';
+    end = GNUNET_STRINGS_data_to_string (&dtp.merchant,
+                                         sizeof (struct
+                                                 TALER_MerchantPublicKeyP),
+                                         mpub_str,
+                                         sizeof (mpub_str));
+    *end = '\0';
+    end = GNUNET_STRINGS_data_to_string (h_contract_terms,
+                                         sizeof (struct
+                                                 GNUNET_HashCode),
+                                         chash_str,
+                                         sizeof (chash_str));
+    *end = '\0';
+    end = GNUNET_STRINGS_data_to_string (coin_pub,
+                                         sizeof (struct
+                                                 TALER_CoinSpendPublicKeyP),
+                                         cpub_str,
+                                         sizeof (cpub_str));
+    *end = '\0';
+    end = GNUNET_STRINGS_data_to_string (&merchant_sig,
+                                         sizeof (struct
+                                                 TALER_MerchantSignatureP),
+                                         msig_str,
+                                         sizeof (msig_str));
+    *end = '\0';
+
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/deposits/%s/%s/%s/%s?merchant_sig=%s",
+                     whash_str,
+                     mpub_str,
+                     chash_str,
+                     cpub_str,
+                     msig_str);
   }
 
   dwh = GNUNET_new (struct TALER_EXCHANGE_TrackTransactionHandle);
   dwh->exchange = exchange;
   dwh->cb = cb;
   dwh->cb_cls = cb_cls;
-  dwh->url = TEAH_path_to_url (exchange, "/track/transaction");
+  dwh->url = TEAH_path_to_url (exchange,
+                               arg_str);
   dwh->depconf.purpose.size = htonl (sizeof (struct TALER_ConfirmWirePS));
   dwh->depconf.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_WIRE);
   dwh->depconf.h_wire = *h_wire;
@@ -331,25 +367,12 @@ TALER_EXCHANGE_track_transaction (struct TALER_EXCHANGE_Handle *exchange,
   dwh->depconf.coin_pub = *coin_pub;
 
   eh = TEL_curl_easy_get (dwh->url);
-  if (GNUNET_OK !=
-      TALER_curl_easy_post (&dwh->ctx,
-                            eh,
-                            deposit_wtid_obj))
-  {
-    GNUNET_break (0);
-    curl_easy_cleanup (eh);
-    json_decref (deposit_wtid_obj);
-    GNUNET_free (dwh->url);
-    GNUNET_free (dwh);
-    return NULL;
-  }
-  json_decref (deposit_wtid_obj);
   ctx = TEAH_handle_to_context (exchange);
-  dwh->job = GNUNET_CURL_job_add2 (ctx,
-                                   eh,
-                                   dwh->ctx.headers,
-                                   &handle_deposit_wtid_finished,
-                                   dwh);
+  dwh->job = GNUNET_CURL_job_add (ctx,
+                                  eh,
+                                  GNUNET_NO,
+                                  &handle_deposit_wtid_finished,
+                                  dwh);
   return dwh;
 }
 
diff --git a/src/lib/exchange_api_track_transfer.c b/src/lib/exchange_api_track_transfer.c
index ba8948fe..2fdfdde1 100644
--- a/src/lib/exchange_api_track_transfer.c
+++ b/src/lib/exchange_api_track_transfer.c
@@ -334,9 +334,8 @@ TALER_EXCHANGE_track_transfer (struct TALER_EXCHANGE_Handle *exchange,
 {
   struct TALER_EXCHANGE_TrackTransferHandle *wdh;
   struct GNUNET_CURL_Context *ctx;
-  char *buf;
-  char *path;
   CURL *eh;
+  char arg_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2 + 32];
 
   if (GNUNET_YES !=
       TEAH_handle_is_ready (exchange))
@@ -350,17 +349,23 @@ TALER_EXCHANGE_track_transfer (struct TALER_EXCHANGE_Handle *exchange,
   wdh->cb = cb;
   wdh->cb_cls = cb_cls;
 
-  buf = GNUNET_STRINGS_data_to_string_alloc (wtid,
-                                             sizeof (struct
-                                                     TALER_WireTransferIdentifierRawP));
-  GNUNET_asprintf (&path,
-                   "/track/transfer?wtid=%s",
-                   buf);
-  wdh->url = TEAH_path_to_url (wdh->exchange,
-                               path);
-  GNUNET_free (buf);
-  GNUNET_free (path);
+  {
+    char wtid_str[sizeof (struct TALER_WireTransferIdentifierRawP) * 2];
+    char *end;
 
+    end = GNUNET_STRINGS_data_to_string (wtid,
+                                         sizeof (struct
+                                                 TALER_WireTransferIdentifierRawP),
+                                         wtid_str,
+                                         sizeof (wtid_str));
+    *end = '\0';
+    GNUNET_snprintf (arg_str,
+                     sizeof (arg_str),
+                     "/transfers/%s",
+                     wtid_str);
+  }
+  wdh->url = TEAH_path_to_url (wdh->exchange,
+                               arg_str);
   eh = TEL_curl_easy_get (wdh->url);
   ctx = TEAH_handle_to_context (exchange);
   wdh->job = GNUNET_CURL_job_add (ctx,
diff --git a/src/testing/testing_api_cmd_status.c b/src/testing/testing_api_cmd_status.c
index 1c652b6d..690a5b9c 100644
--- a/src/testing/testing_api_cmd_status.c
+++ b/src/testing/testing_api_cmd_status.c
@@ -93,6 +93,7 @@ reserve_status_cb (void *cls,
                 http_status,
                 __FILE__,
                 __LINE__);
+    json_dumpf (json, stderr, 0);
     TALER_TESTING_interpreter_fail (ss->is);
     return;
   }
diff --git a/src/testing/testing_api_loop.c b/src/testing/testing_api_loop.c
index e9ccdb81..5f30f71e 100644
--- a/src/testing/testing_api_loop.c
+++ b/src/testing/testing_api_loop.c
@@ -693,6 +693,16 @@ do_abort (void *cls)
     TALER_EXCHANGE_disconnect (is->exchange);
     is->exchange = NULL;
   }
+  if (NULL != is->ctx)
+  {
+    GNUNET_CURL_fini (is->ctx);
+    is->ctx = NULL;
+  }
+  if (NULL != is->rc)
+  {
+    GNUNET_CURL_gnunet_rc_destroy (is->rc);
+    is->rc = NULL;
+  }
 }
 
 
full.diff (130,710 bytes)

Florian Dold

2020-03-09 12:15

manager   ~0015438

Wallet support for protocol version 7:0:0 has been implemented in wallet-core.git 26d961ad63

(Recoup is not up to date yet and tracked separately in 0006119)

Issue History

Date Modified Username Field Change
2020-01-28 12:09 Christian Grothoff New Issue
2020-01-28 12:09 Christian Grothoff Status new => assigned
2020-01-28 12:09 Christian Grothoff Assigned To => Christian Grothoff
2020-01-28 15:34 Florian Dold Note Added: 0015301
2020-02-05 21:16 Christian Grothoff Note Added: 0015310
2020-02-26 17:31 Christian Grothoff File Added: phase1.diff
2020-02-26 17:31 Christian Grothoff Note Added: 0015411
2020-02-26 22:29 Christian Grothoff File Added: stage2.diff
2020-02-26 22:29 Christian Grothoff Note Added: 0015412
2020-02-27 00:06 Christian Grothoff File Added: full.diff
2020-02-27 00:06 Christian Grothoff Note Added: 0015413
2020-02-27 00:06 Christian Grothoff Assigned To Christian Grothoff => Florian Dold
2020-03-09 12:15 Florian Dold Status assigned => resolved
2020-03-09 12:15 Florian Dold Resolution open => fixed
2020-03-09 12:15 Florian Dold Note Added: 0015438
2020-03-31 16:04 Christian Grothoff Status resolved => closed