diff --git a/README b/README
index 2cfc1d870643f740d4ee1647160c1cde5c0e088d..a735a0dbbe6d9fb3d44c47a044ffbedfaec6d4a2 100644
--- a/README
+++ b/README
@@ -204,7 +204,7 @@ The web and SOAP interfaces depend on the following packages:
 - apache2             >= 2.2.11
 - libapache2-mod-wsgi >= 2.8
 - python-webpy        >= 0.33
-- python-soaplib      >= 2.0.0-alpha1
+- python-rpclib       >= 2.8.0-beta
 - python-simpletal    >= 4.1-6
 
 Automatic remote deployment depends on Fabric:
@@ -230,7 +230,7 @@ source code (excluding the standard library imports):
     pyparsing
     setuptools
     simpletal
-    soaplib
+    rpclib
     suds
     web
     webtest
diff --git a/bin/mutalyzer-json-service.wsgi b/bin/mutalyzer-json-service.wsgi
new file mode 100755
index 0000000000000000000000000000000000000000..052dbbb36111927d5c7ef3853af9610a1181b00f
--- /dev/null
+++ b/bin/mutalyzer-json-service.wsgi
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+"""
+WSGI interface to the Mutalyzer HTTP/RPC+JSON webservice.
+
+The WSGI interface is exposed through the module variable 'application'.
+
+Example Apache/mod_wsgi configuration:
+
+  WSGIScriptAlias /json /usr/local/bin/mutalyzer-json-service.wsgi
+
+Be sure to have this line first if you also define a / alias, like this:
+
+  WSGIScriptAlias /services /usr/local/bin/mutalyzer-json-service.wsgi
+  WSGIScriptAlias / /usr/local/bin/mutalyzer-website.wsgi
+
+You can also use the built-in HTTP server by running this file directly.
+
+To start the built-in HTTP server on port 8082:
+
+  /usr/local/bin/mutalyzer-json-service.wsgi 8082
+"""
+
+
+import sys
+from wsgiref.simple_server import make_server
+from rpclib.server.wsgi import WsgiApplication
+from mutalyzer.services import json
+
+
+DEFAULT_PORT = 8082
+
+
+application = WsgiApplication(json.application)
+
+
+if __name__ == '__main__':
+    port = DEFAULT_PORT
+    if len(sys.argv) > 1:
+        try:
+            port = int(sys.argv[1])
+        except ValueError:
+            print 'Not a valid port number: %s' % sys.argv[1]
+            sys.exit(1)
+    print 'Listening at http://localhost:%d/' % port
+    make_server('localhost', port, application).serve_forever()
diff --git a/bin/mutalyzer-webservice.wsgi b/bin/mutalyzer-soap-service.wsgi
similarity index 57%
rename from bin/mutalyzer-webservice.wsgi
rename to bin/mutalyzer-soap-service.wsgi
index 5a01545e2e705cf9b04bc26b14da521c24e8c3d9..79fe02e17b3a59e40af2a6ad93d5a36721b96973 100755
--- a/bin/mutalyzer-webservice.wsgi
+++ b/bin/mutalyzer-soap-service.wsgi
@@ -6,39 +6,32 @@ The WSGI interface is exposed through the module variable 'application'.
 
 Example Apache/mod_wsgi configuration:
 
-  WSGIScriptAlias /services /usr/local/bin/mutalyzer-webservice.wsgi
+  WSGIScriptAlias /services /usr/local/bin/mutalyzer-soap-service.wsgi
 
 Be sure to have this line first if you also define a / alias, like this:
 
-  WSGIScriptAlias /services /usr/local/bin/mutalyzer-webservice.wsgi
+  WSGIScriptAlias /services /usr/local/bin/mutalyzer-soap-service.wsgi
   WSGIScriptAlias / /usr/local/bin/mutalyzer-website.wsgi
 
 You can also use the built-in HTTP server by running this file directly.
 
 To start the built-in HTTP server on port 8081:
 
-  /usr/local/bin/mutalyzer-webservice.wsgi 8081
-
-@todo: Do we really use namespaces correctly?
-@todo: For some reason, the server exposes its location including ?wsdl.
-@todo: More thourough input checking. The @soap decorator does not do any
-       kind of strictness checks on the input. For example, in
-       transcriptInfo, the build argument must really be present. (Hint:
-       use __checkBuild.)
+  /usr/local/bin/mutalyzer-soap-service.wsgi 8081
 """
 
 
 import sys
 from wsgiref.simple_server import make_server
-from mutalyzer import webservice
+from mutalyzer.services import soap
 
 
 DEFAULT_PORT = 8081
 
 
 # Unfortunately we cannot instantiate wsgi.Application here, see the note
-# near the bottom of mutalyzer/webservice.py.
-application = webservice.application
+# near the bottom of mutalyzer/services/soap.py.
+application = soap.wsgi_application
 
 
 if __name__ == '__main__':
@@ -49,6 +42,6 @@ if __name__ == '__main__':
         except ValueError:
             print 'Not a valid port number: %s' % sys.argv[1]
             sys.exit(1)
-    print 'Listening to http://localhost:%d/' % port
+    print 'Listening at http://localhost:%d/' % port
     print 'WDSL file is at http://localhost:%d/?wsdl' % port
     make_server('localhost', port, application).serve_forever()
diff --git a/extras/apache/mutalyzer.conf b/extras/apache/mutalyzer.conf
index 67b8fd172b8db0b7c501204449369ddd5e6e3c5d..0142a57666db70f5cbc57ca2c625c2cd0092deee 100644
--- a/extras/apache/mutalyzer.conf
+++ b/extras/apache/mutalyzer.conf
@@ -11,14 +11,22 @@ Alias /mutalyzer/base /var/www/mutalyzer/base
 WSGIDaemonProcess mutalyzer processes=2 threads=15 maximum-requests=10000
 WSGIProcessGroup mutalyzer
 
-# Webservice
-WSGIScriptAlias /mutalyzer/services <MUTALYZER_BIN_WEBSERVICE>
+# SOAP/1.1 webservice
+WSGIScriptAlias /mutalyzer/services <MUTALYZER_BIN_SOAP_SERVICE>
 <Directory /mutalyzer/services>
     Order deny,allow
     Allow from all
     Options -Indexes
 </Directory>
 
+# HTTP/RPC+JSON webservice
+WSGIScriptAlias /mutalyzer/json <MUTALYZER_BIN_JSON_SERVICE>
+<Directory /mutalyzer/json>
+    Order deny,allow
+    Allow from all
+    Options -Indexes
+</Directory>
+
 # Website
 WSGIScriptAlias /mutalyzer <MUTALYZER_BIN_WEBSITE>
 <Directory /mutalyzer>
diff --git a/extras/migrations/007-apache-json-webservice.migration b/extras/migrations/007-apache-json-webservice.migration
new file mode 100755
index 0000000000000000000000000000000000000000..cd9b82784e59c2b3db54ffce99d252f3312342da
--- /dev/null
+++ b/extras/migrations/007-apache-json-webservice.migration
@@ -0,0 +1,46 @@
+#!/bin/bash
+
+# Add HTTP/RPC+JSON webservice entry to Apache configuration.
+#
+# Usage:
+#   ./007-apache-json-webservice.migration [migrate]
+
+COLOR_INFO='\033[32m'
+COLOR_WARNING='\033[33m'
+COLOR_ERROR='\033[31m'
+COLOR_END='\033[0m'
+
+BIN_SOAP_SERVICE=$(which mutalyzer-soap-service.wsgi)
+BIN_JSON_SERVICE=$(which mutalyzer-json-service.wsgi)
+
+BIN_OLD_SOAP_SERVICE=$(echo $BIN_SOAP_SERVICE | sed 's/mutalyzer-soap-service/mutalyzer-webservice/')
+
+CONF=/etc/apache2/conf.d/mutalyzer.conf
+
+if [ -e $CONF ] && ! $(grep -q "${BIN_JSON_SERVICE}" $CONF) && $(grep -q "${BIN_OLD_SOAP_SERVICE}" $CONF); then
+    echo -e "${COLOR_WARNING}This migration is needed.${COLOR_END}"
+    if [ "$1" = 'migrate' ]; then
+        echo 'Performing migration.'
+        if $(grep -q '^# Website' $CONF); then
+            sed -i 's!^# Webservice!# SOAP/1.1 webservice!' $CONF
+            sed -i "s!$BIN_OLD_SOAP_SERVICE!$BIN_SOAP_SERVICE!g" $CONF
+            echo -e "${COLOR_INFO}Changed ${BIN_OLD_SOAP_SERVICE} to ${BIN_SOAP_SERVICE} in ${CONF}${COLOR_END}"
+            # Insert before '# Website' line
+            sed -i "/^# Website/ i\\
+# HTTP/RPC+JSON webservice\\
+WSGIScriptAlias /mutalyzer/json $BIN_JSON_SERVICE\\
+<Directory /mutalyzer/json>\\
+    Order deny,allow\\
+    Allow from all\\
+    Options -Indexes\\
+</Directory>\\
+" $CONF
+            echo -e "${COLOR_INFO}Added HTTP/RPC+JSON webservice at /mutalyzer/json to ${CONF}${COLOR_END}"
+            echo 'Performed migration.'
+        else
+            echo -e "${COLOR_ERROR}Could not perform migration.${COLOR_END}"
+        fi
+    fi
+else
+    echo -e "${COLOR_INFO}This migration is not needed.${COLOR_END}"
+fi
diff --git a/extras/post-install.sh b/extras/post-install.sh
index 3a001756912f3a4c177760ba4515e5392c635411..34a4213761a29c1d704bb67d65d8633809357b50 100644
--- a/extras/post-install.sh
+++ b/extras/post-install.sh
@@ -31,7 +31,8 @@ BIN_BATCHD=$(which mutalyzer-batchd)
 BIN_CACHE_SYNC=$(which mutalyzer-cache-sync)
 BIN_MAPPING_UPDATE=$(which mutalyzer-mapping-update)
 BIN_WEBSITE=$(which mutalyzer-website.wsgi)
-BIN_WEBSERVICE=$(which mutalyzer-webservice.wsgi)
+BIN_SOAP_SERVICE=$(which mutalyzer-soap-service.wsgi)
+BIN_JSON_SERVICE=$(which mutalyzer-json-service.wsgi)
 
 if [ ! -e /etc/mutalyzer/config ]; then
     echo -e "${COLOR_INFO}Creating /etc/mutalyzer/config${COLOR_END}"
@@ -83,7 +84,7 @@ sed -i -e "s@<MUTALYZER_BIN_MAPPING_UPDATE>@${BIN_MAPPING_UPDATE}@g" /etc/cron.d
 
 echo -e "${COLOR_INFO}Creating /etc/apache2/conf.d/mutalyzer.conf${COLOR_END}"
 cp extras/apache/mutalyzer.conf /etc/apache2/conf.d/mutalyzer.conf
-sed -i -e "s@<MUTALYZER_BIN_WEBSITE>@${BIN_WEBSITE}@g" -e "s@<MUTALYZER_BIN_WEBSERVICE>@${BIN_WEBSERVICE}@g" -e "s@<MUTALYZER_BIN_BATCHD>@${BIN_BATCHD}@g" /etc/apache2/conf.d/mutalyzer.conf
+sed -i -e "s@<MUTALYZER_BIN_WEBSITE>@${BIN_WEBSITE}@g" -e "s@<MUTALYZER_BIN_SOAP_SERVICE>@${BIN_SOAP_SERVICE}@g" -e "s@<MUTALYZER_BIN_JSON_SERVICE>@${BIN_JSON_SERVICE}@g" -e "s@<MUTALYZER_BIN_BATCHD>@${BIN_BATCHD}@g" /etc/apache2/conf.d/mutalyzer.conf
 chmod u=rw,go=r /etc/apache2/conf.d/mutalyzer.conf
 
 echo "You will now be asked for the MySQL root password"
diff --git a/extras/post-upgrade.sh b/extras/post-upgrade.sh
index b6224d7619607400404abbf3c027df5e29d75ec9..16178068cbac5b545ab32fce89152e1f401dee19 100644
--- a/extras/post-upgrade.sh
+++ b/extras/post-upgrade.sh
@@ -23,7 +23,8 @@ COLOR_END='\033[0m'
 # directory to be used.
 PACKAGE_ROOT=$(cd / && python -c 'import mutalyzer; print mutalyzer.package_root()')
 BIN_WEBSITE=$(which mutalyzer-website.wsgi)
-BIN_WEBSERVICE=$(which mutalyzer-webservice.wsgi)
+BIN_SOAP_SERVICE=$(which mutalyzer-soap-service.wsgi)
+BIN_JSON_SERVICE=$(which mutalyzer-json-service.wsgi)
 
 if [ ! -e /var/www/mutalyzer ]; then
     mkdir -p /var/www/mutalyzer
@@ -48,7 +49,8 @@ echo -e "${COLOR_INFO}Assuming mod_wsgi daemon mode, not restarting Apache${COLO
 
 echo "Touching WSGI entry to reload application"
 touch $BIN_WEBSITE
-touch $BIN_WEBSERVICE
+touch $BIN_SOAP_SERVICE
+touch $BIN_JSON_SERVICE
 
 echo "Restarting Mutalyzer batch daemon"
 /etc/init.d/mutalyzer-batchd restart
diff --git a/extras/pre-install.sh b/extras/pre-install.sh
index e9b096c9a5c3e87d09970edcf0dbcf3928e43ca1..7994868d4fa4e2ad10b2c003b26f9cb2642a17fe 100644
--- a/extras/pre-install.sh
+++ b/extras/pre-install.sh
@@ -44,10 +44,10 @@ apt-get install -y \
   python-setuptools \
   git-core
 
-echo -e "${COLOR_INFO}Installing latest soaplib from mirrored git master${COLOR_END}"
+echo -e "${COLOR_INFO}Installing latest rpclib from git master${COLOR_END}"
 
 pushd $(mktemp -d)
-git clone https://github.com/martijnvermaat/soaplib.git .
+git clone https://github.com/arskom/rpclib.git .
 python setup.py install
 popd
 
diff --git a/mutalyzer/models.py b/mutalyzer/models.py
index 41c3afd82ad45a256e1d6c49b6660d5cef7cf37e..0dcc07ec440cc716e679611f825cce7c2e331ca8 100644
--- a/mutalyzer/models.py
+++ b/mutalyzer/models.py
@@ -1,13 +1,13 @@
 """
 Collection of serilizable objects used by the SOAP webservice. They extend
-from the soaplib ClassModel.
+from the rpclib ClassModel.
 
-Default attributes for the soaplib ClassModel:
+Default attributes for the rpclib ClassModel:
 - nillable = True
 - min_occurs = 0
 - max_occurs = 1
 
-Additional attributes values for the soaplib String model:
+Additional attributes values for the rpclib String model:
 - min_len = 0
 - max_len = 'unbounded'
 - pattern = None
@@ -17,15 +17,15 @@ Additional attributes values for the soaplib String model:
 """
 
 
-from soaplib.core.model.primitive import String, Integer, Boolean, DateTime
-from soaplib.core.model.clazz import ClassModel, Array
+from rpclib.model.primitive import String, Integer, Boolean, DateTime
+from rpclib.model.complex import ComplexModel, Array
 
 from mutalyzer import SOAP_NAMESPACE
 
 
 class Mandatory(object):
     """
-    This is soaplib.model.primitive.Mandatory, but without min_length=1 for
+    This is rpclib.model.primitive.Mandatory, but without min_length=1 for
     the String model.
     """
     String = String(min_occurs=1, nillable=False)
@@ -35,7 +35,7 @@ class Mandatory(object):
 #Mandatory
 
 
-class SoapMessage(ClassModel):
+class SoapMessage(ComplexModel):
     """
     Type of messages used in SOAP method return values.
     """
@@ -46,7 +46,7 @@ class SoapMessage(ClassModel):
 #SoapMessage
 
 
-class Mapping(ClassModel):
+class Mapping(ComplexModel):
     """
     Return type of SOAP method mappingInfo.
     """
@@ -64,7 +64,7 @@ class Mapping(ClassModel):
 #Mapping
 
 
-class Transcript(ClassModel):
+class Transcript(ComplexModel):
     """
     Return type of SOAP method transcriptInfo.
     """
@@ -76,7 +76,7 @@ class Transcript(ClassModel):
 #Transcript
 
 
-class RawVariant(ClassModel):
+class RawVariant(ComplexModel):
     """
     Used in MutalyzerOutput data type.
     """
@@ -87,7 +87,7 @@ class RawVariant(ClassModel):
 #RawVariant
 
 
-class MutalyzerOutput(ClassModel):
+class MutalyzerOutput(ComplexModel):
     """
     Return type of SOAP method runMutalyzer.
     """
@@ -128,7 +128,7 @@ class MutalyzerOutput(ClassModel):
 #MutalyzerOutput
 
 
-class TranscriptNameInfo(ClassModel):
+class TranscriptNameInfo(ComplexModel):
     """
     Return type of SOAP method getGeneAndTranscript.
     """
@@ -139,7 +139,7 @@ class TranscriptNameInfo(ClassModel):
 #TranscriptNameInfo
 
 
-class ExonInfo(ClassModel):
+class ExonInfo(ComplexModel):
     """
     Used in TranscriptInfo data type.
     """
@@ -154,7 +154,7 @@ class ExonInfo(ClassModel):
 #ExonInfo
 
 
-class ProteinTranscript(ClassModel):
+class ProteinTranscript(ComplexModel):
     """
     Used in TranscriptInfo data type.
     """
@@ -166,7 +166,7 @@ class ProteinTranscript(ClassModel):
 #ProteinTranscript
 
 
-class TranscriptInfo(ClassModel):
+class TranscriptInfo(ComplexModel):
     """
     Used in return type of SOAP method getTranscriptsAndInfo.
 
@@ -206,7 +206,7 @@ class TranscriptInfo(ClassModel):
 #TranscriptInfo
 
 
-class TranscriptMappingInfo(ClassModel):
+class TranscriptMappingInfo(ComplexModel):
     """
     Used in return type of SOAP method getTranscriptsRange.
     """
@@ -226,7 +226,7 @@ class TranscriptMappingInfo(ClassModel):
 #TranscriptMappingInfo
 
 
-class CheckSyntaxOutput(ClassModel):
+class CheckSyntaxOutput(ComplexModel):
     """
     Return type of SOAP method checkSyntax.
     """
@@ -237,7 +237,7 @@ class CheckSyntaxOutput(ClassModel):
 #CheckSyntaxOutput
 
 
-class InfoOutput(ClassModel):
+class InfoOutput(ComplexModel):
     """
     Return type of SOAP method info.
     """
@@ -253,7 +253,7 @@ class InfoOutput(ClassModel):
 #InfoOutput
 
 
-class CacheEntry(ClassModel):
+class CacheEntry(ComplexModel):
     """
     Used in getCache SOAP method.
     """
diff --git a/mutalyzer/services/__init__.py b/mutalyzer/services/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..05b3d031865b91b2a3ebd2ead081592a52a119e2
--- /dev/null
+++ b/mutalyzer/services/__init__.py
@@ -0,0 +1,3 @@
+"""
+Services (RPC) for Mutalyzer.
+"""
diff --git a/mutalyzer/services/json.py b/mutalyzer/services/json.py
new file mode 100644
index 0000000000000000000000000000000000000000..b1f46c313d6724a9f0b8f81d723fe692f1f9ad63
--- /dev/null
+++ b/mutalyzer/services/json.py
@@ -0,0 +1,17 @@
+"""
+Mutalyzer webservice HTTP/RPC with JSON response payloads.
+"""
+
+
+from rpclib.application import Application
+from rpclib.protocol.http import HttpRpc
+from rpclib.protocol.json import JsonObject
+
+import mutalyzer
+from mutalyzer.services import rpc
+
+
+# HTTP/RPC application.
+application = Application([rpc.MutalyzerService], tns=mutalyzer.SOAP_NAMESPACE,
+                          in_protocol=HttpRpc(), out_protocol=JsonObject(skip_depth=2),
+                          name='Mutalyzer')
diff --git a/mutalyzer/webservice.py b/mutalyzer/services/rpc.py
similarity index 83%
rename from mutalyzer/webservice.py
rename to mutalyzer/services/rpc.py
index 71c3447b6991e488ca54ed4f866383b231674aea..1cb4a7ac79c17a26b2bdc1449426c15ba687372f 100644
--- a/mutalyzer/webservice.py
+++ b/mutalyzer/services/rpc.py
@@ -1,8 +1,6 @@
 """
-Mutalyzer webservices.
+Mutalyzer RPC services.
 
-@todo: Do we really use namespaces correctly?
-@todo: For some reason, the server exposes its location including ?wsdl.
 @todo: More thourough input checking. The @soap decorator does not do any
        kind of strictness checks on the input. For example, in
        transcriptInfo, the build argument must really be present. (Hint:
@@ -10,23 +8,11 @@ Mutalyzer webservices.
 """
 
 
-# WSGI applications should never print anything to stdout. We redirect to
-# stderr, but eventually Mutalyzer should be fixed to never just 'print'
-# anything.
-# http://code.google.com/p/modwsgi/wiki/DebuggingTechniques
-import sys
-sys.stdout = sys.stderr
-
-# Log exceptions to stdout
-import logging; logging.basicConfig()
-
-from soaplib.core import Application
-from soaplib.core.service import soap
-from soaplib.core.service import DefinitionBase
-from soaplib.core.model.primitive import String, Integer, Boolean, DateTime
-from soaplib.core.model.clazz import Array
-from soaplib.core.model.exception import Fault
-from soaplib.core.server import wsgi
+from rpclib.decorator import srpc
+from rpclib.service import ServiceBase
+from rpclib.model.primitive import String, Integer, Boolean, DateTime
+from rpclib.model.complex import Array
+from rpclib.model.fault import Fault
 import os
 import socket
 from operator import itemgetter, attrgetter
@@ -44,102 +30,106 @@ from mutalyzer import GenRecord
 from mutalyzer.models import *
 
 
-class MutalyzerService(DefinitionBase):
+def _checkBuild(L, build) :
     """
-    Mutalyzer webservices.
+    Check if the build is supported (hg18 or hg19).
 
-    These methods are made public via a SOAP interface.
+    Returns:
+        - Nothing (but raises an EARG exception).
+
+    @arg L: An output object for logging.
+    @type L: object
+    @arg build: The human genome build name that needs to be checked.
+    @type build: string
     """
-    def __init__(self, environ=None):
-        super(MutalyzerService, self).__init__(environ)
-    #__init__
 
-    def __checkBuild(self, L, build) :
-        """
-        Check if the build is supported (hg18 or hg19).
+    if not build in config.get('dbNames'):
+        L.addMessage(__file__, 4, "EARG", "EARG %s" % build)
+        raise Fault("EARG",
+                    "The build argument (%s) was not a valid " \
+                    "build name." % build)
+    #if
+#_checkBuild
 
-        Returns:
-            - Nothing (but raises an EARG exception).
 
-        @arg L: An output object for logging.
-        @type L: object
-        @arg build: The human genome build name that needs to be checked.
-        @type build: string
-        """
+def _checkChrom(L, D, chrom) :
+    """
+    Check if the chromosome is in our database.
 
-        if not build in config.get('dbNames'):
-            L.addMessage(__file__, 4, "EARG", "EARG %s" % build)
-            raise Fault("EARG",
-                        "The build argument (%s) was not a valid " \
-                        "build name." % build)
-        #if
-    #__checkBuild
+    Returns:
+        - Nothing (but raises an EARG exception).
 
-    def __checkChrom(self, L, D, chrom) :
-        """
-        Check if the chromosome is in our database.
+    @arg L: An output object for logging.
+    @type L: object
+    @arg D: A handle to the database.
+    @type D: object
+    @arg chrom: The name of the chromosome.
+    @type chrom: string
+    """
 
-        Returns:
-            - Nothing (but raises an EARG exception).
+    if not D.isChrom(chrom) :
+        L.addMessage(__file__, 4, "EARG", "EARG %s" % chrom)
+        raise Fault("EARG",
+                    "The chrom argument (%s) was not a valid " \
+                    "chromosome name." % chrom)
+    #if
+#_checkChrom
 
-        @arg L: An output object for logging.
-        @type L: object
-        @arg D: A handle to the database.
-        @type D: object
-        @arg chrom: The name of the chromosome.
-        @type chrom: string
-        """
 
-        if not D.isChrom(chrom) :
-            L.addMessage(__file__, 4, "EARG", "EARG %s" % chrom)
-            raise Fault("EARG",
-                        "The chrom argument (%s) was not a valid " \
-                        "chromosome name." % chrom)
-        #if
-    #__checkChrom
+def _checkPos(L, pos) :
+    """
+    Check if the position is valid.
+
+    Returns:
+        - Nothing (but raises an ERANGE exception).
 
-    def __checkPos(self, L, pos) :
-        """
-        Check if the position is valid.
+    @arg L: An output object for logging.
+    @type L: object
+    @arg pos: The position.
+    @type pos: integer
+    """
 
-        Returns:
-            - Nothing (but raises an ERANGE exception).
+    if pos < 1 :
+        L.addMessage(__file__, 4, "ERANGE", "ERANGE %i" % pos)
+        raise Fault("ERANGE",
+                    "The pos argument (%i) is out of range." % pos)
+    #if
+#_checkPos
 
-        @arg L: An output object for logging.
-        @type L: object
-        @arg pos: The position.
-        @type pos: integer
-        """
 
-        if pos < 1 :
-            L.addMessage(__file__, 4, "ERANGE", "ERANGE %i" % pos)
-            raise Fault("ERANGE",
-                        "The pos argument (%i) is out of range." % pos)
-        #if
-    #__checkPos
+def _checkVariant(L, variant) :
+    """
+    Check if a variant is provided.
 
-    def __checkVariant(self, L, variant) :
-        """
-        Check if a variant is provided.
+    Returns:
+        - Nothing (but raises an EARG exception).
 
-        Returns:
-            - Nothing (but raises an EARG exception).
+    @arg L: An output object for logging.
+    @type L: object
+    @arg variant: The variant.
+    @type variant: string
+    """
 
-        @arg L: An output object for logging.
-        @type L: object
-        @arg variant: The variant.
-        @type variant: string
-        """
+    if not variant :
+        L.addMessage(__file__, 4, "EARG", "EARG no variant")
+        raise Fault("EARG", "The variant argument is not provided.")
+    #if
+#_checkVariant
 
-        if not variant :
-            L.addMessage(__file__, 4, "EARG", "EARG no variant")
-            raise Fault("EARG", "The variant argument is not provided.")
-        #if
-    #__checkVariant
 
-    @soap(Mandatory.String, Mandatory.String, Mandatory.Integer, Boolean,
+class MutalyzerService(ServiceBase):
+    """
+    Mutalyzer webservices.
+
+    These methods are made public via a SOAP interface.
+    """
+    def __init__(self, environ=None):
+        super(MutalyzerService, self).__init__(environ)
+    #__init__
+
+    @srpc(Mandatory.String, Mandatory.String, Mandatory.Integer, Boolean,
         _returns = Array(Mandatory.String))
-    def getTranscripts(self, build, chrom, pos, versions=False) :
+    def getTranscripts(build, chrom, pos, versions=False) :
         """
         Get all the transcripts that overlap with a chromosomal position.
 
@@ -167,11 +157,11 @@ class MutalyzerService(DefinitionBase):
                      "Received request getTranscripts(%s %s %s %s)" % (build,
                      chrom, pos, versions))
 
-        self.__checkBuild(L, build)
+        _checkBuild(L, build)
         D = Db.Mapping(build)
 
-        self.__checkChrom(L, D, chrom)
-        self.__checkPos(L, pos)
+        _checkChrom(L, D, chrom)
+        _checkPos(L, pos)
 
         ret = D.get_Transcripts(chrom, pos, pos, True)
 
@@ -192,8 +182,8 @@ class MutalyzerService(DefinitionBase):
         return ret
     #getTranscripts
 
-    @soap(Mandatory.String, Mandatory.String, _returns = Array(Mandatory.String))
-    def getTranscriptsByGeneName(self, build, name):
+    @srpc(Mandatory.String, Mandatory.String, _returns = Array(Mandatory.String))
+    def getTranscriptsByGeneName(build, name):
         """
         Todo: documentation.
         """
@@ -203,7 +193,7 @@ class MutalyzerService(DefinitionBase):
                      "Received request getTranscriptsByGene(%s %s)" % (build,
                      name))
 
-        self.__checkBuild(L, build)
+        _checkBuild(L, build)
         D = Db.Mapping(build)
 
         ret = D.get_TranscriptsByGeneName(name)
@@ -221,9 +211,9 @@ class MutalyzerService(DefinitionBase):
         return []
     #getTranscriptsByGene
 
-    @soap(Mandatory.String, Mandatory.String, Mandatory.Integer,
+    @srpc(Mandatory.String, Mandatory.String, Mandatory.Integer,
         Mandatory.Integer, Mandatory.Integer, _returns = Array(Mandatory.String))
-    def getTranscriptsRange(self, build, chrom, pos1, pos2, method) :
+    def getTranscriptsRange(build, chrom, pos1, pos2, method) :
         """
         Get all the transcripts that overlap with a range on a chromosome.
 
@@ -250,7 +240,7 @@ class MutalyzerService(DefinitionBase):
             chrom, pos1, pos2, method))
 
         D = Db.Mapping(build)
-        self.__checkBuild(L, build)
+        _checkBuild(L, build)
 
         ret = D.get_Transcripts(chrom, pos1, pos2, method)
 
@@ -265,9 +255,9 @@ class MutalyzerService(DefinitionBase):
         return ret
     #getTranscriptsRange
 
-    @soap(Mandatory.String, Mandatory.String, Mandatory.Integer,
+    @srpc(Mandatory.String, Mandatory.String, Mandatory.Integer,
         Mandatory.Integer, Mandatory.Integer, _returns = Array(TranscriptMappingInfo))
-    def getTranscriptsMapping(self, build, chrom, pos1, pos2, method):
+    def getTranscriptsMapping(build, chrom, pos1, pos2, method):
         """
         Get all the transcripts and their info that overlap with a range on a
         chromosome.
@@ -301,7 +291,7 @@ class MutalyzerService(DefinitionBase):
                           'getTranscriptsRange(%s %s %s %s %s)' % \
                           (build, chrom, pos1, pos2, method))
 
-        self.__checkBuild(output, build)
+        _checkBuild(output, build)
 
         database = Db.Mapping(build)
         transcripts = []
@@ -332,8 +322,8 @@ class MutalyzerService(DefinitionBase):
         return transcripts
     #getTranscriptsMapping
 
-    @soap(Mandatory.String, Mandatory.String, _returns = Mandatory.String)
-    def getGeneName(self, build, accno) :
+    @srpc(Mandatory.String, Mandatory.String, _returns = Mandatory.String)
+    def getGeneName(build, accno) :
         """
         Find the gene name associated with a transcript.
 
@@ -351,7 +341,7 @@ class MutalyzerService(DefinitionBase):
                      "Received request getGeneName(%s %s)" % (build, accno))
 
         D = Db.Mapping(build)
-        self.__checkBuild(L, build)
+        _checkBuild(L, build)
 
         ret = D.get_GeneName(accno.split('.')[0])
 
@@ -363,9 +353,9 @@ class MutalyzerService(DefinitionBase):
     #getGeneName
 
 
-    @soap(Mandatory.String, Mandatory.String, Mandatory.String,
+    @srpc(Mandatory.String, Mandatory.String, Mandatory.String,
         Mandatory.String, _returns = Mapping)
-    def mappingInfo(self, LOVD_ver, build, accNo, variant) :
+    def mappingInfo(LOVD_ver, build, accNo, variant) :
         """
         Search for an NM number in the MySQL database, if the version
         number matches, get the start and end positions in a variant and
@@ -421,9 +411,9 @@ class MutalyzerService(DefinitionBase):
         return result
     #mappingInfo
 
-    @soap(Mandatory.String, Mandatory.String, Mandatory.String,
+    @srpc(Mandatory.String, Mandatory.String, Mandatory.String,
         _returns = Transcript)
-    def transcriptInfo(self, LOVD_ver, build, accNo) :
+    def transcriptInfo(LOVD_ver, build, accNo) :
         """
         Search for an NM number in the MySQL database, if the version
         number matches, the transcription start and end and CDS end
@@ -457,8 +447,8 @@ class MutalyzerService(DefinitionBase):
         return T
     #transcriptInfo
 
-    @soap(Mandatory.String, Mandatory.String, _returns = Mandatory.String)
-    def chromAccession(self, build, name) :
+    @srpc(Mandatory.String, Mandatory.String, _returns = Mandatory.String)
+    def chromAccession(build, name) :
         """
         Get the accession number of a chromosome, given a name.
 
@@ -476,8 +466,8 @@ class MutalyzerService(DefinitionBase):
         L.addMessage(__file__, -1, "INFO",
                      "Received request chromAccession(%s %s)" % (build, name))
 
-        self.__checkBuild(L, build)
-        self.__checkChrom(L, D, name)
+        _checkBuild(L, build)
+        _checkChrom(L, D, name)
 
         result = D.chromAcc(name)
 
@@ -489,8 +479,8 @@ class MutalyzerService(DefinitionBase):
         return result
     #chromAccession
 
-    @soap(Mandatory.String, Mandatory.String, _returns = Mandatory.String)
-    def chromosomeName(self, build, accNo) :
+    @srpc(Mandatory.String, Mandatory.String, _returns = Mandatory.String)
+    def chromosomeName(build, accNo) :
         """
         Get the name of a chromosome, given a chromosome accession number.
 
@@ -508,8 +498,8 @@ class MutalyzerService(DefinitionBase):
         L.addMessage(__file__, -1, "INFO",
                      "Received request chromName(%s %s)" % (build, accNo))
 
-        self.__checkBuild(L, build)
-#        self.__checkChrom(L, D, name)
+        _checkBuild(L, build)
+#        self._checkChrom(L, D, name)
 
         result = D.chromName(accNo)
 
@@ -521,8 +511,8 @@ class MutalyzerService(DefinitionBase):
         return result
     #chromosomeName
 
-    @soap(Mandatory.String, Mandatory.String, _returns = Mandatory.String)
-    def getchromName(self, build, acc) :
+    @srpc(Mandatory.String, Mandatory.String, _returns = Mandatory.String)
+    def getchromName(build, acc) :
         """
         Get the chromosome name, given a transcript identifier (NM number).
 
@@ -540,8 +530,8 @@ class MutalyzerService(DefinitionBase):
         L.addMessage(__file__, -1, "INFO",
                      "Received request getchromName(%s %s)" % (build, acc))
 
-        self.__checkBuild(L, build)
-#        self.__checkChrom(L, D, name)
+        _checkBuild(L, build)
+#        self._checkChrom(L, D, name)
 
         result = D.get_chromName(acc)
 
@@ -553,8 +543,8 @@ class MutalyzerService(DefinitionBase):
         return result
     #chromosomeName
 
-    @soap(Mandatory.String, Mandatory.String, String, _returns = Array(Mandatory.String))
-    def numberConversion(self, build, variant, gene=None):
+    @srpc(Mandatory.String, Mandatory.String, String, _returns = Array(Mandatory.String))
+    def numberConversion(build, variant, gene=None):
         """
         Converts I{c.} to I{g.} notation or vice versa
 
@@ -591,8 +581,8 @@ class MutalyzerService(DefinitionBase):
         return result
     #numberConversion
 
-    @soap(Mandatory.String, _returns = CheckSyntaxOutput)
-    def checkSyntax(self, variant):
+    @srpc(Mandatory.String, _returns = CheckSyntaxOutput)
+    def checkSyntax(variant):
         """
         Checks the syntax of a variant.
 
@@ -611,7 +601,7 @@ class MutalyzerService(DefinitionBase):
 
         result = CheckSyntaxOutput()
 
-        self.__checkVariant(output, variant)
+        _checkVariant(output, variant)
 
         grammar = Grammar(output)
         parsetree = grammar.parse(variant)
@@ -630,8 +620,8 @@ class MutalyzerService(DefinitionBase):
         return result
     #checkSyntax
 
-    @soap(Mandatory.String, _returns = MutalyzerOutput)
-    def runMutalyzer(self, variant) :
+    @srpc(Mandatory.String, _returns = MutalyzerOutput)
+    def runMutalyzer(variant) :
         """
         Run the Mutalyzer name checker.
 
@@ -687,7 +677,7 @@ class MutalyzerService(DefinitionBase):
         result.molecule = O.getIndexedOutput('molecule', 0)
 
         # We force the results to strings here, because some results
-        # may be of type Bio.Seq.Seq which soaplib doesn't like.
+        # may be of type Bio.Seq.Seq which rpclib doesn't like.
         #
         # todo: We might have to also do this elsewhere.
 
@@ -735,8 +725,8 @@ class MutalyzerService(DefinitionBase):
         return result
     #runMutalyzer
 
-    @soap(Mandatory.String, Mandatory.String, _returns = TranscriptNameInfo)
-    def getGeneAndTranscript(self, genomicReference, transcriptReference) :
+    @srpc(Mandatory.String, Mandatory.String, _returns = TranscriptNameInfo)
+    def getGeneAndTranscript(genomicReference, transcriptReference) :
         """
         Todo: documentation.
         """
@@ -768,8 +758,8 @@ class MutalyzerService(DefinitionBase):
         return ret
     #getGeneAndTranscript
 
-    @soap(Mandatory.String, String, _returns = Array(TranscriptInfo))
-    def getTranscriptsAndInfo(self, genomicReference, geneName=None):
+    @srpc(Mandatory.String, String, _returns = Array(TranscriptInfo))
+    def getTranscriptsAndInfo(genomicReference, geneName=None):
         """
         Given a genomic reference, return all its transcripts with their
         transcription/cds start/end sites and exons.
@@ -816,7 +806,7 @@ class MutalyzerService(DefinitionBase):
         D = Db.Cache()
 
         O.addMessage(__file__, -1, "INFO",
-            "Received request getTranscriptsAndInfo(%s)" % genomicReference)
+            "Received request getTranscriptsAndInfo(%s, %s)" % (genomicReference, geneName))
         retriever = Retriever.GenBankRetriever(O, D)
         record = retriever.loadrecord(genomicReference)
 
@@ -909,25 +899,25 @@ class MutalyzerService(DefinitionBase):
         return transcripts
     #getTranscriptsAndInfo
 
-    @soap(Mandatory.String, _returns = Mandatory.String)
-    def upLoadGenBankLocalFile(self, content) :
+    @srpc(Mandatory.String, _returns = Mandatory.String)
+    def upLoadGenBankLocalFile(content) :
         """
         Not implemented yet.
         """
         raise Fault('ENOTIMPLEMENTED', 'Not implemented yet')
     #upLoadGenBankLocalFile
 
-    @soap(Mandatory.String, _returns = Mandatory.String)
-    def upLoadGenBankRemoteFile(self, url) :
+    @srpc(Mandatory.String, _returns = Mandatory.String)
+    def upLoadGenBankRemoteFile(url) :
         """
         Not implemented yet.
         """
         raise Fault('ENOTIMPLEMENTED', 'Not implemented yet')
     #upLoadGenBankRemoteFile
 
-    @soap(Mandatory.String, Mandatory.String, Mandatory.Integer,
+    @srpc(Mandatory.String, Mandatory.String, Mandatory.Integer,
         Mandatory.Integer, _returns = Mandatory.String)
-    def sliceChromosomeByGene(self, geneSymbol, organism, upStream,
+    def sliceChromosomeByGene(geneSymbol, organism, upStream,
         downStream) :
         """
         Todo: documentation, error handling, argument checking, tests.
@@ -955,9 +945,9 @@ class MutalyzerService(DefinitionBase):
         return UD
     #sliceChromosomeByGene
 
-    @soap(Mandatory.String, Mandatory.Integer, Mandatory.Integer,
+    @srpc(Mandatory.String, Mandatory.Integer, Mandatory.Integer,
         Mandatory.Integer, _returns = Mandatory.String)
-    def sliceChromosome(self, chromAccNo, start, end, orientation) :
+    def sliceChromosome(chromAccNo, start, end, orientation) :
         """
         Todo: documentation, error handling, argument checking, tests.
 
@@ -982,8 +972,8 @@ class MutalyzerService(DefinitionBase):
         return UD
     #sliceChromosome
 
-    @soap(_returns = InfoOutput)
-    def info(self):
+    @srpc(_returns = InfoOutput)
+    def info():
         """
         Gives some static application information, such as the current running
         version.
@@ -1020,8 +1010,8 @@ class MutalyzerService(DefinitionBase):
         return result
     #info
 
-    @soap(_returns = Mandatory.String)
-    def ping(self):
+    @srpc(_returns = Mandatory.String)
+    def ping():
         """
         Simple function to test the interface.
 
@@ -1031,8 +1021,8 @@ class MutalyzerService(DefinitionBase):
         return 'pong'
     #ping
 
-    @soap(DateTime, _returns = Array(CacheEntry))
-    def getCache(self, created_since=None):
+    @srpc(DateTime, _returns = Array(CacheEntry))
+    def getCache(created_since=None):
         """
         Get a list of entries from the local cache created since given date.
 
@@ -1063,8 +1053,8 @@ class MutalyzerService(DefinitionBase):
         return map(cache_entry_to_soap, cache)
     #getCache
 
-    @soap(Mandatory.String, _returns = Array(Mandatory.String))
-    def getdbSNPDescriptions(self, rs_id):
+    @srpc(Mandatory.String, _returns = Array(Mandatory.String))
+    def getdbSNPDescriptions(rs_id):
         """
         Lookup HGVS descriptions for a dbSNP rs identifier.
 
@@ -1095,13 +1085,3 @@ class MutalyzerService(DefinitionBase):
         return descriptions
     #getdbSNPDescriptions
 #MutalyzerService
-
-
-# WSGI application for use with e.g. Apache/mod_wsgi
-soap_application = Application([MutalyzerService], mutalyzer.SOAP_NAMESPACE,
-                               'Mutalyzer')
-# Note: We would like to create the wsgi.Application instance only in the
-# bin/mutalyzer-webservice.wsgi script, but unfortunately this breaks the
-# get_wsdl method of soap_application which we use to generate API
-# documentation in website.py.
-application = wsgi.Application(soap_application)
diff --git a/mutalyzer/services/soap.py b/mutalyzer/services/soap.py
new file mode 100644
index 0000000000000000000000000000000000000000..31bb4e67d99376e8504c26af1a694dce1f19976d
--- /dev/null
+++ b/mutalyzer/services/soap.py
@@ -0,0 +1,25 @@
+"""
+Mutalyzer SOAP/1.1 webservice.
+"""
+
+
+from rpclib.application import Application
+from rpclib.protocol.soap import Soap11
+from rpclib.server.wsgi import WsgiApplication
+
+import mutalyzer
+from mutalyzer.services import rpc
+
+
+# SOAP/1.1 application.
+application = Application([rpc.MutalyzerService], tns=mutalyzer.SOAP_NAMESPACE,
+                          in_protocol=Soap11(), out_protocol=Soap11(),
+                          name='Mutalyzer')
+
+
+# Below we define WSGI applications for use with e.g. Apache/mod_wsgi.
+# Note: We would like to create the wsgi.Application instance only in the
+#     bin/mutalyzer-webservice.wsgi script, but unfortunately this breaks the
+#     get_interface_document method of rpclib which we use to generate API
+#     documentation in website.py.
+wsgi_application = WsgiApplication(application)
diff --git a/mutalyzer/templates/webservdoc.html b/mutalyzer/templates/webservdoc.html
deleted file mode 100644
index 03a27cdf264533d4a954fca8e293f63a96198974..0000000000000000000000000000000000000000
--- a/mutalyzer/templates/webservdoc.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<html>
-  <head>
-    <title></title>
-  </head>
-  <body>
-    <div metal:define-macro="content">
-      <script type="text/javascript">
-        function setHeight() {
-          parent.document.getElementById('docframe').height = 
-            document['body'].scrollHeight * 32;
-        }
-      </script> 
-      <center>
-        <h3>Webservices documentation</h3>
-      </center>
-      <iframe 
-        id = "docframe"
-        src = "documentation" 
-        width="100%" 
-        frameborder = "0"
-        scrolling = "no"
-        onload = "setHeight();">
-      </iframe>
-    </div>
-  </body>
-</html>
diff --git a/mutalyzer/templates/webservices.html b/mutalyzer/templates/webservices.html
index f1a7463d7191a3c913ef89ab5d148e81747953a9..c9dbd3040e54ba6496e2780586343f0098b63225 100644
--- a/mutalyzer/templates/webservices.html
+++ b/mutalyzer/templates/webservices.html
@@ -5,14 +5,19 @@
   <body>
     <div metal:define-macro="content">
     <center>
-      <h3>Webservices page</h3>
+      <h3>Webservices</h3>
     </center>
     <br>
-    Most Mutalyzer functionality is available trough a SOAP webservice.
+    Most Mutalyzer functionality is programmatically available trough two
+    interfaces: a SOAP webservice and a HTTP/RPC+JSON webservice.
+    <br>
+    <h3>SOAP webservice</h3>
     A <a href="services/?wsdl">WSDL description</a> is available
-    for easy generation of client programs in many languages.
-    <h3>Example clients</h3>
-    The following are some example client programs for the webservice. They
+    for easy generation of client programs in many languages. See the
+    <a href = "soap-api">annotated API</a> for detailed documentation.
+    <br>
+    <br>
+    The following are some example client programs for SOAP webservice. They
     use the <a href="documentation#op.checkSyntax">checkSyntax</a> method
     to determine if a variant description adheres to the <span class="helper"
       title="Human Genome Variation Society standard variant nomenclature">
@@ -32,9 +37,17 @@
     Here is an example that could be used for
       <a href="download/textmining.py">text mining</a> on a
       <a href="downloads/textmining_sample.txt">sample</a> input file.
-    <h3>Documentation</h3>
-    Also see the <a href = "documentation">documentation</a> page for a full
-    description of the webservice.
+    <h3>HTTP/RPC+JSON webservice</h3>
+    The HTTP/RPC+JSON webservice is experimental and currently undocumented.
+    It can be called using HTTP GET requests on <code tal:content = "structure string:${location}${serviceJsonLocation}/method?param=value"></code> where <code>method</code> is the name    of the method to be called and method parameters are expected as <code>param=value</code> query string parameters. Responses are JSON-encoded.
+    <br>
+    <br>
+    Example: <a tal:attributes="href string:${location}${serviceJsonLocation}/checkSyntax?variant=AB026906.1:c.274del" tal:content="structure string:checkSyntax?variant=AB026906.1:c.274del"></a>
+    <br>
+    <br>
+    For now, you can work from this example using the above mentioned
+    <a href="soap-api">annotated SOAP API</a>, since the HTTP/RPC+JSON
+    webservice mirrors the functionality of the SOAP webservice.
   </div>
   </body>
 </html>
diff --git a/mutalyzer/website.py b/mutalyzer/website.py
index 2c956243546e6475d8ede439cd7f9516b8edb84e..15f5dcf41e477dc1e9bc04317e2e83e83979f9da 100644
--- a/mutalyzer/website.py
+++ b/mutalyzer/website.py
@@ -3,7 +3,8 @@ General Mutalyzer website interface.
 """
 
 
-WEBSERVICE_LOCATION = '/services'
+SERVICE_SOAP_LOCATION = '/services'
+SERVICE_JSON_LOCATION = '/json'
 WSDL_VIEWER = 'templates/wsdl-viewer.xsl'
 GENOME_BROWSER_URL = 'http://genome.ucsc.edu/cgi-bin/hgTracks?db=hg19&position={chromosome}:{start}-{stop}&hgt.customText={bed_file}'
 
@@ -28,12 +29,13 @@ from lxml import etree
 from cStringIO import StringIO
 from simpletal import simpleTALES
 from simpletal import simpleTAL
+from rpclib.interface.wsdl import Wsdl11
 
 import mutalyzer
 from mutalyzer import util
 from mutalyzer import config
 from mutalyzer.grammar import Grammar
-from mutalyzer import webservice
+from mutalyzer.services import soap
 from mutalyzer import variantchecker
 from mutalyzer.output import Output
 from mutalyzer.mapping import Converter
@@ -59,7 +61,6 @@ urls = (
     '/(disclaimer)',                            'Static',
     '/(nameGenerator)',                         'Static',
     '/(webservices)',                           'Static',
-    '/(webservdoc)',                            'Static',
     '/checkForward',                            'CheckForward',
     '/check',                                   'Check',
     '/bed',                                     'Bed',
@@ -71,7 +72,7 @@ urls = (
     '/batch([a-zA-Z]+)?',                       'BatchChecker',
     '/progress',                                'BatchProgress',
     '/Results_(\d+)\.txt',                      'BatchResult',
-    '/documentation',                           'Documentation',
+    '/soap-api',                                'SoapApi',
     '/Variant_info',                            'VariantInfo',
     '/getGS',                                   'GetGS',
     '/download/([a-zA-Z-]+\.(?:py|cs|php|rb))', 'Download',
@@ -134,6 +135,8 @@ class render_tal:
 
             context.addGlobal('interactive', not standalone)
 
+            context.addGlobal('location', web.ctx.homedomain + web.ctx.homepath)
+
             for name, value in self.globals.items():
                 context.addGlobal(name, value)
 
@@ -177,7 +180,10 @@ render = render_tal(os.path.join(mutalyzer.package_root(), 'templates'),
     'releaseDate'         : mutalyzer.__date__,
     'release'             : mutalyzer.RELEASE,
     'copyrightYears'      : mutalyzer.COPYRIGHT_YEARS,
-    'contactEmail'        : config.get('email')})
+    'contactEmail'        : config.get('email'),
+    'serviceSoapLocation' : SERVICE_SOAP_LOCATION,
+    'serviceJsonLocation' : SERVICE_JSON_LOCATION
+})
 
 # web.py application
 app = web.application(urls, globals(), autoreload = False)
@@ -1415,7 +1421,7 @@ class Uploader:
 #Uploader
 
 
-class Documentation:
+class SoapApi:
     """
     SOAP webservice documentation.
     """
@@ -1442,8 +1448,10 @@ class Documentation:
         @todo: Use configuration value for .xsl location.
         @todo: Cache this transformation.
         """
-        url = web.ctx.homedomain + web.ctx.homepath + WEBSERVICE_LOCATION
-        wsdl_handle = StringIO(webservice.soap_application.get_wsdl(url))
+        url = web.ctx.homedomain + web.ctx.homepath + SERVICE_SOAP_LOCATION
+        wsdl = Wsdl11(soap.application.interface)
+        wsdl.build_interface_document(url)
+        wsdl_handle = StringIO(wsdl.get_interface_document())
         xsl_handle = open(os.path.join(mutalyzer.package_root(), WSDL_VIEWER),
                           'r')
         wsdl_doc = etree.parse(wsdl_handle)
@@ -1453,7 +1461,7 @@ class Documentation:
         web.header('Content-Type', 'text/html')
         return str(transform(wsdl_doc))
     #GET
-#Documentation
+#SoapApi
 
 
 class Static:
diff --git a/setup.py b/setup.py
index 2899ae3d0673eb0b2cfae85305df8e294270a744..e27929607de526dee671fcdb23b557ef72283f3c 100644
--- a/setup.py
+++ b/setup.py
@@ -21,7 +21,8 @@ setup(
              'bin/mutalyzer-batchd',
              'bin/mutalyzer-cache-sync',
              'bin/mutalyzer-mapping-update',
-             'bin/mutalyzer-webservice.wsgi',
+             'bin/mutalyzer-soap-service.wsgi',
+             'bin/mutalyzer-json-service.wsgi',
              'bin/mutalyzer-website.wsgi'],
     zip_safe=False
 )
diff --git a/tests/test_services_json.py b/tests/test_services_json.py
new file mode 100644
index 0000000000000000000000000000000000000000..6055a185aee5447062c14f7050b5f3ad907b4f76
--- /dev/null
+++ b/tests/test_services_json.py
@@ -0,0 +1,68 @@
+"""
+Tests for the SOAP interface to Mutalyzer.
+"""
+
+
+from urllib import urlencode
+import simplejson as json
+import requests
+from nose.tools import *
+import mutalyzer
+
+
+SERVICE_ROOT = 'http://localhost/mutalyzer/json/'
+
+
+def call(method, **kwargs):
+    """
+    Do a HTTP/RPC request and decode the JSON response.
+    """
+    r = requests.get(SERVICE_ROOT + method + '?' + urlencode(kwargs))
+    return json.loads(r.content)
+
+
+class TestServicesJson():
+    """
+    Test the Mutalyzer HTTP/RPC+JSON interface.
+    """
+    def test_checksyntax_valid(self):
+        """
+        Running checkSyntax with a valid variant name should return True.
+        """
+        r = call('checkSyntax', variant='AB026906.1:c.274G>T')
+        assert_equal(r['CheckSyntaxOutput']['valid'], True)
+
+    def test_checksyntax_invalid(self):
+        """
+        Running checkSyntax with an invalid variant name should return False
+        and give at least one error message.
+        """
+        r = call('checkSyntax', variant='0:abcd')
+        assert_equal(r['CheckSyntaxOutput']['valid'], False)
+        assert len(r['CheckSyntaxOutput']['messages']['SoapMessage']) >= 1
+
+    def test_checksyntax_empty(self):
+        """
+        Running checkSyntax with no variant name should raise exception.
+        """
+        r = call('checkSyntax')
+        assert r['Fault']
+
+    def test_transcriptinfo_valid(self):
+        """
+        Running transcriptInfo with valid arguments should get us a Transcript
+        object.
+        """
+        r = call('transcriptInfo', LOVD_ver='123', build='hg19',
+                 accNo='NM_002001.2')
+        assert_equal(r['Transcript']['trans_start'], -99)
+        assert_equal(r['Transcript']['trans_stop'], 1066)
+        assert_equal(r['Transcript']['CDS_stop'], 774)
+
+    def test_info(self):
+        """
+        Running the info method should give us some version information.
+        """
+        r = call('info')
+        assert_equal(type(r['InfoOutput']['versionParts']['string']), list)
+        assert_equal(r['InfoOutput']['version'], mutalyzer.__version__)
diff --git a/tests/test_webservice.py b/tests/test_services_soap.py
similarity index 97%
rename from tests/test_webservice.py
rename to tests/test_services_soap.py
index 07e96ba983f5d1f9785c73c5f8996363bf6763aa..0fdbec9a7be4227c6976adcd9ab0d6407d37a3b8 100644
--- a/tests/test_webservice.py
+++ b/tests/test_services_soap.py
@@ -26,7 +26,7 @@ logging.basicConfig(level=logging.INFO)
 for logger in ('suds.metrics', 'suds.wsdl', 'suds.xsd.schema',
                'suds.xsd.sxbasic', 'suds.xsd.sxbase', 'suds.xsd.query',
                'suds.transport.http', 'suds.xsd.deplist', 'suds.mx.core',
-               'suds.mx.literal', 'suds.resolver'):
+               'suds.mx.literal', 'suds.resolver', 'suds.client'):
     logging.getLogger(logger).setLevel(logging.ERROR)
 
 
@@ -46,11 +46,10 @@ class TestWSDL():
         assert 'name="Mutalyzer"' in wsdl
 
 
-class TestWebservice():
+class TestServicesSoap():
     """
     Test the Mutalyzer SOAP interface.
     """
-
     def setUp(self):
         """
         Initialize webservice entrypoint.
@@ -367,23 +366,23 @@ class TestWebservice():
         r = self.client.service.runMutalyzer('NG_012772:g.18964del')
         assert_equal(r.errors, 0)
         assert_equal(r.referenceId, 'NG_012772')
-        assert_equal(r.sourceId, 'NG_012772.1')
+        assert_equal(r.sourceId, 'NG_012772.3')
         assert_equal(r.sourceAccession, 'NG_012772')
-        assert_equal(r.sourceVersion, '1')
-        assert_equal(r.sourceGi, '256574794')
+        assert_equal(r.sourceVersion, '3')
+        assert_equal(r.sourceGi, '388428999')
         assert_equal(r.molecule, 'g')
 
     def test_runmutalyzer_reference_info_ng_version(self):
         """
         Get reference info for an NG variant with version.
         """
-        r = self.client.service.runMutalyzer('NG_012772:g.18964del')
+        r = self.client.service.runMutalyzer('NG_012772.3:g.18964del')
         assert_equal(r.errors, 0)
-        assert_equal(r.referenceId, 'NG_012772')
-        assert_equal(r.sourceId, 'NG_012772.1')
+        assert_equal(r.referenceId, 'NG_012772.3')
+        assert_equal(r.sourceId, 'NG_012772.3')
         assert_equal(r.sourceAccession, 'NG_012772')
-        assert_equal(r.sourceVersion, '1')
-        assert_equal(r.sourceGi, '256574794')
+        assert_equal(r.sourceVersion, '3')
+        assert_equal(r.sourceGi, '388428999')
         assert_equal(r.molecule, 'g')
 
     def test_runmutalyzer_reference_info_gi(self):
diff --git a/tests/test_website.py b/tests/test_website.py
index 898074d01698008ebd9e99b9e5be28d1f0a342c4..7589230cd9fc7fc31413397e92c8acc034db6c86 100644
--- a/tests/test_website.py
+++ b/tests/test_website.py
@@ -630,11 +630,11 @@ facilisi."""
         assert_equal(r.content_type, 'text/plain')
         r.mustcontain('NM_003002.1:c.3_4insG')
 
-    def test_soap_documentation(self):
+    def test_annotated_soap_api(self):
         """
         Test the SOAP documentation generated from the WSDL.
         """
-        r = self.app.get('/documentation')
+        r = self.app.get('/soap-api')
         assert_equal(r.content_type, 'text/html')
         r.mustcontain('Web Service: Mutalyzer')