Repository: softwaremill/elasticmq
Pull request #815 (2023-05-15 05:54:36)

Implement AWS JSON protocol 1.0
Implement AMZ Json Protocol 1.0.

- [x] Detect AMZ Json Protocol with content type header and parse the request
- [x] Respond with correct error format
~~- [ ] (optional) Customize serialization (use camelCase in case classes and respond with PascalCase)~~
- [x] Support create queue
- [x] Support list queues
- [x] Support send message (need more info regarding MD5OfMessageSystemAttributes)
- [x] Support send message batch
- [x] Support receive message
- [x] Support delete message
- [x] Support delete message batch
- [x] Support get queue URL
- [x] Support purge queue
- [x] Support change message visibility 
- [x] Support change message visibility batch
- [x] Support delete queue
- [x] Support get queue attributes
- [x] Support set queue attributes
- [x] Support add permission
- [x] Support tag queue
- [x] Support untag queue
- [x] Support list tags of queue
- [x] Check if Redrive policy is working (it needs set queue attributes)
~~- [ ] Add ListDeadLetterSourceQueues Action `https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_ListDeadLetterSourceQueues.html`~~
~~- [ ] Add RemovePermission Action `https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_RemovePermission.html`~~
- [x] Unify how message attributes are parsed and validated 

flsh86: Refactoring with AkkaSupport trait

- [] Respond with correct error format
- [x] Support create queue
- [] Support list queues
- [] Support send message (need more info regarding MD5OfMessageSystemAttributes)
- [x] Support send message batch
- [] Support receive message
- [] Support delete message
- [] Support delete message batch
- [] Support get queue URL
- [] Support purge queue
- [] Support change message visibility 
- [] Support change message visibility batch
- [] Support delete queue
- [] Support get queue attributes
- [] Support set queue attributes
- [] Support add permission
- [] Support tag queue
- [] Support untag queue
- [] Support list tags of queue
- [] Check if Redrive policy is working (it needs set queue attributes)
adamw: I left some comments, looks good overall :)

What about testing? Will it be possible to test both the XML and JSON interfaces?

And also ... @micossow maybe you could take a look as well? You're probably most up-to-date with EMQ :)

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a257db92..13eccf48 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -27,6 +27,20 @@ jobs:
         with:
           node-version: 16
 
+      - name: Install AWS cli 1
+        run: |
+          curl "https://s3.amazonaws.com/aws-cli/awscli-bundle-1.27.127.zip" -o "awscliv1.zip"
+          unzip ./awscliv1.zip
+          ls
+          sudo ./awscli-bundle/install -i /usr/local/aws1 -b /usr/local/bin/aws1
+
+      - name: Install AWS cli 2
+        run: |
+          curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
+          unzip ./awscliv2.zip
+          ls
+          sudo ./aws/install -i /usr/local/aws2 -b /usr/local/bin/aws2
+
       - name: Cache SBT
         id: cache-sbt
         uses: actions/cache@v2
@@ -39,7 +53,7 @@ jobs:
 
       - name: Run tests
         id: run-tests
-        run: SCALA_MAJOR_VERSION=${{ env.scala }} sbt root/test
+        run: SCALA_MAJOR_VERSION=${{ env.scala }} sbt "root/testOnly * -- -l org.elasticmq.rest.sqs.Only213"
 
   ci-2-13:
     # run on external PRs, but not on internal PRs since those will be run by push to branch
@@ -62,6 +76,18 @@ jobs:
         with:
           node-version: 16
 
+      - name: Install AWS cli 1
+        run: |
+          curl "https://s3.amazonaws.com/aws-cli/awscli-bundle-1.27.127.zip" -o "awscliv1.zip"
+          unzip -qq ./awscliv1.zip
+          sudo ./awscli-bundle/install -i /usr/local/aws1 -b /usr/local/bin/aws1
+
+      - name: Install AWS cli 2
+        run: |
+          curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
+          unzip -qq ./awscliv2.zip
+          sudo ./aws/install -i /usr/local/aws2 -b /usr/local/bin/aws2
+
       - name: Cache SBT
         id: cache-sbt
         uses: actions/cache@v2
diff --git a/.gitignore b/.gitignore
index 8863a803..c09a0e54 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,5 @@ target
 out
 project/boot
 /bin/
+
+.bsp
\ No newline at end of file
diff --git a/.scalafmt.conf b/.scalafmt.conf
index 80bb9262..8548e033 100644
--- a/.scalafmt.conf
+++ b/.scalafmt.conf
@@ -1,3 +1,3 @@
-version=3.7.3
+version=3.7.4
 maxColumn = 120
 runner.dialect = scala213
diff --git a/native-server/src/main/resources/META-INF/native-image/reflect-config.json b/native-server/src/main/resources/META-INF/native-image/reflect-config.json
index 4e3caf84..7735f92c 100644
--- a/native-server/src/main/resources/META-INF/native-image/reflect-config.json
+++ b/native-server/src/main/resources/META-INF/native-image/reflect-config.json
@@ -344,6 +344,34 @@
 {
   "name":"char[]"
 },
+{
+  "name":"com.sun.crypto.provider.AESCipher$General",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"com.sun.crypto.provider.ARCFOURCipher",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"com.sun.crypto.provider.ChaCha20Cipher$ChaCha20Poly1305",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"com.sun.crypto.provider.DESCipher",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"com.sun.crypto.provider.DESedeCipher",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"com.sun.crypto.provider.DHParameters",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"com.sun.crypto.provider.GaloisCounterMode$AESGCM",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
 {
   "name":"com.sun.crypto.provider.TlsMasterSecretGenerator",
   "methods":[{"name":"<init>","parameterTypes":[] }]
@@ -419,12 +447,20 @@
   "name":"java.lang.Character",
   "fields":[{"name":"TYPE"}]
 },
+{
+  "name":"java.lang.Class",
+  "methods":[{"name":"getModule","parameterTypes":[] }]
+},
 {
   "name":"java.lang.ClassValue"
 },
 {
   "name":"java.lang.Class[]"
 },
+{
+  "name":"java.lang.Deprecated",
+  "queryAllPublicMethods":true
+},
 {
   "name":"java.lang.Double",
   "fields":[{"name":"TYPE"}]
@@ -441,6 +477,10 @@
   "name":"java.lang.Long",
   "fields":[{"name":"TYPE"}]
 },
+{
+  "name":"java.lang.Module",
+  "methods":[{"name":"getDescriptor","parameterTypes":[] }]
+},
 {
   "name":"java.lang.RuntimePermission"
 },
@@ -459,6 +499,10 @@
 {
   "name":"java.lang.String[]"
 },
+{
+  "name":"java.lang.Thread",
+  "methods":[{"name":"isVirtual","parameterTypes":[] }]
+},
 {
   "name":"java.lang.Throwable",
   "methods":[{"name":"getSuppressed","parameterTypes":[] }]
@@ -521,7 +565,8 @@
 },
 {
   "name":"java.lang.management.ThreadInfo",
-  "allPublicMethods":true
+  "allPublicMethods":true,
+  "fields":[{"name":"virtual"}]
 },
 {
   "name":"java.lang.reflect.Field[]"
@@ -606,6 +651,9 @@
   "fields":[{"name":"thisX500Name"}],
   "methods":[{"name":"<init>","parameterTypes":["sun.security.x509.X500Name"] }]
 },
+{
+  "name":"javax.smartcardio.CardPermission"
+},
 {
   "name":"jdk.management.jfr.ConfigurationInfo",
   "allPublicMethods":true
@@ -694,6 +742,176 @@
   "allDeclaredFields":true,
   "allPublicMethods":true
 },
+{
+  "name":"org.elasticmq.rest.sqs.BatchChangeMessageVisibilityResponseEntry",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.QueueReadableAttributeNames",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.BatchDeleteMessageResponseEntry",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.BatchMessageSendResponseEntry",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.BatchRequest",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.BatchResponse",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.ChangeMessageVisibilityBatchEntry",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.ChangeMessageVisibilityDirectives$ChangeMessageVisibilityActionRequest",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.CreateQueueActionRequest",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.CreateQueueResponse",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.DeleteMessageBatchEntry",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.DeleteMessageDirectives$DeleteMessageActionRequest",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.DeleteQueueActionRequest",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.Failed",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.GetQueueAttributesActionRequest",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.GetQueueAttributesResponse",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.GetQueueURLResponse",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.GetQueueUrlActionRequest",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.ListQueueTagsActionRequest",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.ListQueueTagsResponse",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.ListQueuesActionRequest",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.ListQueuesResponse",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.PurgeQueueDirectives$PurgeQueueActionRequest",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.ReceiveMessageDirectives$ReceiveMessageActionRequest",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.ReceiveMessageResponse",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.ReceivedMessage",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.TagsModule$TagNameAndValuesReader",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.SendMessageBatchDirectives$SendMessageBatchActionRequest",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.SendMessageDirectives$SendMessageActionRequest",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.SendMessageResponse",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.SetQueueAttributesActionRequest",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.TagQueueActionRequest",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.UntagQueueActionRequest",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
+{
+  "name":"org.elasticmq.rest.sqs.directives.ExceptionDirectives$ErrorResponse",
+  "allDeclaredFields":true,
+  "queryAllPublicMethods":true
+},
 {
   "name":"org.h2.Driver"
 },
@@ -881,6 +1099,9 @@
   "name":"sun.misc.Unsafe",
   "allDeclaredFields":true
 },
+{
+  "name":"sun.reflect.Reflection"
+},
 {
   "name":"sun.security.pkcs12.PKCS12KeyStore",
   "methods":[{"name":"<init>","parameterTypes":[] }]
@@ -889,6 +1110,14 @@
   "name":"sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12",
   "methods":[{"name":"<init>","parameterTypes":[] }]
 },
+{
+  "name":"sun.security.provider.DSA$SHA224withDSA",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.provider.DSA$SHA256withDSA",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
 {
   "name":"sun.security.provider.JavaKeyStore$JKS",
   "methods":[{"name":"<init>","parameterTypes":[] }]
@@ -905,18 +1134,42 @@
   "name":"sun.security.provider.SHA",
   "methods":[{"name":"<init>","parameterTypes":[] }]
 },
+{
+  "name":"sun.security.provider.SHA2$SHA224",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
 {
   "name":"sun.security.provider.SHA2$SHA256",
   "methods":[{"name":"<init>","parameterTypes":[] }]
 },
+{
+  "name":"sun.security.provider.SHA5$SHA384",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.provider.SHA5$SHA512",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
 {
   "name":"sun.security.provider.X509Factory",
   "methods":[{"name":"<init>","parameterTypes":[] }]
 },
+{
+  "name":"sun.security.rsa.PSSParameters",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
 {
   "name":"sun.security.rsa.RSAKeyFactory$Legacy",
   "methods":[{"name":"<init>","parameterTypes":[] }]
 },
+{
+  "name":"sun.security.rsa.RSAPSSSignature",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
+{
+  "name":"sun.security.rsa.RSASignature$SHA224withRSA",
+  "methods":[{"name":"<init>","parameterTypes":[] }]
+},
 {
   "name":"sun.security.ssl.KeyManagerFactoryImpl$SunX509",
   "methods":[{"name":"<init>","parameterTypes":[] }]
diff --git a/native-server/src/main/resources/META-INF/native-image/resource-config.json b/native-server/src/main/resources/META-INF/native-image/resource-config.json
index 448517e2..22289c52 100644
--- a/native-server/src/main/resources/META-INF/native-image/resource-config.json
+++ b/native-server/src/main/resources/META-INF/native-image/resource-config.json
@@ -4,9 +4,15 @@
     {
       "pattern":"\\QMETA-INF/MANIFEST.MF\\E"
     }, 
+    {
+      "pattern":"\\QMETA-INF/services/ch.qos.logback.classic.spi.Configurator\\E"
+    }, 
     {
       "pattern":"\\QMETA-INF/services/java.sql.Driver\\E"
     }, 
+    {
+      "pattern":"\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E"
+    }, 
     {
       "pattern":"\\Qakka-http-version.conf\\E"
     }, 
@@ -41,7 +47,7 @@
   "bundles":[{
       "name":"org.apache.commons.dbcp2.LocalStrings",
       "locales":[
-        "", 
+        "",
         "und"
       ]
     }]
diff --git a/project/build.properties b/project/build.properties
index 72413de1..40b3b8e7 100644
--- a/project/build.properties
+++ b/project/build.properties
@@ -1,1 +1,1 @@
-sbt.version=1.8.3
+sbt.version=1.9.0
diff --git a/project/plugins.sbt b/project/plugins.sbt
index a5a40918..9cf35d5d 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -7,7 +7,7 @@ addSbtPlugin("com.softwaremill.sbt-softwaremill" % "sbt-softwaremill-publish" %
 
 addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.1")
 
-addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.7")
+addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.8")
 
 addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.16")
 
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/AddPermissionDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/AddPermissionDirectives.scala
index 41592835..7b5a8e84 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/AddPermissionDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/AddPermissionDirectives.scala
@@ -1,19 +1,13 @@
 package org.elasticmq.rest.sqs
 
-import Constants._
 import org.elasticmq.rest.sqs.Action.AddPermission
 import org.elasticmq.rest.sqs.directives.ElasticMQDirectives
+import org.elasticmq.rest.sqs.model.RequestPayload
 
-trait AddPermissionDirectives { this: ElasticMQDirectives with QueueURLModule =>
-  def addPermission(p: AnyParams) = {
+trait AddPermissionDirectives { this: ElasticMQDirectives with QueueURLModule with ResponseMarshaller =>
+  def addPermission(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies) = {
     p.action(AddPermission) {
-      respondWith {
-        <AddPermissionResponse>
-          <ResponseMetadata>
-            <RequestId>{EmptyRequestId}</RequestId>
-          </ResponseMetadata>
-        </AddPermissionResponse>
-      }
+      emptyResponse("AddPermissionResponse")
     }
   }
 }
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/AttributesModule.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/AttributesModule.scala
index 4ae42a67..047e3d77 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/AttributesModule.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/AttributesModule.scala
@@ -119,3 +119,5 @@ trait AttributesModule {
     }
   }
 }
+
+object AttributesModule extends AttributesModule
\ No newline at end of file
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/BatchRequestsModule.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/BatchRequestsModule.scala
index 13474310..82541ab2 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/BatchRequestsModule.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/BatchRequestsModule.scala
@@ -1,57 +1,51 @@
 package org.elasticmq.rest.sqs
 
-import xml.NodeSeq
-import Constants.IdSubParameter
-import java.util.regex.Pattern
-
+import Constants.{EmptyRequestId, QueueUrlParameter}
 import org.elasticmq.Limits
 
+import java.util.regex.Pattern
+import spray.json.DefaultJsonProtocol._
+import spray.json.{JsonFormat, RootJsonFormat}
+
 import scala.concurrent.Future
+import scala.xml.Elem
 
 trait BatchRequestsModule {
   this: SQSLimitsModule with ActorSystemModule =>
 
-  def batchParametersMap(prefix: String, parameters: Map[String, String]): List[Map[String, String]] = {
-    val subParameters =
-      BatchRequestsModule.subParametersMaps(prefix, parameters)
+  def batchRequest[M <: BatchEntry, R](messagesData: List[M])(
+      single: (M, String, Int) => Future[R]
+  ): Future[BatchResponse[R]] = {
 
-    val uniqueIds = subParameters.map(_.get(IdSubParameter)).toSet
-    if (uniqueIds.size != subParameters.size) {
+    val uniqueIds = messagesData.map(_.Id).toSet
+    if (uniqueIds.size != messagesData.size) {
       throw new SQSException("AWS.SimpleQueueService.BatchEntryIdsNotDistinct")
     }
 
     Limits.verifyBatchSize(uniqueIds.size, sqsLimits).fold(error => throw new SQSException(error), identity)
 
-    subParameters
-  }
-
-  def batchRequest(prefix: String, parameters: AnyParams)(
-      single: (Map[String, String], String, Int) => Future[NodeSeq]
-  ): Future[NodeSeq] = {
-
-    val messagesData = batchParametersMap(prefix, parameters)
-
-    val futures = messagesData.zipWithIndex.map {
+    val result = messagesData.zipWithIndex.map {
       case (messageData, index) => {
-        val id = messageData(IdSubParameter)
-
-        try {
-          single(messageData, id, index)
-        } catch {
-          case e: SQSException =>
-            Future {
-              <BatchResultErrorEntry>
-                <Id>{id}</Id>
-                <SenderFault>true</SenderFault>
-                <Code>{e.code}</Code>
-                <Message>{e.message}</Message>
-              </BatchResultErrorEntry>
-            }
-        }
+        val id = messageData.Id
+
+        Future.unit
+          .flatMap(_ => single(messageData, id, index))
+          .map(Right(_))
+          .recoverWith { case e: SQSException =>
+            Future(Left(Failed(e.code, id, e.message, SenderFault = true)))
+          }
       }
     }
 
-    Future.sequence(futures).map(_.flatten)
+    Future
+      .sequence(result)
+      .map(
+        _.foldLeft((List.empty[Failed], List.empty[R])) {
+          case ((failures, successes), Left(failed))   => (failures :+ failed, successes)
+          case ((failures, successes), Right(success)) => (failures, successes :+ success)
+        }
+      )
+      .map((BatchResponse.apply[R] _).tupled)
   }
 }
 
@@ -61,9 +55,11 @@ object BatchRequestsModule {
     * <code>{prefix}.{discriminator}.key=value</code>, and for each discriminator builds a map of found key-value
     * mappings.
     */
-  def subParametersMaps(prefix: String, parameters: Map[String, String]): List[Map[String, String]] = {
+  def subParametersMaps[M](parameters: Map[String, String])(implicit
+      flatParamsReader: BatchFlatParamsReader[M]
+  ): List[M] = {
     val subParameters = collection.mutable.Map[String, Map[String, String]]()
-    val keyRegexp = (Pattern.quote(prefix) + "\\.([^.]+)\\.(.+)").r
+    val keyRegexp = (Pattern.quote(flatParamsReader.batchPrefix) + "\\.([^.]+)\\.(.+)").r
     parameters.foreach { case (key, value) =>
       keyRegexp.findFirstMatchIn(key).map { keyMatch =>
         val discriminator = keyMatch.group(1)
@@ -75,6 +71,49 @@ object BatchRequestsModule {
       }
     }
 
-    subParameters.toList.sortBy(_._1.toInt).map(_._2)
+    subParameters.toList.sortBy(_._1.toInt).map(_._2).map(flatParamsReader.read)
+  }
+}
+
+trait BatchEntry {
+  def Id: String
+}
+case class BatchRequest[M](
+    Entries: List[M],
+    QueueUrl: String
+)
+
+case class BatchResponse[R](Failed: List[Failed], Successful: List[R])
+
+object BatchResponse {
+  implicit def jsonFormat[R: JsonFormat]: RootJsonFormat[BatchResponse[R]] = jsonFormat2(BatchResponse.apply[R])
+}
+object BatchRequest {
+  implicit def jsonFormat[M: JsonFormat]: RootJsonFormat[BatchRequest[M]] = jsonFormat2(BatchRequest.apply[M])
+
+  implicit def queryParamReader[M: BatchFlatParamsReader]: FlatParamsReader[BatchRequest[M]] =
+    new FlatParamsReader[BatchRequest[M]] {
+      override def read(params: Map[String, String]): BatchRequest[M] = {
+        new BatchRequest[M](
+          BatchRequestsModule.subParametersMaps(params),
+          requiredParameter(params)(QueueUrlParameter)
+        )
+      }
+    }
+
+}
+case class Failed(Code: String, Id: String, Message: String, SenderFault: Boolean)
+
+object Failed {
+  implicit val format: RootJsonFormat[Failed] = jsonFormat4(Failed.apply)
+
+  implicit val xmlSerializer: XmlSerializer[Failed] = new XmlSerializer[Failed] {
+    override def toXml(t: Failed): Elem =
+      <BatchResultErrorEntry>
+        <Id>{t.Id}</Id>
+        <SenderFault>true</SenderFault>
+        <Code>{t.Code}</Code>
+        <Message>{t.Message}</Message>
+      </BatchResultErrorEntry>
   }
 }
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ChangeMessageVisibilityBatchDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ChangeMessageVisibilityBatchDirectives.scala
index 4f7df9f1..b35c2ccc 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ChangeMessageVisibilityBatchDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ChangeMessageVisibilityBatchDirectives.scala
@@ -6,45 +6,80 @@ import org.elasticmq.msg.UpdateVisibilityTimeout
 import org.elasticmq.actor.reply._
 import org.elasticmq.rest.sqs.Action.ChangeMessageVisibilityBatch
 import org.elasticmq.rest.sqs.directives.ElasticMQDirectives
+import org.elasticmq.rest.sqs.model.RequestPayload
+import spray.json.DefaultJsonProtocol._
+import spray.json.RootJsonFormat
+import scala.concurrent.Future
+import scala.xml.Elem
 
 trait ChangeMessageVisibilityBatchDirectives {
-  this: ElasticMQDirectives with BatchRequestsModule =>
-  def changeMessageVisibilityBatch(p: AnyParams) = {
+  this: ElasticMQDirectives with BatchRequestsModule with ResponseMarshaller =>
+  def changeMessageVisibilityBatch(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies) = {
     p.action(ChangeMessageVisibilityBatch) {
-      queueActorFromRequest(p) { queueActor =>
+      val batch = p.as[BatchRequest[ChangeMessageVisibilityBatchEntry]]
+
+      queueActorFromUrl(batch.QueueUrl) { queueActor =>
         val resultsFuture =
-          batchRequest("ChangeMessageVisibilityBatchRequestEntry", p) { (messageData, id, _) =>
-            val receiptHandle = messageData(ReceiptHandleParameter)
-            val visibilityTimeout = MillisVisibilityTimeout.fromSeconds(messageData(VisibilityTimeoutParameter).toLong)
-            val result = queueActor ? UpdateVisibilityTimeout(DeliveryReceipt(receiptHandle), visibilityTimeout)
-
-            result.map {
-              case Left(error) =>
-                <BatchResultErrorEntry>
-                  <Id>{id}</Id>
-                  <Code>{error.code}</Code>
-                  <Message>{error.message}</Message>
-                </BatchResultErrorEntry>
-              case Right(_) =>
-                <ChangeMessageVisibilityBatchResultEntry>
-                  <Id>{id}</Id>
-                </ChangeMessageVisibilityBatchResultEntry>
+          batchRequest(batch.Entries) { (messageData, id, _) =>
+            val result = queueActor ? UpdateVisibilityTimeout(
+              DeliveryReceipt(messageData.ReceiptHandle),
+              MillisVisibilityTimeout.fromSeconds(messageData.VisibilityTimeout)
+            )
+
+            result.flatMap{
+              case Right(_) => Future.successful(BatchChangeMessageVisibilityResponseEntry(id))
+              case Left(invalidHandle) =>
+                Future.failed(new SQSException(invalidHandle.code, errorMessage = Some(invalidHandle.message)))
             }
           }
 
-        resultsFuture.map { results =>
-          respondWith {
-            <ChangeMessageVisibilityBatchResponse>
-              <ChangeMessageVisibilityBatchResult>
-                {results}
-              </ChangeMessageVisibilityBatchResult>
-              <ResponseMetadata>
-                <RequestId>{EmptyRequestId}</RequestId>
-              </ResponseMetadata>
-            </ChangeMessageVisibilityBatchResponse>
-          }
-        }
+        resultsFuture.map(complete(_))
       }
     }
   }
 }
+
+case class BatchChangeMessageVisibilityResponseEntry(Id: String)
+
+object BatchChangeMessageVisibilityResponseEntry {
+  implicit val jsonFormat: RootJsonFormat[BatchChangeMessageVisibilityResponseEntry] = jsonFormat1(BatchChangeMessageVisibilityResponseEntry.apply)
+
+  implicit val xmlSerializer: XmlSerializer[BatchChangeMessageVisibilityResponseEntry] = new XmlSerializer[BatchChangeMessageVisibilityResponseEntry] {
+    override def toXml(t: BatchChangeMessageVisibilityResponseEntry): Elem =
+      <ChangeMessageVisibilityBatchResultEntry>
+        <Id>{t.Id}</Id>
+      </ChangeMessageVisibilityBatchResultEntry>
+  }
+
+  implicit def batchXmlSerializer(implicit successSerializer: XmlSerializer[BatchChangeMessageVisibilityResponseEntry]): XmlSerializer[BatchResponse[BatchChangeMessageVisibilityResponseEntry]]
+    = new XmlSerializer[BatchResponse[BatchChangeMessageVisibilityResponseEntry]] {
+      override def toXml(t: BatchResponse[BatchChangeMessageVisibilityResponseEntry]): Elem =
+        <ChangeMessageVisibilityBatchResponse>
+          <ChangeMessageVisibilityBatchResult>
+            {t.Successful.map(successSerializer.toXml) ++ t.Failed.map(XmlSerializer[Failed].toXml)}
+          </ChangeMessageVisibilityBatchResult>
+          <ResponseMetadata>
+            <RequestId>
+              {EmptyRequestId}
+            </RequestId>
+          </ResponseMetadata>
+        </ChangeMessageVisibilityBatchResponse>
+    }
+}
+
+case class ChangeMessageVisibilityBatchEntry(Id: String, ReceiptHandle: String, VisibilityTimeout: Long) extends BatchEntry
+
+object ChangeMessageVisibilityBatchEntry {
+  implicit val jsonFormat: RootJsonFormat[ChangeMessageVisibilityBatchEntry] = jsonFormat3(ChangeMessageVisibilityBatchEntry.apply)
+
+  implicit val queryReader: BatchFlatParamsReader[ChangeMessageVisibilityBatchEntry] = new BatchFlatParamsReader[ChangeMessageVisibilityBatchEntry] {
+    override def read(params: Map[String, String]): ChangeMessageVisibilityBatchEntry =
+      ChangeMessageVisibilityBatchEntry(
+        requiredParameter(params)(IdSubParameter),
+        requiredParameter(params)(ReceiptHandleParameter),
+        requiredParameter(params)(VisibilityTimeoutParameter).toLong
+      )
+
+    override def batchPrefix: String = "ChangeMessageVisibilityBatchRequestEntry"
+  }
+}
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ChangeMessageVisibilityDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ChangeMessageVisibilityDirectives.scala
index 2fe17863..de20aa5a 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ChangeMessageVisibilityDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ChangeMessageVisibilityDirectives.scala
@@ -1,35 +1,52 @@
 package org.elasticmq.rest.sqs
 
+import akka.http.scaladsl.model.HttpEntity
 import org.elasticmq.actor.reply._
 import org.elasticmq.msg.UpdateVisibilityTimeout
 import org.elasticmq.rest.sqs.Action.ChangeMessageVisibility
 import org.elasticmq.rest.sqs.Constants._
 import org.elasticmq.rest.sqs.directives.ElasticMQDirectives
 import org.elasticmq.{DeliveryReceipt, MillisVisibilityTimeout}
+import spray.json.RootJsonFormat
+import spray.json.DefaultJsonProtocol._
+import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
+import org.elasticmq.rest.sqs.model.RequestPayload
 
-trait ChangeMessageVisibilityDirectives { this: ElasticMQDirectives =>
-  def changeMessageVisibility(p: AnyParams) = {
+trait ChangeMessageVisibilityDirectives { this: ElasticMQDirectives with ResponseMarshaller =>
+  def changeMessageVisibility(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies) = {
     p.action(ChangeMessageVisibility) {
-      queueActorFromRequest(p) { queueActor =>
-        (p.requiredParam(ReceiptHandleParameter) and p.requiredParam(VisibilityTimeoutParameter)) {
-          (receipt, visibilityTimeout) =>
-            val result = queueActor ? UpdateVisibilityTimeout(
-              DeliveryReceipt(receipt),
-              MillisVisibilityTimeout.fromSeconds(visibilityTimeout.toLong)
-            )
-            result.map {
-              case Left(error) => throw new SQSException(error.code, errorMessage = Some(error.message))
-              case Right(_) =>
-                respondWith {
-                  <ChangeMessageVisibilityResponse>
-                  <ResponseMetadata>
-                    <RequestId>{EmptyRequestId}</RequestId>
-                  </ResponseMetadata>
-                </ChangeMessageVisibilityResponse>
-                }
-            }
+      val requestParams = p.as[ChangeMessageVisibilityActionRequest]
+
+      queueActorFromUrl(requestParams.QueueUrl) { queueActor =>
+        val result = queueActor ? UpdateVisibilityTimeout(
+          DeliveryReceipt(requestParams.ReceiptHandle),
+          MillisVisibilityTimeout.fromSeconds(requestParams.VisibilityTimeout)
+        )
+        result.map {
+          case Left(error) => throw new SQSException(error.code, errorMessage = Some(error.message))
+          case Right(_) =>
+            emptyResponse("ChangeMessageVisibilityResponse")
         }
       }
     }
   }
+
+  case class ChangeMessageVisibilityActionRequest(QueueUrl: String, ReceiptHandle: String, VisibilityTimeout: Int)
+
+  object ChangeMessageVisibilityActionRequest {
+    implicit val requestJsonFormat: RootJsonFormat[ChangeMessageVisibilityActionRequest] = jsonFormat3(
+      ChangeMessageVisibilityActionRequest.apply
+    )
+
+    implicit val requestParamReader: FlatParamsReader[ChangeMessageVisibilityActionRequest] =
+      new FlatParamsReader[ChangeMessageVisibilityActionRequest] {
+        override def read(params: Map[String, String]): ChangeMessageVisibilityActionRequest = {
+          val queueUrl = requiredParameter(params)(QueueUrlParameter)
+          val receiptHandle = requiredParameter(params)(ReceiptHandleParameter)
+          val visibilityTimeout = requiredParameter(params)(VisibilityTimeoutParameter).toInt
+          ChangeMessageVisibilityActionRequest(queueUrl, receiptHandle, visibilityTimeout)
+        }
+      }
+  }
+
 }
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/CreateQueueDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/CreateQueueDirectives.scala
index 7b4733bd..a9f94fd3 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/CreateQueueDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/CreateQueueDirectives.scala
@@ -8,93 +8,86 @@ import org.elasticmq.rest.sqs.Constants._
 import org.elasticmq.rest.sqs.ParametersUtil._
 import org.elasticmq.rest.sqs.directives.ElasticMQDirectives
 import org.elasticmq.rest.sqs.model.RedrivePolicy.BackwardCompatibleRedrivePolicy
+import org.elasticmq.rest.sqs.model.RequestPayload
 import org.joda.time.Duration
+import spray.json.DefaultJsonProtocol._
 import spray.json.JsonParser.ParsingException
 import spray.json._
 
 import scala.async.Async._
 import scala.concurrent.Future
+import scala.xml.Elem
 
 trait CreateQueueDirectives {
-  this: ElasticMQDirectives with QueueURLModule with AttributesModule with TagsModule with SQSLimitsModule =>
+  this: ElasticMQDirectives with QueueURLModule with SQSLimitsModule with ResponseMarshaller =>
 
-  def createQueue(p: AnyParams) = {
+  def createQueue(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies) = {
     p.action(CreateQueue) {
       rootPath {
-        queueNameFromParams(p) { queueName =>
-          val attributes = attributeNameAndValuesReader.read(p)
-
-          val redrivePolicy =
-            try {
-              import org.elasticmq.rest.sqs.model.RedrivePolicyJson._
-              attributes
-                .get(RedrivePolicyParameter)
-                .map(_.parseJson.convertTo[BackwardCompatibleRedrivePolicy])
-            } catch {
-              case e: DeserializationException =>
-                logger.warn("Cannot deserialize the redrive policy attribute", e)
-                throw new SQSException("MalformedQueryString")
-              case e: ParsingException =>
-                logger.warn("Cannot parse the redrive policy attribute", e)
-                throw new SQSException("MalformedQueryString")
-            }
-
-          async {
-            redrivePolicy match {
-              case Some(rd) =>
-                if (await(queueManagerActor ? LookupQueue(rd.queueName)).isEmpty) {
-                  throw SQSException.nonExistentQueue
-                }
-
-                if (rd.maxReceiveCount < 1 || rd.maxReceiveCount > 1000) {
-                  throw SQSException.invalidParameterValue
-                }
-              case None =>
-            }
-
-            val secondsVisibilityTimeoutOpt = attributes.parseOptionalLong(VisibilityTimeoutParameter)
-            val secondsDelayOpt = attributes.parseOptionalLong(DelaySecondsAttribute)
-            val secondsReceiveMessageWaitTimeOpt = attributes.parseOptionalLong(ReceiveMessageWaitTimeSecondsAttribute)
-            val isFifo = attributes.get("FifoQueue").contains("true")
-            val hasContentBasedDeduplication = attributes.get("ContentBasedDeduplication").contains("true")
-
-            val newQueueData = CreateQueueData(
-              queueName,
-              secondsVisibilityTimeoutOpt.map(sec => MillisVisibilityTimeout.fromSeconds(sec)),
-              secondsDelayOpt.map(sec => Duration.standardSeconds(sec)),
-              secondsReceiveMessageWaitTimeOpt.map(sec => Duration.standardSeconds(sec)),
-              None,
-              None,
-              redrivePolicy.map(rd => DeadLettersQueueData(rd.queueName, rd.maxReceiveCount)),
-              isFifo,
-              hasContentBasedDeduplication,
-              tags = tagNameAndValuesReader.read(p)
-            )
-
-            secondsReceiveMessageWaitTimeOpt.foreach(messageWaitTime =>
-              Limits
-                .verifyMessageWaitTime(messageWaitTime, sqsLimits)
-                .fold(error => throw new SQSException(error), identity)
-            )
-
-            await(lookupOrCreateQueue(newQueueData))
-
-            queueURL(queueName) { url =>
-              respondWith {
-                <CreateQueueResponse>
-                  <CreateQueueResult>
-                    <QueueUrl>{url}</QueueUrl>
-                  </CreateQueueResult>
-                  <ResponseMetadata>
-                    <RequestId>{EmptyRequestId}</RequestId>
-                  </ResponseMetadata>
-                </CreateQueueResponse>
+
+        val requestParams = p.as[CreateQueueActionRequest]
+        val attributes = requestParams.Attributes.getOrElse(Map.empty)
+
+        val redrivePolicy =
+          try {
+            import org.elasticmq.rest.sqs.model.RedrivePolicyJson._
+            attributes
+              .get(RedrivePolicyParameter)
+              .map(_.parseJson.convertTo[BackwardCompatibleRedrivePolicy])
+          } catch {
+            case e: DeserializationException =>
+              logger.warn("Cannot deserialize the redrive policy attribute", e)
+              throw new SQSException("MalformedQueryString")
+            case e: ParsingException =>
+              logger.warn("Cannot parse the redrive policy attribute", e)
+              throw new SQSException("MalformedQueryString")
+          }
+
+        async {
+          redrivePolicy match {
+            case Some(rd) =>
+              if (await(queueManagerActor ? LookupQueue(rd.queueName)).isEmpty) {
+                throw SQSException.nonExistentQueue
+              }
+
+              if (rd.maxReceiveCount < 1 || rd.maxReceiveCount > 1000) {
+                throw SQSException.invalidParameterValue
               }
-            }
+            case None =>
           }
+
+          val secondsVisibilityTimeoutOpt = attributes.parseOptionalLong(VisibilityTimeoutParameter)
+          val secondsDelayOpt = attributes.parseOptionalLong(DelaySecondsAttribute)
+          val secondsReceiveMessageWaitTimeOpt = attributes.parseOptionalLong(ReceiveMessageWaitTimeSecondsAttribute)
+          val isFifo = attributes.get("FifoQueue").contains("true")
+          val hasContentBasedDeduplication = attributes.get("ContentBasedDeduplication").contains("true")
+
+          val newQueueData = CreateQueueData(
+            requestParams.QueueName,
+            secondsVisibilityTimeoutOpt.map(sec => MillisVisibilityTimeout.fromSeconds(sec)),
+            secondsDelayOpt.map(sec => Duration.standardSeconds(sec)),
+            secondsReceiveMessageWaitTimeOpt.map(sec => Duration.standardSeconds(sec)),
+            None,
+            None,
+            redrivePolicy.map(rd => DeadLettersQueueData(rd.queueName, rd.maxReceiveCount)),
+            isFifo,
+            hasContentBasedDeduplication,
+            tags = requestParams.tags.getOrElse(Map.empty)
+          )
+
+          secondsReceiveMessageWaitTimeOpt.foreach(messageWaitTime =>
+            Limits
+              .verifyMessageWaitTime(messageWaitTime, sqsLimits)
+              .fold(error => throw new SQSException(error), identity)
+          )
+
+          await(lookupOrCreateQueue(newQueueData))
+
+          queueURL(requestParams.QueueName) { url => complete(CreateQueueResponse(url)) }
         }
       }
     }
+
   }
 
   private def lookupOrCreateQueue[T](newQueueData: CreateQueueData): Future[Unit] = {
@@ -108,3 +101,41 @@ trait CreateQueueDirectives {
     }
   }
 }
+
+case class CreateQueueActionRequest(
+    QueueName: String,
+    Attributes: Option[Map[String, String]],
+    tags: Option[Map[String, String]]
+)
+
+object CreateQueueActionRequest {
+  implicit val requestJsonFormat: RootJsonFormat[CreateQueueActionRequest] = jsonFormat3(CreateQueueActionRequest.apply)
+
+  implicit val requestParamReader: FlatParamsReader[CreateQueueActionRequest] =
+    new FlatParamsReader[CreateQueueActionRequest] {
+      override def read(params: Map[String, String]): CreateQueueActionRequest = {
+        val attributes = AttributesModule.attributeNameAndValuesReader.read(params)
+        val tags = TagsModule.tagNameAndValuesReader.read(params)
+        val queueName = requiredParameter(params)(QueueNameParameter)
+        CreateQueueActionRequest(queueName, Some(attributes), Some(tags))
+      }
+    }
+}
+
+case class CreateQueueResponse(QueueUrl: String)
+
+object CreateQueueResponse {
+  implicit val format: RootJsonFormat[CreateQueueResponse] = jsonFormat1(CreateQueueResponse.apply)
+
+  implicit val xmlSerializer: XmlSerializer[CreateQueueResponse] = new XmlSerializer[CreateQueueResponse] {
+    override def toXml(t: CreateQueueResponse): Elem =
+      <CreateQueueResponse>
+        <CreateQueueResult>
+          <QueueUrl>{t.QueueUrl}</QueueUrl>
+        </CreateQueueResult>
+        <ResponseMetadata>
+          <RequestId>{EmptyRequestId}</RequestId>
+        </ResponseMetadata>
+      </CreateQueueResponse>
+  }
+}
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/DeleteMessageBatchDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/DeleteMessageBatchDirectives.scala
index 01b1ba7c..7b457895 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/DeleteMessageBatchDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/DeleteMessageBatchDirectives.scala
@@ -6,45 +6,78 @@ import org.elasticmq.msg.DeleteMessage
 import org.elasticmq.actor.reply._
 import org.elasticmq.rest.sqs.Action.DeleteMessageBatch
 import org.elasticmq.rest.sqs.directives.ElasticMQDirectives
+import org.elasticmq.rest.sqs.model.RequestPayload
+import spray.json.RootJsonFormat
+import spray.json.DefaultJsonProtocol._
 
-import scala.xml.{Node, Text}
+import scala.concurrent.Future
+import scala.xml.Elem
 
 trait DeleteMessageBatchDirectives {
-  this: ElasticMQDirectives with BatchRequestsModule =>
-  def deleteMessageBatch(p: AnyParams) = {
+  this: ElasticMQDirectives with BatchRequestsModule with ResponseMarshaller =>
+  def deleteMessageBatch(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies) = {
     p.action(DeleteMessageBatch) {
-      queueActorFromRequest(p) { queueActor =>
-        val resultsFuture = batchRequest("DeleteMessageBatchRequestEntry", p) { (messageData, id, _) =>
-          val receiptHandle = messageData(ReceiptHandleParameter)
-          val result = queueActor ? DeleteMessage(DeliveryReceipt(receiptHandle))
 
-          result.map {
-            case Left(error) =>
-              <BatchResultErrorEntry>
-                <Id>{id}</Id>
-                <Code>{error.code}</Code>
-                <Message>{error.message}</Message>
-              </BatchResultErrorEntry>
-            case Right(_) =>
-              <DeleteMessageBatchResultEntry>
-                <Id>{id}</Id>
-              </DeleteMessageBatchResultEntry>
-          }
-        }
+      val batch = p.as[BatchRequest[DeleteMessageBatchEntry]]
+
+      queueActorFromUrl(batch.QueueUrl) { queueActor =>
+        val resultsFuture = batchRequest(batch.Entries) { (messageData, id, _) =>
+          val receiptHandle = messageData.ReceiptHandle
+          val result = queueActor ? DeleteMessage(DeliveryReceipt(receiptHandle))
 
-        resultsFuture.map { results =>
-          respondWith {
-            <DeleteMessageBatchResponse>
-              <DeleteMessageBatchResult>
-                {results}
-              </DeleteMessageBatchResult>
-              <ResponseMetadata>
-                <RequestId>{EmptyRequestId}</RequestId>
-              </ResponseMetadata>
-            </DeleteMessageBatchResponse>
+          result.flatMap {
+            case Right(_) => Future.successful(BatchDeleteMessageResponseEntry(id))
+            case Left(invalidHandle) =>
+              Future.failed(new SQSException(invalidHandle.code, errorMessage = Some(invalidHandle.message)))
           }
         }
+        complete(resultsFuture)
       }
     }
   }
 }
+
+case class BatchDeleteMessageResponseEntry(Id: String)
+
+object BatchDeleteMessageResponseEntry {
+  implicit val jsonFormat: RootJsonFormat[BatchDeleteMessageResponseEntry] = jsonFormat1(
+    BatchDeleteMessageResponseEntry.apply
+  )
+
+  implicit val entryXmlSerializer: XmlSerializer[BatchDeleteMessageResponseEntry] =
+    new XmlSerializer[BatchDeleteMessageResponseEntry] {
+      override def toXml(t: BatchDeleteMessageResponseEntry): Elem =
+        <DeleteMessageBatchResultEntry>
+          <Id>{t.Id}</Id>
+        </DeleteMessageBatchResultEntry>
+    }
+
+  implicit def batchXmlSerializer[T](implicit successSerializer: XmlSerializer[T]): XmlSerializer[BatchResponse[T]] =
+    new XmlSerializer[BatchResponse[T]] {
+      override def toXml(t: BatchResponse[T]): Elem =
+        <DeleteMessageBatchResponse>
+        <DeleteMessageBatchResult>
+          {t.Successful.map(successSerializer.toXml) ++ t.Failed.map(XmlSerializer[Failed].toXml)}
+        </DeleteMessageBatchResult>
+        <ResponseMetadata>
+          <RequestId>{EmptyRequestId}</RequestId>
+        </ResponseMetadata>
+      </DeleteMessageBatchResponse>
+    }
+}
+case class DeleteMessageBatchEntry(Id: String, ReceiptHandle: String) extends BatchEntry
+
+object DeleteMessageBatchEntry {
+  implicit val jsonFormat: RootJsonFormat[DeleteMessageBatchEntry] = jsonFormat2(DeleteMessageBatchEntry.apply)
+
+  implicit val queryReader: BatchFlatParamsReader[DeleteMessageBatchEntry] =
+    new BatchFlatParamsReader[DeleteMessageBatchEntry] {
+      override def batchPrefix: String = "DeleteMessageBatchRequestEntry"
+
+      override def read(params: Map[String, String]): DeleteMessageBatchEntry =
+        DeleteMessageBatchEntry(
+          requiredParameter(params)(IdSubParameter),
+          requiredParameter(params)(ReceiptHandleParameter)
+        )
+    }
+}
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/DeleteMessageDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/DeleteMessageDirectives.scala
index 5b3a8b1e..d13e495b 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/DeleteMessageDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/DeleteMessageDirectives.scala
@@ -6,27 +6,39 @@ import org.elasticmq.actor.reply._
 import org.elasticmq.msg.DeleteMessage
 import org.elasticmq.rest.sqs.Action.{DeleteMessage => DeleteMessageAction}
 import org.elasticmq.rest.sqs.directives.ElasticMQDirectives
+import org.elasticmq.rest.sqs.model.RequestPayload
+import spray.json.DefaultJsonProtocol._
+import spray.json.RootJsonFormat
 
-trait DeleteMessageDirectives { this: ElasticMQDirectives =>
-  def deleteMessage(p: AnyParams) = {
+trait DeleteMessageDirectives { this: ElasticMQDirectives with ResponseMarshaller =>
+  def deleteMessage(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies) = {
     p.action(DeleteMessageAction) {
-      queueActorFromRequest(p) { queueActor =>
-        p.requiredParam(ReceiptHandleParameter) { receipt =>
-          val result = queueActor ? DeleteMessage(DeliveryReceipt(receipt))
+      val requestParams = p.as[DeleteMessageActionRequest]
 
-          result.map {
-            case Left(error) => throw new SQSException(error.code, errorMessage = Some(error.message))
-            case Right(_) =>
-              respondWith {
-                <DeleteMessageResponse>
-                  <ResponseMetadata>
-                    <RequestId>{EmptyRequestId}</RequestId>
-                  </ResponseMetadata>
-                </DeleteMessageResponse>
-              }
-          }
+      queueActorFromUrl(requestParams.QueueUrl) { queueActor =>
+        val result = queueActor ? DeleteMessage(DeliveryReceipt(requestParams.ReceiptHandle))
+        result.map {
+          case Left(error) => throw new SQSException(error.code, errorMessage = Some(error.message))
+          case Right(_)    => emptyResponse("DeleteMessageResponse")
         }
       }
     }
   }
+
+  case class DeleteMessageActionRequest(QueueUrl: String, ReceiptHandle: String)
+
+  object DeleteMessageActionRequest {
+    implicit val requestJsonFormat: RootJsonFormat[DeleteMessageActionRequest] = jsonFormat2(
+      DeleteMessageActionRequest.apply
+    )
+
+    implicit val requestParamReader: FlatParamsReader[DeleteMessageActionRequest] =
+      new FlatParamsReader[DeleteMessageActionRequest] {
+        override def read(params: Map[String, String]): DeleteMessageActionRequest = {
+          val queueUrl = requiredParameter(params)(QueueUrlParameter)
+          val receiptHandle = requiredParameter(params)(ReceiptHandleParameter)
+          DeleteMessageActionRequest(queueUrl, receiptHandle)
+        }
+      }
+  }
 }
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/DeleteQueueDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/DeleteQueueDirectives.scala
index 5c31ccb4..b1a308fe 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/DeleteQueueDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/DeleteQueueDirectives.scala
@@ -2,28 +2,39 @@ package org.elasticmq.rest.sqs
 
 import Constants._
 import org.elasticmq.actor.reply._
+
 import scala.async.Async._
 import org.elasticmq.msg.DeleteQueue
 import org.elasticmq.rest.sqs.Action.{DeleteQueue => DeleteQueueAction}
 import org.elasticmq.rest.sqs.directives.ElasticMQDirectives
+import org.elasticmq.rest.sqs.model.RequestPayload
+import spray.json.DefaultJsonProtocol._
+import spray.json.RootJsonFormat
 
-trait DeleteQueueDirectives { this: ElasticMQDirectives with QueueURLModule =>
-  def deleteQueue(p: AnyParams) = {
+trait DeleteQueueDirectives { this: ElasticMQDirectives with QueueURLModule with ResponseMarshaller =>
+  def deleteQueue(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies) = {
     p.action(DeleteQueueAction) {
-      queueActorAndNameFromRequest(p) {
-        (queueActor, queueName) => // We need the queue actor just to check that the queue exists
+      queueActorAndNameFromUrl(p.as[DeleteQueueActionRequest].QueueUrl) {
+        (_, queueName) => // We need the queue actor just to check that the queue exists
           async {
             await(queueManagerActor ? DeleteQueue(queueName))
-
-            respondWith {
-              <DeleteQueueResponse>
-              <ResponseMetadata>
-                <RequestId>{EmptyRequestId}</RequestId>
-              </ResponseMetadata>
-            </DeleteQueueResponse>
-            }
+            emptyResponse("DeleteQueueResponse")
           }
       }
     }
   }
 }
+
+case class DeleteQueueActionRequest(QueueUrl: String)
+
+object DeleteQueueActionRequest {
+  implicit val requestJsonFormat: RootJsonFormat[DeleteQueueActionRequest] = jsonFormat1(DeleteQueueActionRequest.apply)
+
+  implicit val requestParamReader: FlatParamsReader[DeleteQueueActionRequest] = new FlatParamsReader[DeleteQueueActionRequest] {
+    override def read(params: Map[String, String]): DeleteQueueActionRequest = {
+      new DeleteQueueActionRequest(
+        requiredParameter(params)(QueueUrlParameter)
+      )
+    }
+  }
+}
\ No newline at end of file
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/FlatParamsReader.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/FlatParamsReader.scala
new file mode 100644
index 00000000..a38a5dd9
--- /dev/null
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/FlatParamsReader.scala
@@ -0,0 +1,19 @@
+package org.elasticmq.rest.sqs
+
+trait FlatParamsReader[A] {
+  def read(params: Map[String, String]): A
+
+  protected def requiredParameter(params: Map[String, String])(n: String): String =
+    params.getOrElse(n, throw new SQSException(s"Missing required field: $n"))
+
+  protected def optionalParameter(params: Map[String, String])(n: String): Option[String] =
+    params.get(n)
+}
+
+object BatchFlatParamsReader {
+  def apply[A](implicit fpr: BatchFlatParamsReader[A]): BatchFlatParamsReader[A] = fpr
+}
+
+trait BatchFlatParamsReader[A] extends FlatParamsReader[A] {
+  def batchPrefix: String
+}
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/GetQueueUrlDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/GetQueueUrlDirectives.scala
index 4da04ee5..d69b6ceb 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/GetQueueUrlDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/GetQueueUrlDirectives.scala
@@ -2,27 +2,52 @@ package org.elasticmq.rest.sqs
 
 import org.elasticmq.rest.sqs.Action.GetQueueUrl
 import org.elasticmq.rest.sqs.Constants._
+import org.elasticmq.rest.sqs.GetQueueUrlRequest.{requestJsonFormat, requestParamReader}
 import org.elasticmq.rest.sqs.directives.ElasticMQDirectives
+import spray.json.DefaultJsonProtocol._
+import spray.json.RootJsonFormat
+import org.elasticmq.rest.sqs.model.RequestPayload
 
-trait GetQueueUrlDirectives { this: ElasticMQDirectives with QueueURLModule =>
-  def getQueueUrl(p: AnyParams) = {
+trait GetQueueUrlDirectives { this: ElasticMQDirectives with QueueURLModule with ResponseMarshaller =>
+  def getQueueUrl(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies) = {
     p.action(GetQueueUrl) {
       rootPath {
-        queueDataFromParams(p) { queueData =>
-          queueURL(queueData.name) { url =>
-            respondWith {
-              <GetQueueUrlResponse>
-                <GetQueueUrlResult>
-                  <QueueUrl>{url}</QueueUrl>
-                </GetQueueUrlResult>
-                <ResponseMetadata>
-                  <RequestId>{EmptyRequestId}</RequestId>
-                </ResponseMetadata>
-              </GetQueueUrlResponse>
-            }
-          }
+        val requestParams = p.as[GetQueueUrlActionRequest]
+        queueURL(requestParams.QueueName) { url =>
+          complete(GetQueueURLResponse(url))
         }
       }
     }
   }
 }
+
+case class GetQueueUrlActionRequest(QueueName: String, QueueOwnerAWSAccountId: Option[String])
+
+object GetQueueUrlRequest {
+  implicit val requestJsonFormat: RootJsonFormat[GetQueueUrlActionRequest] = jsonFormat2(GetQueueUrlActionRequest.apply)
+
+  implicit val requestParamReader: FlatParamsReader[GetQueueUrlActionRequest] =
+    new FlatParamsReader[GetQueueUrlActionRequest] {
+      override def read(params: Map[String, String]): GetQueueUrlActionRequest = {
+        val queueName = requiredParameter(params)(QueueNameParameter)
+        val queueOwnerAWSAccountId = optionalParameter(params)("QueueOwnerAWSAccountId")
+        GetQueueUrlActionRequest(queueName, queueOwnerAWSAccountId)
+      }
+    }
+}
+
+case class GetQueueURLResponse(QueueUrl: String)
+
+object GetQueueURLResponse {
+  implicit val format: RootJsonFormat[GetQueueURLResponse] = jsonFormat1(GetQueueURLResponse.apply)
+
+  implicit val xmlSerializer: XmlSerializer[GetQueueURLResponse] = t =>
+    <GetQueueUrlResponse>
+      <GetQueueUrlResult>
+        <QueueUrl>{t.QueueUrl}</QueueUrl>
+      </GetQueueUrlResult>
+      <ResponseMetadata>
+        <RequestId>{EmptyRequestId}</RequestId>
+      </ResponseMetadata>
+    </GetQueueUrlResponse>
+}
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ListQueuesDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ListQueuesDirectives.scala
index 5be869bf..ea0105e5 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ListQueuesDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ListQueuesDirectives.scala
@@ -5,12 +5,19 @@ import org.elasticmq.actor.reply._
 import org.elasticmq.msg.ListQueues
 import org.elasticmq.rest.sqs.Action.{ListQueues => ListQueuesAction}
 import org.elasticmq.rest.sqs.directives.ElasticMQDirectives
+import spray.json.DefaultJsonProtocol._
+import spray.json.RootJsonFormat
+import org.elasticmq.rest.sqs.model.RequestPayload
 
-trait ListQueuesDirectives { this: ElasticMQDirectives with QueueURLModule =>
-  def listQueues(p: AnyParams) = {
+import scala.xml.Elem
+
+trait ListQueuesDirectives { this: ElasticMQDirectives with QueueURLModule with ResponseMarshaller =>
+  def listQueues(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies) = {
     p.action(ListQueuesAction) {
       rootPath {
-        val prefixOption = p.get("QueueNamePrefix")
+        val payload = p.as[ListQueuesActionRequest]
+
+        val prefixOption = payload.QueueNamePrefix
         for {
           allQueueNames <- queueManagerActor ? ListQueues()
         } yield {
@@ -20,19 +27,53 @@ trait ListQueuesDirectives { this: ElasticMQDirectives with QueueURLModule =>
           }
 
           baseQueueURL { baseURL =>
-            respondWith {
-              <ListQueuesResponse>
-                <ListQueuesResult>
-                  {queueNames.map(queueName => <QueueUrl>{baseURL + "/" + queueName}</QueueUrl>)}
-                </ListQueuesResult>
-                <ResponseMetadata>
-                  <RequestId>{EmptyRequestId}</RequestId>
-                </ResponseMetadata>
-              </ListQueuesResponse>
-            }
+            complete(ListQueuesResponse(queueNames.map(queueName => s"$baseURL/$queueName").toList))
           }
         }
       }
     }
   }
 }
+
+case class ListQueuesActionRequest(
+    MaxResults: Option[Int],
+    NextToken: Option[String],
+    QueueNamePrefix: Option[String]
+)
+
+object ListQueuesActionRequest {
+
+  implicit val requestJsonFormat: RootJsonFormat[ListQueuesActionRequest] = jsonFormat3(ListQueuesActionRequest.apply)
+
+  implicit val requestParamReader: FlatParamsReader[ListQueuesActionRequest] =
+    new FlatParamsReader[ListQueuesActionRequest] {
+      override def read(params: Map[String, String]): ListQueuesActionRequest = {
+        new ListQueuesActionRequest(
+          params.get(MaxResultsParameter).map(_.toInt),
+          params.get(NextTokenParameter),
+          params.get(QueueNamePrefixParameter)
+        )
+      }
+    }
+
+}
+
+case class ListQueuesResponse(QueueUrls: List[String])
+
+object ListQueuesResponse {
+  implicit val format: RootJsonFormat[ListQueuesResponse] = jsonFormat1(ListQueuesResponse.apply)
+
+  implicit val xmlSerializer: XmlSerializer[ListQueuesResponse] = new XmlSerializer[ListQueuesResponse] {
+    override def toXml(t: ListQueuesResponse): Elem = {
+
+      <ListQueuesResponse>
+        <ListQueuesResult>
+          {t.QueueUrls.map(queueUrl => <QueueUrl>{queueUrl}</QueueUrl>)}
+        </ListQueuesResult>
+        <ResponseMetadata>
+          <RequestId>{EmptyRequestId}</RequestId>
+        </ResponseMetadata>
+      </ListQueuesResponse>
+    }
+  }
+}
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/MarshallerDependencies.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/MarshallerDependencies.scala
new file mode 100644
index 00000000..9209dbcf
--- /dev/null
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/MarshallerDependencies.scala
@@ -0,0 +1,23 @@
+package org.elasticmq.rest.sqs
+
+import akka.http.scaladsl.server.Directives._
+import org.elasticmq.rest.sqs.Constants.SqsDefaultVersion
+import scala.language.postfixOps
+
+case class MarshallerDependencies(protocol: AWSProtocol, xmlNsVersion: XmlNsVersion)
+
+sealed trait AWSProtocol extends Product with Serializable
+
+object AWSProtocol {
+  case object AWSQueryProtocol extends AWSProtocol
+  case object `AWSJsonProtocol1.0` extends AWSProtocol
+}
+
+case class XmlNsVersion(version: String)
+
+object XmlNsVersion {
+  def apply(): XmlNsVersion = XmlNsVersion(SqsDefaultVersion)
+
+  def extractXmlNs = parameter("Version" ?).map { versionOpt => versionOpt.fold(XmlNsVersion())(XmlNsVersion(_)) }
+
+}
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/MessageAttributesSupport.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/MessageAttributesSupport.scala
new file mode 100644
index 00000000..190be780
--- /dev/null
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/MessageAttributesSupport.scala
@@ -0,0 +1,35 @@
+package org.elasticmq.rest.sqs
+
+import org.elasticmq.{BinaryMessageAttribute, MessageAttribute, NumberMessageAttribute, StringMessageAttribute}
+import spray.json.{JsObject, JsString, JsValue, JsonReader, RootJsonFormat}
+import spray.json.DefaultJsonProtocol._
+
+trait MessageAttributesSupport {
+
+  implicit val messageAttributeJsonFormat: RootJsonFormat[MessageAttribute] = new RootJsonFormat[MessageAttribute] {
+
+    override def write(obj: MessageAttribute): JsValue = obj match {
+      case NumberMessageAttribute(value, _) =>
+        JsObject("DataType" -> JsString("Number"), "StringValue" -> JsString(value))
+      case StringMessageAttribute(value, _) =>
+        JsObject("DataType" -> JsString("String"), "StringValue" -> JsString(value))
+      case msg: BinaryMessageAttribute =>
+        JsObject("DataType" -> JsString("Binary"), "BinaryValue" -> JsString(msg.asBase64))
+    }
+
+    override def read(json: JsValue): MessageAttribute = {
+      val fields = json.asJsObject.fields
+
+      def pickField[O: JsonReader](name: String) =
+        fields.getOrElse(name, throw new SQSException(s"Field $name is required")).convertTo[O]
+
+      val dataType = pickField[String]("DataType")
+      dataType match {
+        case "Number" => NumberMessageAttribute(pickField[String]("StringValue"))
+        case "String" => StringMessageAttribute(pickField[String]("StringValue"))
+        case "Binary" => BinaryMessageAttribute(pickField[Array[Byte]]("BinaryValue"))
+      }
+    }
+  }
+
+}
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/PurgeQueueDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/PurgeQueueDirectives.scala
index 58d1141e..81272c67 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/PurgeQueueDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/PurgeQueueDirectives.scala
@@ -5,23 +5,35 @@ import org.elasticmq.msg.ClearQueue
 import org.elasticmq.rest.sqs.Action.PurgeQueue
 import org.elasticmq.rest.sqs.Constants._
 import org.elasticmq.rest.sqs.directives.ElasticMQDirectives
+import org.elasticmq.rest.sqs.model.RequestPayload
+import spray.json.DefaultJsonProtocol.{StringJsonFormat, jsonFormat1}
+import spray.json.RootJsonFormat
 
-trait PurgeQueueDirectives { this: ElasticMQDirectives with QueueURLModule =>
-  def purgeQueue(p: AnyParams) = {
+trait PurgeQueueDirectives { this: ElasticMQDirectives with QueueURLModule with ResponseMarshaller =>
+  def purgeQueue(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies) = {
     p.action(PurgeQueue) {
-      queueActorFromRequest(p) { queueActor =>
+      val requestParams = p.as[PurgeQueueActionRequest]
+
+      queueActorFromUrl(requestParams.QueueUrl){ queueActor =>
         for {
           _ <- queueActor ? ClearQueue()
         } yield {
-          respondWith {
-            <PurgeQueueResponse>
-              <ResponseMetadata>
-                <RequestId>{EmptyRequestId}</RequestId>
-              </ResponseMetadata>
-            </PurgeQueueResponse>
-          }
+          emptyResponse("PurgeQueueResponse")
         }
       }
     }
   }
+
+  case class PurgeQueueActionRequest(QueueUrl: String)
+
+  object PurgeQueueActionRequest {
+    implicit val requestJsonFormat: RootJsonFormat[PurgeQueueActionRequest] = jsonFormat1(PurgeQueueActionRequest.apply)
+
+    implicit val requestParamReader: FlatParamsReader[PurgeQueueActionRequest] = new FlatParamsReader[PurgeQueueActionRequest] {
+      override def read(params: Map[String, String]): PurgeQueueActionRequest = {
+        val queueUrl = requiredParameter(params)(QueueUrlParameter)
+        PurgeQueueActionRequest(queueUrl)
+      }
+    }
+  }
 }
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/QueueAttributesDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/QueueAttributesDirectives.scala
index 9627f05b..cb27e1b0 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/QueueAttributesDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/QueueAttributesDirectives.scala
@@ -2,54 +2,98 @@ package org.elasticmq.rest.sqs
 
 import akka.http.scaladsl.server.Route
 import org.elasticmq.rest.sqs.Action.{GetQueueAttributes, SetQueueAttributes}
+import org.elasticmq.rest.sqs.AttributesModule.attributesToXmlConverter
 import org.elasticmq.rest.sqs.Constants._
 import org.elasticmq.rest.sqs.directives.ElasticMQDirectives
+import org.elasticmq.rest.sqs.model.RequestPayload
+import spray.json.RootJsonFormat
+import spray.json.DefaultJsonProtocol._
+
 
 import scala.concurrent.Future
+import scala.xml.Elem
 
 trait QueueAttributesDirectives {
-  this: ElasticMQDirectives with QueueAttributesOps =>
+  this: ElasticMQDirectives with QueueAttributesOps with ResponseMarshaller =>
 
-  def getQueueAttributes(p: AnyParams): Route = {
+  def getQueueAttributes(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies): Route = {
     p.action(GetQueueAttributes) {
-      queueActorAndDataFromRequest(p) { (queueActor, queueData) =>
-        val attributesFuture = getQueueAttributes(p, queueActor, queueData)
-
-        def responseXml(attributes: List[(String, String)]) = {
-          <GetQueueAttributesResponse>
-            <GetQueueAttributesResult>
-              {attributesToXmlConverter.convert(attributes)}
-            </GetQueueAttributesResult>
-            <ResponseMetadata>
-              <RequestId>{EmptyRequestId}</RequestId>
-            </ResponseMetadata>
-          </GetQueueAttributesResponse>
-        }
+      val requestParams = p.as[GetQueueAttributesActionRequest]
+      queueActorAndDataFromQueueUrl(requestParams.QueueUrl) { (queueActor, queueData) =>
+        val attributesFuture: Future[List[(String, String)]] =
+          getQueueAttributes(requestParams.AttributeNames.getOrElse(List.empty), queueActor, queueData)
 
         attributesFuture.map { attributes =>
-          respondWith {
-            responseXml(attributes)
-          }
+            complete(GetQueueAttributesResponse(attributes.toMap))
         }
       }
     }
   }
 
-  def setQueueAttributes(p: AnyParams): Route = {
+  def setQueueAttributes(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies): Route = {
     p.action(SetQueueAttributes) {
-      queueActorFromRequest(p) { queueActor =>
-        val result = setQueueAttributes(p, queueActor, queueManagerActor)
-
+      val requestParameters = p.as[SetQueueAttributesActionRequest]
+      queueActorFromUrl(requestParameters.QueueUrl) { queueActor =>
+        val result = setQueueAttributes(requestParameters.Attributes, queueActor, queueManagerActor)
         Future.sequence(result).map { _ =>
-          respondWith {
-            <SetQueueAttributesResponse>
-              <ResponseMetadata>
-                <RequestId>{EmptyRequestId}</RequestId>
-              </ResponseMetadata>
-            </SetQueueAttributesResponse>
-          }
+          emptyResponse("SetQueueAttributesResponse")
         }
       }
     }
   }
 }
+
+case class GetQueueAttributesActionRequest(AttributeNames: Option[List[String]], QueueUrl: String)
+
+object GetQueueAttributesActionRequest {
+  implicit val responseJsonFormat: RootJsonFormat[GetQueueAttributesActionRequest] = jsonFormat2(
+    GetQueueAttributesActionRequest.apply
+  )
+
+  implicit val requestParamReader: FlatParamsReader[GetQueueAttributesActionRequest] =
+    new FlatParamsReader[GetQueueAttributesActionRequest] {
+      override def read(params: Map[String, String]): GetQueueAttributesActionRequest = {
+        val queueUrl = requiredParameter(params)(QueueUrlParameter)
+        val attributeNames =
+          AttributesModule.attributeNamesReader.read(params, QueueReadableAttributeNames.AllAttributeNames)
+        GetQueueAttributesActionRequest(Some(attributeNames), queueUrl)
+      }
+    }
+}
+
+case class GetQueueAttributesResponse(Attributes: Map[String, String])
+
+object GetQueueAttributesResponse {
+  implicit val responseJsonFormat: RootJsonFormat[GetQueueAttributesResponse] = jsonFormat1(
+    GetQueueAttributesResponse.apply
+  )
+
+  implicit val xmlSerializer: XmlSerializer[GetQueueAttributesResponse] = new XmlSerializer[GetQueueAttributesResponse] {
+    override def toXml(t: GetQueueAttributesResponse): Elem =
+      <GetQueueAttributesResponse>
+        <GetQueueAttributesResult>
+          {attributesToXmlConverter.convert(t.Attributes.toList)}
+        </GetQueueAttributesResult>
+        <ResponseMetadata>
+          <RequestId>{EmptyRequestId}</RequestId>
+        </ResponseMetadata>
+      </GetQueueAttributesResponse>
+  }
+}
+
+case class SetQueueAttributesActionRequest(Attributes: Map[String, String], QueueUrl: String)
+
+object SetQueueAttributesActionRequest {
+  implicit val jsonRequestFormat: RootJsonFormat[SetQueueAttributesActionRequest] = jsonFormat2(
+    SetQueueAttributesActionRequest.apply
+  )
+
+  implicit val requestParamReader: FlatParamsReader[SetQueueAttributesActionRequest] =
+    new FlatParamsReader[SetQueueAttributesActionRequest] {
+      override def read(params: Map[String, String]): SetQueueAttributesActionRequest = {
+        val queueUrl = requiredParameter(params)(QueueUrlParameter)
+        val attributes = AttributesModule.attributeNameAndValuesReader.read(params)
+        SetQueueAttributesActionRequest(attributes, queueUrl)
+      }
+    }
+}
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/QueueAttributesOps.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/QueueAttributesOps.scala
index aeb18e01..6586bf33 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/QueueAttributesOps.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/QueueAttributesOps.scala
@@ -26,58 +26,14 @@ trait QueueAttributesOps extends AttributesModule {
 
   val attributeValuesCalculator = new AttributeValuesCalculator
 
-  object QueueWriteableAttributeNames {
-    val AllWriteableAttributeNames: List[String] = VisibilityTimeoutParameter :: DelaySecondsAttribute ::
-      ReceiveMessageWaitTimeSecondsAttribute :: RedrivePolicyParameter :: Nil
-  }
-
-  object UnsupportedAttributeNames {
-    val PolicyAttribute = "Policy"
-    val MaximumMessageSizeAttribute = "MaximumMessageSize"
-    val MessageRetentionPeriodAttribute = "MessageRetentionPeriod"
-    val FifoQueueAttribute = "FifoQueue"
-
-    val AllUnsupportedAttributeNames: List[String] = PolicyAttribute :: MaximumMessageSizeAttribute ::
-      MessageRetentionPeriodAttribute :: FifoQueueAttribute :: Nil
-  }
-
-  object FifoAttributeNames {
-    val ContentBasedDeduplication = "ContentBasedDeduplication"
-    val FifoQueue = "FifoQueue"
-
-    val AllFifoAttributeNames: Seq[String] = Seq(
-      ContentBasedDeduplication,
-      FifoQueue
-    )
-  }
-
-  object QueueReadableAttributeNames {
-    val ApproximateNumberOfMessagesAttribute = "ApproximateNumberOfMessages"
-    val ApproximateNumberOfMessagesNotVisibleAttribute =
-      "ApproximateNumberOfMessagesNotVisible"
-    val ApproximateNumberOfMessagesDelayedAttribute =
-      "ApproximateNumberOfMessagesDelayed"
-    val CreatedTimestampAttribute = "CreatedTimestamp"
-    val LastModifiedTimestampAttribute = "LastModifiedTimestamp"
-
-    val AllAttributeNames: List[String] = QueueWriteableAttributeNames.AllWriteableAttributeNames ++
-      (ApproximateNumberOfMessagesAttribute ::
-        ApproximateNumberOfMessagesNotVisibleAttribute ::
-        ApproximateNumberOfMessagesDelayedAttribute ::
-        CreatedTimestampAttribute ::
-        LastModifiedTimestampAttribute ::
-        QueueArnAttribute :: Nil) ++ FifoAttributeNames.AllFifoAttributeNames
-  }
-
   def getAllQueueAttributes(queueActor: ActorRef, queueData: QueueData)(implicit
       timeout: Timeout,
       executionContext: ExecutionContext
   ): Future[List[(String, String)]] = {
-    val attributesParams = attributeNamesReader.prepareParametersForRead(QueueReadableAttributeNames.AllAttributeNames)
-    getQueueAttributes(attributesParams, queueActor, queueData)
+    getQueueAttributes(QueueReadableAttributeNames.AllAttributeNames, queueActor, queueData)
   }
 
-  def getQueueAttributes(p: AnyParams, queueActor: ActorRef, queueData: QueueData)(implicit
+  def getQueueAttributes(attributeNames: List[String], queueActor: ActorRef, queueData: QueueData)(implicit
       timeout: Timeout,
       executionContext: ExecutionContext
   ): Future[List[(String, String)]] = {
@@ -146,17 +102,13 @@ trait QueueAttributesOps extends AttributesModule {
       attributeValuesCalculator.calculate(attributeNames, rules: _*)
     }
 
-    val attributeNames = attributeNamesReader.read(p, AllAttributeNames)
     Future.sequence(calculateAttributeValues(attributeNames).map(p => p._2.map((p._1, _))))
-
   }
 
-  def setQueueAttributes(p: AnyParams, queueActor: ActorRef, queueManagerActor: ActorRef)(implicit
+  def setQueueAttributes(attributes: Map[String, String], queueActor: ActorRef, queueManagerActor: ActorRef)(implicit
       timeout: Timeout,
       executionContext: ExecutionContext
   ) = {
-    val attributes = attributeNameAndValuesReader.read(p)
-
     attributes.map({ case (attributeName, attributeValue) =>
       attributeName match {
         case VisibilityTimeoutParameter =>
@@ -167,6 +119,7 @@ trait QueueAttributesOps extends AttributesModule {
           queueActor ? UpdateQueueDelay(Duration.standardSeconds(attributeValue.toLong))
         case ReceiveMessageWaitTimeSecondsAttribute =>
           queueActor ? UpdateQueueReceiveMessageWait(Duration.standardSeconds(attributeValue.toLong))
+
         case RedrivePolicyParameter =>
           val redrivePolicy =
             try {
@@ -206,3 +159,46 @@ trait QueueAttributesOps extends AttributesModule {
   }
 
 }
+
+object QueueWriteableAttributeNames {
+  val AllWriteableAttributeNames: List[String] = VisibilityTimeoutParameter :: DelaySecondsAttribute ::
+    ReceiveMessageWaitTimeSecondsAttribute :: RedrivePolicyParameter :: Nil
+}
+
+object UnsupportedAttributeNames {
+  val PolicyAttribute = "Policy"
+  val MaximumMessageSizeAttribute = "MaximumMessageSize"
+  val MessageRetentionPeriodAttribute = "MessageRetentionPeriod"
+  val FifoQueueAttribute = "FifoQueue"
+
+  val AllUnsupportedAttributeNames: List[String] = PolicyAttribute :: MaximumMessageSizeAttribute ::
+    MessageRetentionPeriodAttribute :: FifoQueueAttribute :: Nil
+}
+
+object FifoAttributeNames {
+  val ContentBasedDeduplication = "ContentBasedDeduplication"
+  val FifoQueue = "FifoQueue"
+
+  val AllFifoAttributeNames: Seq[String] = Seq(
+    ContentBasedDeduplication,
+    FifoQueue
+  )
+}
+
+object QueueReadableAttributeNames {
+  val ApproximateNumberOfMessagesAttribute = "ApproximateNumberOfMessages"
+  val ApproximateNumberOfMessagesNotVisibleAttribute =
+    "ApproximateNumberOfMessagesNotVisible"
+  val ApproximateNumberOfMessagesDelayedAttribute =
+    "ApproximateNumberOfMessagesDelayed"
+  val CreatedTimestampAttribute = "CreatedTimestamp"
+  val LastModifiedTimestampAttribute = "LastModifiedTimestamp"
+
+  val AllAttributeNames: List[String] = QueueWriteableAttributeNames.AllWriteableAttributeNames ++
+    (ApproximateNumberOfMessagesAttribute ::
+      ApproximateNumberOfMessagesNotVisibleAttribute ::
+      ApproximateNumberOfMessagesDelayedAttribute ::
+      CreatedTimestampAttribute ::
+      LastModifiedTimestampAttribute ::
+      QueueArnAttribute :: Nil) ++ FifoAttributeNames.AllFifoAttributeNames
+}
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ReceiveMessageDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ReceiveMessageDirectives.scala
index 9d5d2c4f..7c5076fe 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ReceiveMessageDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ReceiveMessageDirectives.scala
@@ -4,15 +4,20 @@ import org.elasticmq._
 import org.elasticmq.actor.reply._
 import org.elasticmq.msg.ReceiveMessages
 import org.elasticmq.rest.sqs.Action.ReceiveMessage
+import org.elasticmq.rest.sqs.AttributesModule.{attributesToXmlConverter, messageAttributesToXmlConverter}
 import org.elasticmq.rest.sqs.Constants._
 import org.elasticmq.rest.sqs.MD5Util._
 import org.elasticmq.rest.sqs.directives.ElasticMQDirectives
+import org.elasticmq.rest.sqs.model.RequestPayload
 import org.joda.time.Duration
+import spray.json.DefaultJsonProtocol.{StringJsonFormat, jsonFormat7}
+import spray.json.RootJsonFormat
+import spray.json.DefaultJsonProtocol._
 
 import scala.xml.Elem
 
 trait ReceiveMessageDirectives {
-  this: ElasticMQDirectives with AttributesModule with SQSLimitsModule =>
+  this: ElasticMQDirectives with AttributesModule with SQSLimitsModule with ResponseMarshaller =>
   object MessageReadeableAttributeNames {
     val SentTimestampAttribute = "SentTimestamp"
     val ApproximateReceiveCountAttribute = "ApproximateReceiveCount"
@@ -33,16 +38,17 @@ trait ReceiveMessageDirectives {
       MessageGroupIdAttribute :: AWSTraceHeaderAttribute :: SequenceNumberAttribute :: Nil
   }
 
-  def receiveMessage(p: AnyParams) = {
+  def receiveMessage(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies) = {
     import MessageReadeableAttributeNames._
 
     p.action(ReceiveMessage) {
-      queueActorAndDataFromRequest(p) { (queueActor, queueData) =>
-        val visibilityTimeoutParameterOpt = p.get(VisibilityTimeoutParameter).map(_.toInt)
-        val maxNumberOfMessagesAttributeOpt = p.get(MaxNumberOfMessagesAttribute).map(_.toInt)
-        val waitTimeSecondsAttributeOpt = p.get(WaitTimeSecondsAttribute).map(_.toLong)
+      val requestParameters = p.as[ReceiveMessageActionRequest]
+      queueActorAndDataFromQueueUrl(requestParameters.QueueUrl) { (queueActor, queueData) =>
+        val visibilityTimeoutParameterOpt: Option[Int] = requestParameters.VisibilityTimeout
+        val maxNumberOfMessagesAttributeOpt: Option[Int] = requestParameters.MaxNumberOfMessages
+        val waitTimeSecondsAttributeOpt: Option[Long] = requestParameters.WaitTimeSeconds
 
-        val receiveRequestAttemptId = p.get(ReceiveRequestAttemptIdAttribute) match {
+        val receiveRequestAttemptId = requestParameters.ReceiveRequestAttemptId match {
           // ReceiveRequestAttemptIdAttribute is only supported for FIFO queues
           case Some(v) if !queueData.isFifo =>
             throw SQSException.invalidQueueTypeParameter(v, ReceiveRequestAttemptIdAttribute)
@@ -68,7 +74,7 @@ trait ReceiveMessageDirectives {
         val waitTimeSecondsFromParameters =
           waitTimeSecondsAttributeOpt.map(Duration.standardSeconds)
 
-        val messageAttributeNames = getMessageAttributeNames(p)
+        val messageAttributeNames = requestParameters.MessageAttributeNames.getOrElse(List.empty)
 
         Limits
           .verifyNumberOfMessagesFromParameters(maxNumberOfMessagesFromParameters, sqsLimits)
@@ -87,8 +93,7 @@ trait ReceiveMessageDirectives {
           receiveRequestAttemptId
         )
 
-        val attributeNames = attributeNamesReader.read(p, AllAttributeNames)
-
+        val attributeNames = requestParameters.AttributeNames.getOrElse(List.empty)
         def calculateAttributeValues(msg: MessageData): List[(String, String)] = {
           import AttributeValuesCalculator.Rule
 
@@ -125,52 +130,146 @@ trait ReceiveMessageDirectives {
           }
         }
 
-        def messagesToXml(messages: List[MessageData]): List[Elem] = {
-          messages.map { msg =>
-            val receipt = msg.deliveryReceipt
+        def mapMessages(messages: List[MessageData]): List[ReceivedMessage] =
+          messages.map { message =>
+            val receipt = message.deliveryReceipt
               .map(_.receipt)
               .getOrElse(throw new RuntimeException("No receipt for a received msg."))
-            val filteredMessageAttributes = getFilteredAttributeNames(messageAttributeNames, msg)
-            <Message>
-              <MessageId>{msg.id.id}</MessageId>
-              <ReceiptHandle>{receipt}</ReceiptHandle>
-              <MD5OfBody>{md5Digest(msg.content)}</MD5OfBody>
-              <Body>{XmlUtil.convertTexWithCRToNodeSeq(msg.content)}</Body>
-              {attributesToXmlConverter.convert(calculateAttributeValues(msg))}
-              {
-              if (filteredMessageAttributes.nonEmpty) <MD5OfMessageAttributes>{
-                md5AttributeDigest(filteredMessageAttributes)
-              }</MD5OfMessageAttributes>
-            }
-              {messageAttributesToXmlConverter.convert(filteredMessageAttributes.toList)}
-            </Message>
-          }
-        }
+            val filteredMessageAttributes = getFilteredAttributeNames(messageAttributeNames, message)
 
-        msgsFuture.map { messages =>
-          respondWith {
-            <ReceiveMessageResponse> {
-              if (messages.isEmpty)
-                <ReceiveMessageResult/>
-              else
-                <ReceiveMessageResult>{messagesToXml(messages)}</ReceiveMessageResult>
-            }
-              <ResponseMetadata>
-                <RequestId>{EmptyRequestId}</RequestId>
-              </ResponseMetadata>
-            </ReceiveMessageResponse>
+            ReceivedMessage(
+              Attributes = calculateAttributeValues(message).toMap,
+              Body = message.content,
+              MD5OfBody = md5Digest(message.content),
+              MD5OfMessageAttributes =
+                if (filteredMessageAttributes.nonEmpty) Some(md5AttributeDigest(filteredMessageAttributes)) else None,
+              MessageAttributes = filteredMessageAttributes,
+              MessageId = message.id.id,
+              ReceiptHandle = receipt
+            )
           }
+
+        msgsFuture.map(mapMessages).map(messages => complete(ReceiveMessageResponse(messages)))
+      }
+    }
+  }
+
+  case class ReceiveMessageActionRequest(
+      AttributeNames: Option[List[String]],
+      MaxNumberOfMessages: Option[Int],
+      MessageAttributeNames: Option[List[String]],
+      QueueUrl: String,
+      ReceiveRequestAttemptId: Option[String],
+      VisibilityTimeout: Option[Int],
+      WaitTimeSeconds: Option[Long]
+  )
+
+  object ReceiveMessageActionRequest {
+    def apply(
+        AttributeNames: Option[List[String]],
+        MaxNumberOfMessages: Option[Int],
+        MessageAttributeNames: Option[List[String]],
+        QueueUrl: String,
+        ReceiveRequestAttemptId: Option[String],
+        VisibilityTimeout: Option[Int],
+        WaitTimeSeconds: Option[Long]
+    ): ReceiveMessageActionRequest = {
+      new ReceiveMessageActionRequest(
+        AttributeNames =
+          AttributeNames.map(atr => if (atr.contains("All")) MessageReadeableAttributeNames.AllAttributeNames else atr),
+        MaxNumberOfMessages = MaxNumberOfMessages,
+        MessageAttributeNames = MessageAttributeNames,
+        QueueUrl = QueueUrl,
+        ReceiveRequestAttemptId = ReceiveRequestAttemptId,
+        VisibilityTimeout = VisibilityTimeout,
+        WaitTimeSeconds = WaitTimeSeconds
+      )
+    }
+
+    implicit val requestJsonFormat: RootJsonFormat[ReceiveMessageActionRequest] = jsonFormat7(
+      ReceiveMessageActionRequest.apply
+    )
+
+    implicit val requestParamReader: FlatParamsReader[ReceiveMessageActionRequest] =
+      new FlatParamsReader[ReceiveMessageActionRequest] {
+        override def read(params: Map[String, String]): ReceiveMessageActionRequest = {
+          val attributeNames = attributeNamesReader.read(params, MessageReadeableAttributeNames.AllAttributeNames)
+          val maxNumberOfMessages = params.get(MessageReadeableAttributeNames.MaxNumberOfMessagesAttribute).map(_.toInt)
+          val messageAttributeNames = getMessageAttributeNames(params).toList
+          val queueUrl = requiredParameter(params)(QueueUrlParameter)
+          val receiveRequestAttemptId = params.get(MessageReadeableAttributeNames.ReceiveRequestAttemptIdAttribute)
+          val visibilityTimeout = params.get(VisibilityTimeoutParameter).map(_.toInt)
+          val waitTimeSeconds = params.get(MessageReadeableAttributeNames.WaitTimeSecondsAttribute).map(_.toLong)
+          ReceiveMessageActionRequest(
+            Some(attributeNames),
+            maxNumberOfMessages,
+            Some(messageAttributeNames),
+            queueUrl,
+            receiveRequestAttemptId,
+            visibilityTimeout,
+            waitTimeSeconds
+          )
         }
       }
+
+    def getMessageAttributeNames(p: Map[String, String]): Iterable[String] = {
+      p.filterKeys(k =>
+        MessageReadeableAttributeNames.MessageAttributeNamePattern
+          .findFirstIn(k)
+          .isDefined
+      ).values
     }
   }
+}
+
+case class ReceiveMessageResponse(Messages: List[ReceivedMessage])
+
+object ReceiveMessageResponse {
+  implicit val responseJsonFormat: RootJsonFormat[ReceiveMessageResponse] = jsonFormat1(ReceiveMessageResponse.apply)
+
+  implicit def xmlSerializer(implicit
+      messageSerializer: XmlSerializer[ReceivedMessage]
+  ): XmlSerializer[ReceiveMessageResponse] = new XmlSerializer[ReceiveMessageResponse] {
+    override def toXml(t: ReceiveMessageResponse): Elem = {
+      val messages = t.Messages
 
-  def getMessageAttributeNames(p: AnyParams): Iterable[String] = {
-    p.filterKeys(k =>
-      MessageReadeableAttributeNames.MessageAttributeNamePattern
-        .findFirstIn(k)
-        .isDefined
-    ).values
+      <ReceiveMessageResponse>
+        {
+        if (messages.isEmpty) <ReceiveMessageResult/>
+        else
+          <ReceiveMessageResult>{messages.map(messageSerializer.toXml)}</ReceiveMessageResult>
+      }
+        <ResponseMetadata>
+        <RequestId>{EmptyRequestId}</RequestId>
+      </ResponseMetadata>
+      </ReceiveMessageResponse>
+    }
   }
+}
 
+case class ReceivedMessage(
+    Attributes: Map[String, String],
+    Body: String,
+    MD5OfBody: String,
+    MD5OfMessageAttributes: Option[String],
+    MessageAttributes: Map[String, MessageAttribute],
+    MessageId: String,
+    ReceiptHandle: String
+)
+
+object ReceivedMessage extends MessageAttributesSupport {
+  implicit val responseJsonFormat: RootJsonFormat[ReceivedMessage] = jsonFormat7(ReceivedMessage.apply)
+
+  implicit val xmlSerializer: XmlSerializer[ReceivedMessage] = new XmlSerializer[ReceivedMessage] {
+    override def toXml(msg: ReceivedMessage): Elem =
+      <Message>
+        <MessageId>{msg.MessageId}</MessageId>
+        <ReceiptHandle>{msg.ReceiptHandle}</ReceiptHandle>
+        <MD5OfBody>{msg.MD5OfBody}</MD5OfBody>
+        <Body>{XmlUtil.convertTexWithCRToNodeSeq(msg.Body)}</Body>
+        {attributesToXmlConverter.convert(msg.Attributes.toList)}
+        {msg.MD5OfMessageAttributes.map(md5 => <MD5OfMessageAttributes>{md5}</MD5OfMessageAttributes>).getOrElse("")}
+        {messageAttributesToXmlConverter.convert(msg.MessageAttributes.toList)}
+      </Message>
+  }
 }
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ResponseMarshaller.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ResponseMarshaller.scala
new file mode 100644
index 00000000..e2205220
--- /dev/null
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/ResponseMarshaller.scala
@@ -0,0 +1,47 @@
+package org.elasticmq.rest.sqs
+
+import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
+import spray.json.RootJsonFormat
+import akka.http.scaladsl.marshalling.Marshaller
+import akka.http.scaladsl.model.ContentTypes._
+import akka.http.scaladsl.model.{HttpEntity, RequestEntity}
+import akka.util.ByteString
+import org.elasticmq.rest.sqs.directives.RespondDirectives
+
+import scala.xml.{Elem, Null, UnprefixedAttribute}
+
+trait ResponseMarshaller {
+  _: RespondDirectives =>
+
+  private def namespace[T](
+      body: UnprefixedAttribute => Marshaller[T, RequestEntity]
+  )(implicit v: XmlNsVersion): Marshaller[T, RequestEntity] = {
+    body(new UnprefixedAttribute("xmlns", "http://queue.amazonaws.com/doc/%s/".format(v.version), Null))
+  }
+
+  implicit def elasticMQMarshaller[T](implicit
+      xmlSerializer: XmlSerializer[T],
+      json: RootJsonFormat[T],
+      marshallerDependencies: MarshallerDependencies
+  ): Marshaller[T, RequestEntity] =
+    marshallerDependencies.protocol match {
+      case AWSProtocol.`AWSJsonProtocol1.0` => sprayJsonMarshaller[T]
+      case _ =>
+        namespace { ns =>
+          Marshaller.withFixedContentType[T, RequestEntity](`text/xml(UTF-8)`) { t =>
+            val xml = xmlSerializer.toXml(t) % ns
+            HttpEntity(`text/xml(UTF-8)`, ByteString(xml.toString()))
+          }
+        }(marshallerDependencies.xmlNsVersion)
+    }
+}
+
+trait XmlSerializer[T] {
+
+  def toXml(t: T): Elem
+
+}
+
+object XmlSerializer {
+  def apply[T](implicit instance: XmlSerializer[T]) = instance
+}
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SQSException.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SQSException.scala
index 794e800a..cd4c49bc 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SQSException.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SQSException.scala
@@ -7,7 +7,7 @@ import Constants._
 class SQSException(
     val code: String,
     val httpStatusCode: Int = 400,
-    errorType: String = "Sender",
+    val errorType: String = "Sender",
     errorMessage: Option[String] = None
 ) extends Exception {
   val message: String = errorMessage.getOrElse(code + "; see the SQS docs.")
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SQSRestServerBuilder.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SQSRestServerBuilder.scala
index c91885a1..eaed1c5a 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SQSRestServerBuilder.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SQSRestServerBuilder.scala
@@ -10,7 +10,9 @@ import org.elasticmq._
 import org.elasticmq.actor.QueueManagerActor
 import org.elasticmq.metrics.QueuesMetrics
 import org.elasticmq.rest.sqs.Constants._
-import org.elasticmq.rest.sqs.directives.{ElasticMQDirectives, UnmatchedActionRoutes}
+import org.elasticmq.rest.sqs.XmlNsVersion.extractXmlNs
+import org.elasticmq.rest.sqs.directives.{AWSProtocolDirectives, AnyParamDirectives, ElasticMQDirectives, UnmatchedActionRoutes}
+import org.elasticmq.rest.sqs.model.RequestPayload
 import org.elasticmq.util.{Logging, NowProvider}
 
 import java.io.ByteArrayOutputStream
@@ -120,7 +122,7 @@ case class TheSQSRestServerBuilder(
     this.copy(queueEventListener = Some(_queueEventListener))
 
   def start(): SQSRestServer = {
-    val (theActorSystem, stopActorSystem) = getOrCreateActorSystem
+    val (theActorSystem, _) = getOrCreateActorSystem
     val theQueueManagerActor = getOrCreateQueueManagerActor(theActorSystem)
     val theServerAddress =
       if (generateServerAddress)
@@ -140,8 +142,10 @@ case class TheSQSRestServerBuilder(
       with SQSLimitsModule
       with BatchRequestsModule
       with ElasticMQDirectives
+      with AnyParamDirectives
       with CreateQueueDirectives
       with DeleteQueueDirectives
+      with AWSProtocolDirectives
       with QueueAttributesDirectives
       with ListQueuesDirectives
       with SendMessageDirectives
@@ -158,6 +162,7 @@ case class TheSQSRestServerBuilder(
       with TagQueueDirectives
       with TagsModule
       with UnmatchedActionRoutes
+      with ResponseMarshaller
       with QueueAttributesOps {
 
       def serverAddress = currentServerAddress.get()
@@ -175,9 +180,9 @@ case class TheSQSRestServerBuilder(
     }
 
     import env._
-    def rawRoutes(p: AnyParams) =
-      // 1. Sending, receiving, deleting messages
-      sendMessage(p) ~
+    def rawRoutes(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies) =
+        // 1. Sending, receiving, deleting messages
+        sendMessage(p) ~
         sendMessageBatch(p) ~
         receiveMessage(p) ~
         deleteMessage(p) ~
@@ -197,7 +202,7 @@ case class TheSQSRestServerBuilder(
         tagQueue(p) ~
         untagQueue(p) ~
         listQueueTags(p) ~
-        // 4. Unmatched action
+        //4. Unmatched action
         unmatchedAction(p)
 
     val config = new ElasticMQConfig
@@ -205,14 +210,19 @@ case class TheSQSRestServerBuilder(
     implicit val bindingTimeout = Timeout(10, TimeUnit.SECONDS)
 
     val routes =
-      handleServerExceptions {
-        handleRejectionsWithSQSError {
-          anyParamsMap { p =>
-            if (config.debug) {
-              logRequestResult("") {
-                rawRoutes(p)
+      extractXmlNs { implicit version =>
+        extractProtocol { implicit protocol =>
+          handleServerExceptions(protocol) {
+            handleRejectionsWithSQSError(protocol) {
+              anyParamsMap(protocol) { p =>
+                val marshallerDependencies = MarshallerDependencies(protocol, version)
+                if (config.debug) {
+                  logRequestResult("") {
+                    rawRoutes(p)(marshallerDependencies)
+                  }
+                } else rawRoutes(p)(marshallerDependencies)
               }
-            } else rawRoutes(p)
+            }
           }
         }
       }
@@ -304,6 +314,16 @@ object Constants {
   val InvalidParameterValueErrorName = "InvalidParameterValue"
   val MissingParameterName = "MissingParameter"
   val QueueUrlContext = "queue"
+  val QueueUrlParameter = "QueueUrl"
+  val QueueNameParameter = "QueueName"
+  val MessageBodyParameter = "MessageBody"
+  val DelaySecondsParameter = "DelaySeconds"
+  val MessageGroupIdParameter = "MessageGroupId"
+  val MessageDeduplicationIdParameter = "MessageDeduplicationId"
+  val AwsTraceHeaderSystemAttribute = "AWSTraceHeader"
+  val MaxResultsParameter = "MaxResults"
+  val NextTokenParameter = "NextToken"
+  val QueueNamePrefixParameter = "QueueNamePrefix"
 }
 
 object ParametersUtil {
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SendMessageBatchDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SendMessageBatchDirectives.scala
index 5703b50b..c3f350c0 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SendMessageBatchDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SendMessageBatchDirectives.scala
@@ -2,57 +2,135 @@ package org.elasticmq.rest.sqs
 
 import Constants._
 import akka.http.scaladsl.server.Route
+import org.elasticmq.MessageAttribute
 import org.elasticmq.rest.sqs.Action.SendMessageBatch
+import org.elasticmq.rest.sqs.ParametersUtil.ParametersParser
 import org.elasticmq.rest.sqs.directives.ElasticMQDirectives
+import org.elasticmq.rest.sqs.model.RequestPayload
+import spray.json.DefaultJsonProtocol._
+import spray.json.RootJsonFormat
+
+import scala.xml.Elem
 
 trait SendMessageBatchDirectives {
-  this: ElasticMQDirectives with SendMessageDirectives with BatchRequestsModule =>
-  val SendMessageBatchPrefix = "SendMessageBatchRequestEntry"
+  this: ElasticMQDirectives
+    with SendMessageDirectives
+    with BatchRequestsModule
+    with SQSLimitsModule
+    with ResponseMarshaller =>
 
-  def sendMessageBatch(p: AnyParams): Route = {
+  def sendMessageBatch(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies): Route = {
     p.action(SendMessageBatch) {
-      queueActorAndDataFromRequest(p) { (queueActor, queueData) =>
-        verifyMessagesNotTooLong(p)
+      val batch = p.as[BatchRequest[SendMessageBatchActionRequest]]
+      queueActorAndDataFromQueueUrl(batch.QueueUrl) { (queueActor, queueData) =>
+        verifyMessagesNotTooLong(batch.Entries)
+
+        val resultsFuture = batchRequest(batch.Entries) { (messageData, id, index) =>
+          validateMessageAttributes(messageData.MessageAttributes.getOrElse(Map.empty))
 
-        val resultsFuture = batchRequest(SendMessageBatchPrefix, p) { (messageData, id, index) =>
-          val maybeTraceId = p.find { case (key, _) =>
-            key.equalsIgnoreCase(AwsTraceIdHeaderName)
-          }.toMap
-          val message = createMessage(messageData ++ maybeTraceId, queueData, index)
+          val message =
+            createMessage(messageData.toSendMessageActionRequest(batch.QueueUrl), queueData, index, p.xRayTracingHeader)
 
           doSendMessage(queueActor, message).map { case (message, digest, messageAttributeDigest) =>
-            <SendMessageBatchResultEntry>
-                <Id>{id}</Id>
-                {messageAttributeDigest.map(d => <MD5OfMessageAttributes>{d}</MD5OfMessageAttributes>).getOrElse(())}
-                <MD5OfMessageBody>{digest}</MD5OfMessageBody>
-                <MessageId>{message.id.id}</MessageId>
-              </SendMessageBatchResultEntry>
+            BatchMessageSendResponseEntry(id, messageAttributeDigest, digest, None, message.id.id, None)
           }
         }
+        complete(resultsFuture)
+      }
+    }
+  }
 
-        resultsFuture.map { results =>
-          respondWith {
-            <SendMessageBatchResponse>
-              <SendMessageBatchResult>
-                {results}
-              </SendMessageBatchResult>
-              <ResponseMetadata>
-                <RequestId>{EmptyRequestId}</RequestId>
-              </ResponseMetadata>
-            </SendMessageBatchResponse>
-          }
+  private def verifyMessagesNotTooLong(requests: List[SendMessageBatchActionRequest]): Unit = {
+    val messageLengths = requests.map(_.MessageBody.length)
+    verifyMessageNotTooLong(messageLengths.sum)
+  }
+
+  case class SendMessageBatchActionRequest(
+      Id: String,
+      DelaySeconds: Option[Long],
+      MessageBody: String,
+      MessageDeduplicationId: Option[String],
+      MessageGroupId: Option[String],
+      MessageSystemAttributes: Option[Map[String, MessageAttribute]],
+      MessageAttributes: Option[Map[String, MessageAttribute]]
+  ) extends BatchEntry {
+    def toSendMessageActionRequest(queueUrl: String): SendMessageActionRequest = SendMessageActionRequest(
+      DelaySeconds,
+      MessageBody,
+      MessageDeduplicationId,
+      MessageGroupId,
+      MessageSystemAttributes,
+      MessageAttributes,
+      queueUrl
+    )
+  }
+
+  object SendMessageBatchActionRequest extends MessageAttributesSupport {
+
+    implicit val jsonFormat: RootJsonFormat[SendMessageBatchActionRequest] = jsonFormat7(
+      SendMessageBatchActionRequest.apply
+    )
+
+    implicit val queryFormat: BatchFlatParamsReader[SendMessageBatchActionRequest] = {
+      new BatchFlatParamsReader[SendMessageBatchActionRequest] {
+        override def read(params: Map[String, String]): SendMessageBatchActionRequest = {
+          SendMessageBatchActionRequest(
+            requiredParameter(params)(IdSubParameter),
+            params.parseOptionalLong(DelaySecondsParameter),
+            requiredParameter(params)(MessageBodyParameter),
+            params.get(MessageDeduplicationIdParameter),
+            params.get(MessageGroupIdParameter),
+            Some(getMessageSystemAttributes(params)),
+            Some(getMessageAttributes(params))
+          )
         }
+
+        override def batchPrefix: String = "SendMessageBatchRequestEntry"
       }
     }
   }
+}
 
-  def verifyMessagesNotTooLong(parameters: Map[String, String]): Unit = {
-    val messageLengths = for {
-      parameterMap <- batchParametersMap(SendMessageBatchPrefix, parameters)
-    } yield {
-      parameterMap(MessageBodyParameter).length
-    }
+case class BatchMessageSendResponseEntry(
+    Id: String,
+    MD5OfMessageAttributes: Option[String],
+    MD5OfMessageBody: String,
+    MD5OfMessageSystemAttributes: Option[String],
+    MessageId: String,
+    SequenceNumber: Option[Long]
+)
 
-    verifyMessageNotTooLong(messageLengths.sum)
+object BatchMessageSendResponseEntry {
+  implicit val format: RootJsonFormat[BatchMessageSendResponseEntry] = jsonFormat6(
+    BatchMessageSendResponseEntry.apply
+  )
+
+  implicit val entryXmlSerializer: XmlSerializer[BatchMessageSendResponseEntry] = {
+    new XmlSerializer[BatchMessageSendResponseEntry] {
+      override def toXml(t: BatchMessageSendResponseEntry): Elem =
+        <SendMessageBatchResultEntry>
+            <Id>{t.Id}</Id>
+            {t.MD5OfMessageAttributes.map(d => <MD5OfMessageAttributes>{d}</MD5OfMessageAttributes>).getOrElse(())}
+            <MD5OfMessageBody>{t.MD5OfMessageBody}</MD5OfMessageBody>
+            <MessageId>{t.MessageId}</MessageId>
+          </SendMessageBatchResultEntry>
+    }
   }
+
+  implicit def batchXmlSerializer(implicit
+      successSerializer: XmlSerializer[BatchMessageSendResponseEntry]
+  ): XmlSerializer[BatchResponse[BatchMessageSendResponseEntry]] =
+    new XmlSerializer[BatchResponse[BatchMessageSendResponseEntry]] {
+      override def toXml(t: BatchResponse[BatchMessageSendResponseEntry]): Elem =
+        <SendMessageBatchResponse>
+          <SendMessageBatchResult>
+            {t.Successful.map(successSerializer.toXml) ++ t.Failed.map(XmlSerializer[Failed].toXml)}
+          </SendMessageBatchResult>
+          <ResponseMetadata>
+            <RequestId>
+              {EmptyRequestId}
+            </RequestId>
+          </ResponseMetadata>
+        </SendMessageBatchResponse>
+    }
 }
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SendMessageDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SendMessageDirectives.scala
index fd64ab76..56f4e943 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SendMessageDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/SendMessageDirectives.scala
@@ -10,43 +10,28 @@ import org.elasticmq.rest.sqs.Constants._
 import org.elasticmq.rest.sqs.MD5Util._
 import org.elasticmq.rest.sqs.ParametersUtil._
 import org.elasticmq.rest.sqs.directives.ElasticMQDirectives
-import org.elasticmq.rest.sqs.model.{
-  BinaryMessageSystemAttribute,
-  MessageSystemAttribute,
-  NumberMessageSystemAttribute,
-  StringMessageSystemAttribute
-}
+import org.elasticmq.rest.sqs.model.RequestPayload
+import spray.json.DefaultJsonProtocol._
+import spray.json.RootJsonFormat
 
 import scala.concurrent.Future
+import scala.xml.Elem
 
-trait SendMessageDirectives { this: ElasticMQDirectives with SQSLimitsModule =>
+trait SendMessageDirectives {
+  this: ElasticMQDirectives with SQSLimitsModule with ResponseMarshaller =>
   private val messageSystemAttributeNamePattern = """MessageSystemAttribute\.(\d+)\.Name""".r
-  val MessageBodyParameter = "MessageBody"
-  val DelaySecondsParameter = "DelaySeconds"
-  val MessageGroupIdParameter = "MessageGroupId"
-  val MessageDeduplicationIdParameter = "MessageDeduplicationId"
-  val AwsTraceHeaderSystemAttribute = "AWSTraceHeader"
-  val AwsTraceIdHeaderName = "X-Amzn-Trace-Id"
-
-  def sendMessage(p: AnyParams): Route = {
+
+  def sendMessage(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies): Route = {
     p.action(SendMessageAction) {
-      queueActorAndDataFromRequest(p) { (queueActor, queueData) =>
-        val message = createMessage(p, queueData, orderIndex = 0)
+      val params = p.as[SendMessageActionRequest]
+
+      queueActorAndDataFromQueueUrl(params.QueueUrl) { (queueActor, queueData) =>
+        val message = createMessage(params, queueData, orderIndex = 0, p.xRayTracingHeader)
+
+        validateMessageAttributes(params.MessageAttributes.getOrElse(Map.empty))
 
         doSendMessage(queueActor, message).map { case (message, digest, messageAttributeDigest) =>
-          respondWith {
-            <SendMessageResponse>
-                <SendMessageResult>
-                  {messageAttributeDigest.map(d => <MD5OfMessageAttributes>{d}</MD5OfMessageAttributes>).getOrElse(())}
-                  <MD5OfMessageBody>{digest}</MD5OfMessageBody>
-                  <MessageId>{message.id.id}</MessageId>
-                  {message.sequenceNumber.map(x => <SequenceNumber>{x}</SequenceNumber>).getOrElse(())}
-                </SendMessageResult>
-                <ResponseMetadata>
-                  <RequestId>{EmptyRequestId}</RequestId>
-                </ResponseMetadata>
-              </SendMessageResponse>
-          }
+          complete(SendMessageResponse(messageAttributeDigest, digest, None, message.id.id, message.sequenceNumber))
         }
       }
     }
@@ -66,10 +51,6 @@ trait SendMessageDirectives { this: ElasticMQDirectives with SQSLimitsModule =>
       .union(List(0))
       .max // even if nothing, return 0
 
-    Limits
-      .verifyMessageAttributesNumber(numAttributes, sqsLimits)
-      .fold(error => throw new SQSException(error), identity)
-
     (1 to numAttributes).map { i =>
       val name = parameters("MessageAttribute." + i + ".Name")
       val dataType = parameters("MessageAttribute." + i + ".Value.DataType")
@@ -81,20 +62,12 @@ trait SendMessageDirectives { this: ElasticMQDirectives with SQSLimitsModule =>
         None
       }
 
-      val value = primaryDataType match {
+      val value: MessageAttribute = primaryDataType match {
         case "String" =>
-          val strValue =
-            parameters("MessageAttribute." + i + ".Value.StringValue")
-          Limits
-            .verifyMessageStringAttribute(name, strValue, sqsLimits)
-            .fold(error => throw new SQSException(error), identity)
+          val strValue = parameters("MessageAttribute." + i + ".Value.StringValue")
           StringMessageAttribute(strValue, customDataType)
         case "Number" =>
-          val strValue =
-            parameters("MessageAttribute." + i + ".Value.StringValue")
-          Limits
-            .verifyMessageNumberAttribute(strValue, name, sqsLimits)
-            .fold(error => throw new SQSException(error), identity)
+          val strValue = parameters("MessageAttribute." + i + ".Value.StringValue")
           NumberMessageAttribute(strValue, customDataType)
         case "Binary" =>
           BinaryMessageAttribute.fromBase64(parameters("MessageAttribute." + i + ".Value.BinaryValue"), customDataType)
@@ -108,29 +81,34 @@ trait SendMessageDirectives { this: ElasticMQDirectives with SQSLimitsModule =>
     }.toMap
   }
 
-  private def getMessageSystemAttributes(parameters: Map[String, String]): Map[String, MessageSystemAttribute] = {
+  def getMessageSystemAttributes(parameters: Map[String, String]): Map[String, MessageAttribute] = {
     parameters.flatMap {
       case (messageSystemAttributeNamePattern(index), parameterName) =>
         val parameterDataType = parameters(s"MessageSystemAttribute.$index.Value.DataType")
         val parameterValue = parameterDataType match {
-          case "String" => StringMessageSystemAttribute(parameters(s"MessageSystemAttribute.$index.Value.StringValue"))
-          case "Number" => NumberMessageSystemAttribute(parameters(s"MessageSystemAttribute.$index.Value.StringValue"))
+          case "String" => StringMessageAttribute(parameters(s"MessageSystemAttribute.$index.Value.StringValue"))
+          case "Number" => NumberMessageAttribute(parameters(s"MessageSystemAttribute.$index.Value.StringValue"))
           case "Binary" =>
-            BinaryMessageSystemAttribute.fromString(parameters(s"MessageAttribute.$index.Value.BinaryValue"))
+            BinaryMessageAttribute.fromBase64(parameters(s"MessageAttribute.$index.Value.BinaryValue"))
         }
         Some((parameterName, parameterValue))
       case _ => None
     }
   }
 
-  def createMessage(parameters: Map[String, String], queueData: QueueData, orderIndex: Int): NewMessageData = {
-    val body = parameters(MessageBodyParameter)
-    val messageAttributes = getMessageAttributes(parameters)
-    val messageSystemAttributes = getMessageSystemAttributes(parameters)
+  def createMessage(
+      parameters: SendMessageActionRequest,
+      queueData: QueueData,
+      orderIndex: Int,
+      xRayTracingHeder: Option[String]
+  ): NewMessageData = {
+    val body = parameters.MessageBody
+    val messageAttributes = parameters.MessageAttributes.getOrElse(Map.empty)
+    val messageSystemAttributes = parameters.MessageSystemAttributes.getOrElse(Map.empty)
 
     Limits.verifyMessageBody(body, sqsLimits).fold(error => throw new SQSException(error), identity)
 
-    val messageGroupId = parameters.get(MessageGroupIdParameter) match {
+    val messageGroupId = parameters.MessageGroupId match {
       // MessageGroupId is only supported for FIFO queues
       case Some(v) if !queueData.isFifo => throw SQSException.invalidQueueTypeParameter(v, MessageGroupIdParameter)
 
@@ -145,7 +123,7 @@ trait SendMessageDirectives { this: ElasticMQDirectives with SQSLimitsModule =>
       case m => m
     }
 
-    val messageDeduplicationId = parameters.get(MessageDeduplicationIdParameter) match {
+    val messageDeduplicationId = parameters.MessageDeduplicationId match {
       // MessageDeduplicationId is only supported for FIFO queues
       case Some(v) if !queueData.isFifo =>
         throw SQSException.invalidQueueTypeParameter(v, MessageDeduplicationIdParameter)
@@ -174,7 +152,7 @@ trait SendMessageDirectives { this: ElasticMQDirectives with SQSLimitsModule =>
       case None => None
     }
 
-    val delaySecondsOption = parameters.parseOptionalLong(DelaySecondsParameter) match {
+    val delaySecondsOption = parameters.DelaySeconds match {
       case Some(v) if v < 0 || v > 900 =>
         // Messages can at most be delayed for 15 minutes
         throw SQSException.invalidParameter(
@@ -196,15 +174,15 @@ trait SendMessageDirectives { this: ElasticMQDirectives with SQSLimitsModule =>
     val maybeTracingId = messageSystemAttributes
       .get(AwsTraceHeaderSystemAttribute)
       .map {
-        case StringMessageSystemAttribute(value) => TracingId(value)
-        case NumberMessageSystemAttribute(_) =>
+        case StringMessageAttribute(value, _) => TracingId(value)
+        case NumberMessageAttribute(_, _) =>
           throw new SQSException(
             InvalidParameterValueErrorName,
             errorMessage = Some(
               s"$AwsTraceHeaderSystemAttribute should be declared as a String, instead it was recognized as a Number"
             )
           )
-        case BinaryMessageSystemAttribute(_) =>
+        case BinaryMessageAttribute(_, _) =>
           throw new SQSException(
             InvalidParameterValueErrorName,
             errorMessage = Some(
@@ -212,7 +190,7 @@ trait SendMessageDirectives { this: ElasticMQDirectives with SQSLimitsModule =>
             )
           )
       }
-      .orElse(parameters.get(AwsTraceIdHeaderName).map(TracingId.apply))
+      .orElse(xRayTracingHeder.map(TracingId.apply))
 
     NewMessageData(
       None,
@@ -232,6 +210,7 @@ trait SendMessageDirectives { this: ElasticMQDirectives with SQSLimitsModule =>
       message: NewMessageData
   ): Future[(MessageData, String, Option[String])] = {
     val digest = md5Digest(message.content)
+
     val messageAttributeDigest = if (message.messageAttributes.isEmpty) {
       None
     } else {
@@ -245,4 +224,88 @@ trait SendMessageDirectives { this: ElasticMQDirectives with SQSLimitsModule =>
 
   def verifyMessageNotTooLong(messageLength: Int): Unit =
     Limits.verifyMessageLength(messageLength, sqsLimits).fold(error => throw new SQSException(error), identity)
+
+  def validateMessageAttributes(messageAttributes: Map[String, MessageAttribute]): Unit = {
+
+    messageAttributes.foreach { case (name, value) =>
+      Limits
+        .verifyMessageAttributesNumber(messageAttributes.size, sqsLimits)
+        .fold(error => throw new SQSException(error), identity)
+
+      val availableDataTypes = Set("String", "Number", "Binary")
+      if (value.getDataType().isEmpty)
+        throw throw new SQSException(s"Attribute '$name' must contain a non-empty attribute type")
+      if (!availableDataTypes.contains(value.getDataType()))
+        throw new Exception("Currently only handles String, Number and Binary typed attributes")
+
+      value match {
+        case StringMessageAttribute(stringValue, _) =>
+          Limits
+            .verifyMessageStringAttribute(name, stringValue, sqsLimits)
+            .fold(error => throw new SQSException(error), identity)
+        case NumberMessageAttribute(stringValue, _) =>
+          Limits
+            .verifyMessageNumberAttribute(stringValue, name, sqsLimits)
+            .fold(error => throw new SQSException(error), identity)
+        case BinaryMessageAttribute(_, _) => ()
+      }
+    }
+  }
+
+  case class SendMessageActionRequest(
+      DelaySeconds: Option[Long],
+      MessageBody: String,
+      MessageDeduplicationId: Option[String],
+      MessageGroupId: Option[String],
+      MessageSystemAttributes: Option[Map[String, MessageAttribute]],
+      MessageAttributes: Option[Map[String, MessageAttribute]],
+      QueueUrl: String
+  )
+
+  object SendMessageActionRequest extends MessageAttributesSupport {
+
+    implicit val jsonFormat: RootJsonFormat[SendMessageActionRequest] = jsonFormat7(SendMessageActionRequest.apply)
+
+    implicit val queryFormat: FlatParamsReader[SendMessageActionRequest] =
+      new FlatParamsReader[SendMessageActionRequest] {
+        override def read(params: Map[String, String]): SendMessageActionRequest = {
+          SendMessageActionRequest(
+            DelaySeconds = params.parseOptionalLong(DelaySecondsParameter),
+            MessageBody = requiredParameter(params)(MessageBodyParameter),
+            MessageDeduplicationId = params.get(MessageDeduplicationIdParameter),
+            MessageGroupId = params.get(MessageGroupIdParameter),
+            MessageSystemAttributes = Some(getMessageSystemAttributes(params)),
+            MessageAttributes = Some(getMessageAttributes(params)),
+            QueueUrl = requiredParameter(params)(QueueUrlParameter)
+          )
+        }
+      }
+  }
+}
+
+case class SendMessageResponse(
+    MD5OfMessageAttributes: Option[String],
+    MD5OfMessageBody: String,
+    MD5OfMessageSystemAttributes: Option[String],
+    MessageId: String,
+    SequenceNumber: Option[String]
+)
+
+object SendMessageResponse {
+  implicit val jsonFormat: RootJsonFormat[SendMessageResponse] = jsonFormat5(SendMessageResponse.apply)
+
+  implicit val xmlSerializer: XmlSerializer[SendMessageResponse] = new XmlSerializer[SendMessageResponse] {
+    override def toXml(t: SendMessageResponse): Elem =
+      <SendMessageResponse>
+          <SendMessageResult>
+            {t.MD5OfMessageAttributes.map(d => <MD5OfMessageAttributes>{d}</MD5OfMessageAttributes>).getOrElse(())}
+            <MD5OfMessageBody>{t.MD5OfMessageBody}</MD5OfMessageBody>
+            <MessageId>{t.MessageId}</MessageId>
+            {t.SequenceNumber.map(x => <SequenceNumber>{x}</SequenceNumber>).getOrElse(())}
+          </SendMessageResult>
+          <ResponseMetadata>
+            <RequestId>{EmptyRequestId}</RequestId>
+          </ResponseMetadata>
+        </SendMessageResponse>
+  }
 }
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/TagQueueDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/TagQueueDirectives.scala
index 5bc38247..b55a0ef8 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/TagQueueDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/TagQueueDirectives.scala
@@ -4,57 +4,114 @@ import org.elasticmq.actor.reply._
 import org.elasticmq.msg._
 import org.elasticmq.rest.sqs.Action.{ListQueueTags, TagQueue, UntagQueue}
 import org.elasticmq.rest.sqs.Constants._
+import org.elasticmq.rest.sqs.TagsModule.tagsToXmlConverter
 import org.elasticmq.rest.sqs.directives.ElasticMQDirectives
+import spray.json.DefaultJsonProtocol.jsonFormat1
+import spray.json.RootJsonFormat
+import spray.json.DefaultJsonProtocol._
+import org.elasticmq.rest.sqs.model.RequestPayload
+
+import scala.xml.Elem
 
 trait TagQueueDirectives {
-  this: ElasticMQDirectives with TagsModule =>
+  this: ElasticMQDirectives with TagsModule with ResponseMarshaller =>
 
-  def listQueueTags(p: AnyParams) = {
+  def listQueueTags(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies) = {
     p.action(ListQueueTags) {
-      queueActorAndDataFromRequest(p) { (_, queueData) =>
-        respondWith {
-          <ListQueueTagsResponse>
-            <ListQueueTagsResult>
-              {tagsToXmlConverter.convert(queueData.tags)}
-            </ListQueueTagsResult>
-            <ResponseMetadata>
-              <RequestId>{EmptyRequestId}</RequestId>
-            </ResponseMetadata>
-          </ListQueueTagsResponse>
-        }
+      val queueUrl = p.as[ListQueueTagsActionRequest].QueueUrl
+      queueActorAndDataFromQueueUrl(queueUrl) { (_, queueData) =>
+        complete(ListQueueTagsResponse(queueData.tags))
       }
     }
   }
 
-  def untagQueue(p: AnyParams) = {
+  def untagQueue(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies) = {
     p.action(UntagQueue) {
-      queueActorFromRequest(p) { queueActor =>
-        val tags = tagNamesReader.read(p)
+      val params = p.as[UntagQueueActionRequest]
+      queueActorFromUrl(params.QueueUrl) { queueActor =>
+        val tags = params.TagKeys
         queueActor ? RemoveQueueTags(tags)
-        respondWith {
-          <UntagQueueResponse>
-            <ResponseMetadata>
-              <RequestId>{EmptyRequestId}</RequestId>
-            </ResponseMetadata>
-          </UntagQueueResponse>
-        }
+        emptyResponse("UntagQueueResponse")
       }
     }
   }
 
-  def tagQueue(p: AnyParams) = {
+  def tagQueue(p: RequestPayload)(implicit marshallerDependencies: MarshallerDependencies) = {
     p.action(TagQueue) {
-      queueActorFromRequest(p) { queueActor =>
-        val tags = tagNameAndValuesReader.read(p)
+      val params = p.as[TagQueueActionRequest]
+      queueActorFromUrl(params.QueueUrl) { queueActor =>
+        val tags = params.Tags
         queueActor ? UpdateQueueTags(tags)
-        respondWith(
-          <TagQueueResponse>
-            <ResponseMetadata>
-              <RequestId>{EmptyRequestId}</RequestId>
-            </ResponseMetadata>
-          </TagQueueResponse>
-        )
+        emptyResponse("TagQueueResponse")
       }
     }
   }
 }
+
+case class TagQueueActionRequest(
+    QueueUrl: String,
+    Tags: Map[String, String]
+)
+
+object TagQueueActionRequest {
+  implicit val requestJsonFormat: RootJsonFormat[TagQueueActionRequest] = jsonFormat2(TagQueueActionRequest.apply)
+
+  implicit val requestParamReader: FlatParamsReader[TagQueueActionRequest] = new FlatParamsReader[TagQueueActionRequest] {
+    override def read(params: Map[String, String]): TagQueueActionRequest = {
+      val tags = TagsModule.tagNameAndValuesReader.read(params)
+      val queueUrl = requiredParameter(params)(QueueUrlParameter)
+      TagQueueActionRequest(queueUrl, tags)
+    }
+  }
+}
+
+case class UntagQueueActionRequest(
+    QueueUrl: String,
+    TagKeys: List[String]
+)
+
+object UntagQueueActionRequest {
+  implicit val requestJsonFormat: RootJsonFormat[UntagQueueActionRequest] = jsonFormat2(UntagQueueActionRequest.apply)
+
+  implicit val requestParamReader: FlatParamsReader[UntagQueueActionRequest] = new FlatParamsReader[UntagQueueActionRequest] {
+    override def read(params: Map[String, String]): UntagQueueActionRequest = {
+      val tags = TagsModule.tagNamesReader.read(params)
+      val queueUrl = requiredParameter(params)(QueueUrlParameter)
+      UntagQueueActionRequest(queueUrl, tags)
+    }
+  }
+}
+
+case class ListQueueTagsActionRequest(
+    QueueUrl: String
+)
+
+object ListQueueTagsActionRequest {
+  implicit val requestJsonFormat: RootJsonFormat[ListQueueTagsActionRequest] = jsonFormat1(ListQueueTagsActionRequest.apply)
+
+  implicit val requestParamReader: FlatParamsReader[ListQueueTagsActionRequest] = new FlatParamsReader[ListQueueTagsActionRequest] {
+    override def read(params: Map[String, String]): ListQueueTagsActionRequest = ListQueueTagsActionRequest(requiredParameter(params)(QueueUrlParameter))
+  }
+}
+
+case class ListQueueTagsResponse(Tags: Map[String, String])
+
+
+
+object ListQueueTagsResponse {
+  implicit val format: RootJsonFormat[ListQueueTagsResponse] = jsonFormat1(ListQueueTagsResponse.apply)
+
+  implicit val xmlSerializer: XmlSerializer[ListQueueTagsResponse] = new XmlSerializer[ListQueueTagsResponse] {
+    override def toXml(t: ListQueueTagsResponse): Elem =
+      <ListQueueTagsResponse>
+        <ListQueueTagsResult>
+          {tagsToXmlConverter.convert(t.Tags)}
+        </ListQueueTagsResult>
+        <ResponseMetadata>
+          <RequestId>
+            {EmptyRequestId}
+          </RequestId>
+        </ResponseMetadata>
+      </ListQueueTagsResponse>
+  }
+}
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/TagsModule.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/TagsModule.scala
index b02cbb37..d4f20a24 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/TagsModule.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/TagsModule.scala
@@ -49,3 +49,5 @@ trait TagsModule {
     }
   }
 }
+
+object TagsModule extends TagsModule
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/AWSProtocolDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/AWSProtocolDirectives.scala
new file mode 100644
index 00000000..24311d04
--- /dev/null
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/AWSProtocolDirectives.scala
@@ -0,0 +1,28 @@
+package org.elasticmq.rest.sqs.directives
+
+import akka.http.scaladsl.model.{ContentType, MediaType}
+import akka.http.scaladsl.server.Directives.extractRequest
+import org.elasticmq.rest.sqs.AWSProtocol
+
+trait AWSProtocolDirectives {
+  def extractProtocol = extractRequest.map { request =>
+    request.entity.contentType match {
+      case AmzJsonProtocol(_) => AWSProtocol.`AWSJsonProtocol1.0`
+      case _ => AWSProtocol.AWSQueryProtocol
+    }
+  }
+
+}
+
+object AmzJsonProtocol {
+  def unapply(contentType: ContentType): Option[MediaType] = {
+    if (contentType.mediaType.subType.startsWith("x-amz-json")) {
+      Some(contentType.mediaType)
+    } else {
+      None
+    }
+  }
+
+  def contentType(version: String): ContentType.WithMissingCharset =
+    ContentType.WithMissingCharset(MediaType.customWithOpenCharset("application", s"x-amz-json-$version"))
+}
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/AnyParamDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/AnyParamDirectives.scala
index cb8ab9c7..a3722b32 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/AnyParamDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/AnyParamDirectives.scala
@@ -3,31 +3,98 @@ package org.elasticmq.rest.sqs.directives
 import akka.http.scaladsl.model.{FormData, HttpRequest}
 import akka.http.scaladsl.server.{Directives, Route, UnsupportedRequestContentTypeRejection}
 import akka.stream.Materializer
+import org.elasticmq.rest.sqs.{AWSProtocol, ContextPathModule, QueueURLModule}
+import org.elasticmq.rest.sqs.Constants.{QueueNameParameter, QueueUrlParameter}
+import org.elasticmq.rest.sqs.directives.QueueDirectives.AccountIdRegex
+import org.elasticmq.rest.sqs.model.{JsonData, RequestPayload}
 
 trait AnyParamDirectives {
-  this: Directives =>
+  this: Directives with ContextPathModule with QueueURLModule =>
 
-  private def entityOrEmpty =
+  private def formDataOrEmpty =
     entity(as[FormData]).recoverPF {
       // #68: some clients don't set the body as application/x-www-form-urlencoded, e.g. perl
       case Seq(UnsupportedRequestContentTypeRejection(_)) =>
         provide(FormData.Empty)
     }
 
-  private def extractAwsXRayTracingHeader(request: HttpRequest): Map[String, String] = {
-    request.headers
-      .find(_.name().equalsIgnoreCase("X-Amzn-Trace-Id"))
-      .map(header => header.name() -> header.value())
-      .toMap
+  private val actionHeaderPattern = """^AmazonSQS\.(\w+)$""".r
+  private def extractActionFromHeader =
+    extractRequest.map(request =>
+      request.headers
+        .find(_.name() == "X-Amz-Target")
+        .flatMap(
+          _.value() match {
+            case actionHeaderPattern(action) => Some(action)
+            case _                           => None
+          }
+        )
+        .getOrElse(throw new IllegalArgumentException("Couldn't find header X-Amz-Target"))
+    )
+
+  private def extractAwsXRayTracingHeader =
+    extractRequest.map(
+      _.headers
+        .find(_.name().equalsIgnoreCase("X-Amzn-Trace-Id"))
+        .map(_.value())
+    )
+
+  private def extractQueueNameAndUrlFromRequest(body: Map[String, String] => Route): Route = {
+
+    val pathDirective = {
+      if (contextPath.nonEmpty) {
+        pathPrefix(contextPath / AccountIdRegex / Segment).tmap(t => Option(t._2)) |
+          pathPrefix(contextPath / Segment).map(Option(_)) |
+          provide(Option.empty[String])
+      } else {
+        pathPrefix(AccountIdRegex / Segment).tmap(t => Option(t._2)) |
+          pathPrefix(Segment).map(Option(_)) |
+          provide(Option.empty[String])
+      }
+    }.flatMap {
+      case Some(name) =>
+        queueURL(name).map { url =>
+          Map(QueueNameParameter -> name, QueueUrlParameter -> url)
+        }
+      case None => provide(Map.empty[String, String])
+    }
+
+    pathDirective(body)
   }
 
-  def anyParamsMap(body: Map[String, String] => Route) = {
-    parameterMap { queryParameters =>
-      extractRequest { request =>
-        entityOrEmpty { fd =>
-          body((fd.fields.toMap ++ queryParameters ++ extractAwsXRayTracingHeader(request)).filter(_._1 != ""))
+  def anyParamsMap(protocol: AWSProtocol)(body: RequestPayload => Route) = {
+
+    protocol match {
+      case AWSProtocol.`AWSJsonProtocol1.0` =>
+        extractActionFromHeader { action =>
+          extractAwsXRayTracingHeader { tracingHeader =>
+            entity(as[JsonData]) { json =>
+              body(
+                RequestPayload.JsonParams(
+                  json.payload,
+                  action,
+                  tracingHeader
+                )
+              )
+            }
+          }
+        }
+      case _ =>
+        parameterMap { queryParameters =>
+          extractAwsXRayTracingHeader { tracingHeader =>
+            formDataOrEmpty { fd =>
+              extractQueueNameAndUrlFromRequest { queueNameAndUrl =>
+                val params = queryParameters ++ fd.fields.toMap ++ queueNameAndUrl
+                body(
+                  RequestPayload.QueryParams(
+                    params.filter(_._1 != ""),
+                    tracingHeader
+                  )
+                )
+              }
+            }
+          }
         }
-      }
     }
   }
 
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/ElasticMQDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/ElasticMQDirectives.scala
index fd71ac83..69c1ad57 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/ElasticMQDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/ElasticMQDirectives.scala
@@ -1,19 +1,19 @@
 package org.elasticmq.rest.sqs.directives
 
 import akka.http.scaladsl.server.{Directives, Route}
-import org.elasticmq.rest.sqs.{ActorSystemModule, ContextPathModule, QueueManagerActorModule}
+import org.elasticmq.rest.sqs.{ActorSystemModule, ContextPathModule, QueueManagerActorModule, QueueURLModule}
 import org.elasticmq.util.Logging
 
 trait ElasticMQDirectives
     extends Directives
     with RespondDirectives
+    with AWSProtocolDirectives
     with FutureDirectives
     with ExceptionDirectives
     with QueueDirectives
     with QueueManagerActorModule
     with ContextPathModule
     with ActorSystemModule
-    with AnyParamDirectives
     with RejectionDirectives
     with Logging {
 
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/ExceptionDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/ExceptionDirectives.scala
index 5040a567..506e3ac4 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/ExceptionDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/ExceptionDirectives.scala
@@ -1,28 +1,42 @@
 package org.elasticmq.rest.sqs.directives
 
-import akka.http.scaladsl.model.StatusCodes
+import akka.http.scaladsl.model.{HttpResponse, StatusCodes}
 import akka.http.scaladsl.server.{Directive0, Directives, ExceptionHandler, Route}
 import org.elasticmq.rest.sqs.Constants._
-import org.elasticmq.rest.sqs.SQSException
+import org.elasticmq.rest.sqs.{AWSProtocol, SQSException}
 import org.elasticmq.util.Logging
+import spray.json.DefaultJsonProtocol._
+import spray.json.RootJsonFormat
+import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
 
 trait ExceptionDirectives extends Logging {
   this: Directives with RespondDirectives =>
 
-  def handleSQSException(e: SQSException): Route = {
-    respondWith(e.httpStatusCode) {
-      e.toXml(EmptyRequestId)
+  private def handleSQSException(e: SQSException, protocol: AWSProtocol): Route = {
+    protocol match {
+      case AWSProtocol.AWSQueryProtocol =>
+        respondWith(e.httpStatusCode) {
+          e.toXml(EmptyRequestId)
+        }
+      case _ => complete(e.httpStatusCode, ErrorResponse(e.errorType, e.message))
     }
   }
 
-  val exceptionHandlerPF: ExceptionHandler.PF = {
-    case e: SQSException => handleSQSException(e)
+  def exceptionHandlerPF(protocol: AWSProtocol): ExceptionHandler.PF = {
+    case e: SQSException =>
+      handleSQSException(e, protocol)
     case e: Exception =>
       logger.error("Exception when running routes", e)
       _.complete(StatusCodes.InternalServerError)
   }
 
-  val exceptionHandler = ExceptionHandler(exceptionHandlerPF)
+  private def exceptionHandler(protocol: AWSProtocol): ExceptionHandler = ExceptionHandler(exceptionHandlerPF(protocol))
+
+  def handleServerExceptions(protocol: AWSProtocol): Directive0 = handleExceptions(exceptionHandler(protocol))
+
+  case class ErrorResponse(`__type`: String, Message: String)
+  object ErrorResponse {
+    implicit val format: RootJsonFormat[ErrorResponse] = jsonFormat2(ErrorResponse.apply)
+  }
 
-  def handleServerExceptions: Directive0 = handleExceptions(exceptionHandler)
 }
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/FutureDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/FutureDirectives.scala
index 9be51638..b0e98c74 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/FutureDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/FutureDirectives.scala
@@ -9,11 +9,14 @@ import akka.http.scaladsl.server.Directives._
 import scala.util.{Failure, Success}
 
 trait FutureDirectives {
-  this: ExceptionDirectives with ActorSystemModule =>
+  this: ExceptionDirectives with ActorSystemModule with AWSProtocolDirectives =>
 
-  implicit def futureRouteToRoute(futureRoute: Future[Route]): Route =
-    onComplete(futureRoute) {
-      case Success(r) => r
-      case Failure(f) => exceptionHandlerPF(f)
+  implicit def futureRouteToRoute(futureRoute: Future[Route]): Route = {
+    extractProtocol{ protocol =>
+      onComplete(futureRoute) {
+        case Success(r) => r
+        case Failure(f) => exceptionHandlerPF(protocol)(f)
+      }
     }
+  }
 }
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/QueueDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/QueueDirectives.scala
index b74df538..8cfebf78 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/QueueDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/QueueDirectives.scala
@@ -7,7 +7,11 @@ import akka.http.scaladsl.server._
 import org.elasticmq.QueueData
 import org.elasticmq.actor.reply._
 import org.elasticmq.msg.{GetQueueData, LookupQueue}
+import org.elasticmq.rest.sqs.Constants.QueueUrlParameter
 import org.elasticmq.rest.sqs._
+import org.elasticmq.rest.sqs.directives.QueueDirectives.AccountIdRegex
+
+import scala.util.matching.Regex
 
 trait QueueDirectives {
   this: Directives
@@ -15,71 +19,39 @@ trait QueueDirectives {
     with ContextPathModule
     with ActorSystemModule
     with FutureDirectives
-    with AnyParamDirectives =>
-
-  def queueNameFromParams(p: AnyParams): Directive1[String] =
-    p.requiredParam("QueueName")
-
-  def queueDataFromParams(p: AnyParams)(body: QueueData => Route): Route = {
-    queueNameFromParams(p) { queueName => queueActor(queueName, queueData(_, body)) }
-  }
+   =>
 
-  def queueActorFromRequest(p: AnyParams)(body: ActorRef => Route): Route = {
-    queueNameFromRequest(p) { queueName => queueActor(queueName, body) }
-  }
-
-  def queueActorAndDataFromRequest(p: AnyParams)(body: (ActorRef, QueueData) => Route): Route = {
-    queueNameFromRequest(p) { queueName => queueActor(queueName, qa => queueData(qa, qd => body(qa, qd))) }
-  }
+  def queueActorFromUrl(queueUrl: String)(body: ActorRef => Route): Route =
+    getQueueNameFromQueueUrl(queueUrl)(queueName => queueActor(queueName, body))
 
-  def queueActorAndNameFromRequest(p: AnyParams)(body: (ActorRef, String) => Route): Route = {
-    queueNameFromRequest(p) { queueName => queueActor(queueName, qa => body(qa, queueName)) }
+  def queueActorAndNameFromUrl(queueUrl: String)(body: (ActorRef, String) => Route): Route = {
+    getQueueNameFromQueueUrl(queueUrl) { queueName => queueActor(queueName, qa => body(qa, queueName)) }
   }
 
   def queueActorAndDataFromQueueName(queueName: String)(body: (ActorRef, QueueData) => Route): Route = {
     queueActor(queueName, qa => queueData(qa, qd => body(qa, qd)))
   }
 
-  private val queueUrlParameter = "QueueUrl"
-
-  private def queueUrlFromParams(p: AnyParams): Directive1[String] =
-    p.requiredParam(queueUrlParameter)
-
-  private val accountIdRegex = "[a-zA-Z0-9]+".r
-
-  private def queueNameFromRequest(p: AnyParams)(body: String => Route): Route = {
-    val pathDirective =
-      if (contextPath.nonEmpty)
-        pathPrefix(contextPath / accountIdRegex / Segment).tmap(_._2) |
-          pathPrefix(contextPath / Segment)
-      else
-        pathPrefix(accountIdRegex / Segment).tmap(_._2) |
-          pathPrefix(Segment)
-
-    val queueNameDirective =
-      checkOnlyOneSegmentInUri() |
-        pathDirective |
-        queueNameFromParams(p) |
-        queueUrlFromParams(p).flatMap { queueUrl => getQueueNameFromQueueUrl(queueUrl) }
-
-    queueNameDirective(body)
+  def queueActorAndDataFromQueueUrl(queueUrl: String)(body: (ActorRef, QueueData) => Route): Route = {
+    getQueueNameFromQueueUrl(queueUrl)(queueName => queueActor(queueName, qa => queueData(qa, qd => body(qa, qd))))
   }
 
   private def getQueueNameFromQueueUrl(queueUrl: String): Directive1[String] = {
 
     val matcher =
-      if (contextPath.nonEmpty)
-        separateOnSlashes(contextPath) / accountIdRegex / "[^/]+".r
-      else
-        Slash ~ accountIdRegex / "[^/]+".r
+      if (contextPath.nonEmpty) {
+        val pathWithContext = separateOnSlashes(contextPath) / AccountIdRegex / "[^/]+".r
+        Slash ~ pathWithContext | pathWithContext
+      } else
+        Slash ~ AccountIdRegex / "[^/]+".r
 
     matcher(Uri(queueUrl).path) match {
-      case Matched(_, extractions) => provide(extractions._2): Directive1[String]
+      case Matched(_, (_, queueName)) => provide(queueName): Directive1[String]
       case Unmatched =>
         reject(
           MalformedQueryParamRejection(
-            queueUrlParameter,
-            "Invalid queue url, the path should be /<accountId>/<queueName> where accountId must match " + accountIdRegex + " regex"
+            QueueUrlParameter,
+            "Invalid queue url, the path should be /<accountId>/<queueName> where accountId must match " + AccountIdRegex + " regex"
           )
         )
     }
@@ -103,12 +75,8 @@ trait QueueDirectives {
       body(queueData)
     }
   }
-
-  private def checkOnlyOneSegmentInUri(): Directive1[String] = {
-    path(Segment).flatMap { _ =>
-      reject(WrongURLFormatRejection("Provided only queueName instead of the full URL")): Directive1[String]
-    }
-  }
 }
 
-final case class WrongURLFormatRejection(fieldName: String) extends Rejection
+object QueueDirectives {
+  val AccountIdRegex: Regex = "[a-zA-Z0-9]+".r
+}
\ No newline at end of file
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/RejectionDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/RejectionDirectives.scala
index d60fb574..d61c6e80 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/RejectionDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/RejectionDirectives.scala
@@ -1,19 +1,19 @@
 package org.elasticmq.rest.sqs.directives
 
 import akka.http.scaladsl.server.{Directive0, Directives, Rejection, RejectionHandler}
-import org.elasticmq.rest.sqs.SQSException
+import org.elasticmq.rest.sqs.{AWSProtocol, SQSException}
 
 trait RejectionDirectives {
   this: Directives with ExceptionDirectives =>
 
-  val rejectionHandler: RejectionHandler = RejectionHandler
+  def rejectionHandler(protocol: AWSProtocol): RejectionHandler = RejectionHandler
     .newBuilder()
     .handleAll[Rejection] { rejections =>
-      handleServerExceptions { _ =>
+      handleServerExceptions(protocol) { _ =>
         throw new SQSException("Invalid request: " + rejections.map(_.toString).mkString(", "))
       }
     }
     .result()
 
-  def handleRejectionsWithSQSError: Directive0 = handleRejections(rejectionHandler)
+  def handleRejectionsWithSQSError(protocol: AWSProtocol): Directive0 = handleRejections(rejectionHandler(protocol))
 }
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/RespondDirectives.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/RespondDirectives.scala
index b100fe2a..2c1b47e7 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/RespondDirectives.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/RespondDirectives.scala
@@ -4,8 +4,13 @@ import akka.http.scaladsl.model.ContentTypes._
 import akka.http.scaladsl.model.HttpEntity
 import akka.http.scaladsl.server.{Directives, RequestContext, Route}
 import org.elasticmq.rest.sqs.Constants._
+
 import scala.language.postfixOps
+import akka.http.scaladsl.server
+import org.elasticmq.rest.sqs.{AWSProtocol, MarshallerDependencies}
+import org.elasticmq.rest.sqs.Constants.EmptyRequestId
 
+import scala.xml._
 import scala.xml.{Elem, Null, UnprefixedAttribute}
 
 trait RespondDirectives {
@@ -30,10 +35,26 @@ trait RespondDirectives {
   private def namespace(route: UnprefixedAttribute => Route): Route =
     parameter("Version" ?) { versionOpt =>
       val version = versionOpt match {
-        case Some(v) if !v.isEmpty => v
+        case Some(v) if v.nonEmpty => v
         case _                     => SqsDefaultVersion
       }
 
       route(new UnprefixedAttribute("xmlns", "http://queue.amazonaws.com/doc/%s/".format(version), Null))
     }
+
+  def emptyResponse(xmlTagName: String)(implicit marshallerDependencies: MarshallerDependencies): server.Route = {
+    marshallerDependencies.protocol match {
+      case AWSProtocol.`AWSJsonProtocol1.0` => complete(200, HttpEntity.Empty)
+      case _ =>
+        respondWith {
+          <wrapper>
+            <ResponseMetadata>
+              <RequestId>
+                {EmptyRequestId}
+              </RequestId>
+            </ResponseMetadata>
+          </wrapper> % Attribute(None, "name", Text(xmlTagName), Null)
+        }
+    }
+  }
 }
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/UnmatchedActionRoutes.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/UnmatchedActionRoutes.scala
index 0b8c3af3..5c62add4 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/UnmatchedActionRoutes.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/directives/UnmatchedActionRoutes.scala
@@ -1,22 +1,20 @@
 package org.elasticmq.rest.sqs.directives
 
 import akka.http.scaladsl.server.{Directives, Route}
+import org.elasticmq.rest.sqs.model.RequestPayload
 import org.elasticmq.rest.sqs.{Action, AnyParams, SQSException}
 import org.elasticmq.util.Logging
 
 trait UnmatchedActionRoutes {
   this: Logging with Directives =>
 
-  def unmatchedAction(p: AnyParams): Route = {
+  def unmatchedAction(p: RequestPayload): Route = {
     extractRequestContext { _ =>
-      p.get("Action") match {
-        case Some(action) if Action.values.forall(_.toString != action) =>
-          logger.warn(s"Unknown action: $action")
-          throw new SQSException("InvalidAction")
-        case None =>
-          throw new SQSException("MissingAction")
-        case _ =>
-          reject
+      if (Action.values.forall(_.toString != p.action)) {
+        logger.warn(s"Unknown action: ${p.action}")
+        throw new SQSException("InvalidAction")
+      } else {
+        reject
       }
     }
   }
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/model/JsonData.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/model/JsonData.scala
new file mode 100644
index 00000000..b1ddcc8c
--- /dev/null
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/model/JsonData.scala
@@ -0,0 +1,36 @@
+package org.elasticmq.rest.sqs.model
+
+import akka.http.scaladsl.marshalling.Marshaller
+import akka.http.scaladsl.model.{ContentTypeRange, HttpEntity, RequestEntity}
+import akka.http.scaladsl.unmarshalling.{FromEntityUnmarshaller, Unmarshaller}
+import akka.util.ByteString
+import org.elasticmq.rest.sqs.directives.AmzJsonProtocol
+
+import scala.concurrent.Future
+import spray.json._
+case class JsonData(payload: JsObject)
+
+object JsonData {
+  implicit val AmzJsonUnmarshaller: FromEntityUnmarshaller[JsonData] =
+    Unmarshaller.withMaterializer[HttpEntity, JsonData] { implicit ec => implicit materializer => entity =>
+      entity.contentType match {
+        case AmzJsonProtocol(_) =>
+          entity.dataBytes
+            .runFold(ByteString.empty)(_ ++ _)
+            .map(bs => JsonData(bs.utf8String.parseJson.asJsObject))
+        case _ =>
+          Future.failed(
+            Unmarshaller.UnsupportedContentTypeException(
+              entity.contentType,
+              ContentTypeRange(entity.contentType)
+            )
+          )
+      }
+    }
+
+  implicit val AmzJsonMarshaller =
+    Marshaller.withFixedContentType[JsonData, RequestEntity](AmzJsonProtocol.contentType("1.0")) {
+      data =>
+        HttpEntity(AmzJsonProtocol.contentType("1.0"), ByteString(data.payload.prettyPrint))
+    }
+}
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/model/MessageSystemAttribute.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/model/MessageSystemAttribute.scala
deleted file mode 100644
index 674e4822..00000000
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/model/MessageSystemAttribute.scala
+++ /dev/null
@@ -1,14 +0,0 @@
-package org.elasticmq.rest.sqs.model
-
-import java.util.Base64
-
-sealed trait MessageSystemAttribute
-
-case class StringMessageSystemAttribute(value: String) extends MessageSystemAttribute
-case class NumberMessageSystemAttribute(value: String) extends MessageSystemAttribute
-case class BinaryMessageSystemAttribute(value: Array[Byte]) extends MessageSystemAttribute
-
-object BinaryMessageSystemAttribute {
-  def fromString(value: String): BinaryMessageSystemAttribute =
-    BinaryMessageSystemAttribute(Base64.getDecoder.decode(value))
-}
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/model/RequestPayload.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/model/RequestPayload.scala
new file mode 100644
index 00000000..eb0fd0b6
--- /dev/null
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/sqs/model/RequestPayload.scala
@@ -0,0 +1,29 @@
+package org.elasticmq.rest.sqs.model
+
+import akka.http.scaladsl.server.Directive0
+import akka.http.scaladsl.server.Directives._
+import org.elasticmq.rest.sqs.{Action, FlatParamsReader, SQSException}
+import spray.json.{JsObject, RootJsonFormat}
+
+sealed trait RequestPayload {
+  def action(requiredAction: Action.Value): Directive0 = if (requiredAction.toString == action) pass else reject
+
+  def xRayTracingHeader: Option[String]
+  def action: String
+
+  def as[A: RootJsonFormat: FlatParamsReader]: A = this match {
+    case json: RequestPayload.JsonParams => json.readAs[A]
+    case query: RequestPayload.QueryParams => query.readAs[A]
+  }
+}
+
+object RequestPayload {
+  final case class QueryParams(params: Map[String, String], xRayTracingHeader: Option[String] = None) extends RequestPayload {
+    def readAs[A](implicit fpr: FlatParamsReader[A]) = fpr.read(params)
+    override def action: String = params.getOrElse("Action", throw new SQSException("MissingAction"))
+  }
+
+  final case class JsonParams(params: JsObject, action: String, xRayTracingHeader: Option[String] = None) extends RequestPayload {
+    def readAs[A: RootJsonFormat] = params.convertTo[A]
+  }
+}
diff --git a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/stats/StatisticsRestServerBuilder.scala b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/stats/StatisticsRestServerBuilder.scala
index a33a0354..8093b1a4 100644
--- a/rest/rest-sqs/src/main/scala/org/elasticmq/rest/stats/StatisticsRestServerBuilder.scala
+++ b/rest/rest-sqs/src/main/scala/org/elasticmq/rest/stats/StatisticsRestServerBuilder.scala
@@ -1,13 +1,12 @@
 package org.elasticmq.rest.stats
 
 import java.util.concurrent.TimeUnit
-
 import akka.actor.{ActorRef, ActorSystem}
 import akka.http.scaladsl.Http
 import akka.stream.ActorMaterializer
 import akka.util.Timeout
 import org.elasticmq.rest.sqs.QueueAttributesOps
-import org.elasticmq.rest.sqs.directives.ElasticMQDirectives
+import org.elasticmq.rest.sqs.directives.{AWSProtocolDirectives, ElasticMQDirectives}
 import org.elasticmq.util.{Logging, NowProvider}
 
 import scala.concurrent.duration._
@@ -72,7 +71,7 @@ case class TheStatisticsRestServerBuilder(
     implicit val implicitActorSystem = providedActorSystem
     implicit val implicitMaterializer = ActorMaterializer()
 
-    val env = new StatisticsDirectives with QueueAttributesOps with ElasticMQDirectives {
+    val env = new StatisticsDirectives with QueueAttributesOps with ElasticMQDirectives with AWSProtocolDirectives {
 
       lazy val actorSystem = providedActorSystem
       lazy val materializer = implicitMaterializer
@@ -87,10 +86,13 @@ case class TheStatisticsRestServerBuilder(
 
     import env._
 
-    val routes =
-      handleServerExceptions {
-        statistics
+    val routes = {
+      extractProtocol{protocol =>
+        handleServerExceptions(protocol) {
+          statistics
+        }
       }
+    }
 
     val appStartFuture = Http().newServerAt(interface, port).bindFlow(routes)
 
diff --git a/server/src/main/scala/org/elasticmq/server/Main.scala b/server/src/main/scala/org/elasticmq/server/Main.scala
index f0dc5876..0cef6922 100644
--- a/server/src/main/scala/org/elasticmq/server/Main.scala
+++ b/server/src/main/scala/org/elasticmq/server/Main.scala
@@ -5,8 +5,6 @@ import com.typesafe.config.ConfigFactory
 import org.elasticmq.server.config.ElasticMQServerConfig
 import org.elasticmq.util.Logging
 
-import scala.concurrent.duration.Duration.Inf
-import scala.concurrent.{Await, Future}
 import scala.io.Source
 
 object Main extends Logging {


Test patch:
diff --git a/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/AmazonCliTestSuite.scala b/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/AmazonCliTestSuite.scala
new file mode 100644
index 00000000..779773a2
--- /dev/null
+++ b/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/AmazonCliTestSuite.scala
@@ -0,0 +1,515 @@
+package org.elasticmq.rest.sqs
+
+import org.elasticmq.StringMessageAttribute
+import org.scalatest.{Inside, LoneElement, OptionValues, Tag}
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.prop.TableDrivenPropertyChecks
+import spray.json.DefaultJsonProtocol.listFormat
+
+import scala.sys.process._
+import scala.language.postfixOps
+import spray.json._
+
+import scala.collection.mutable
+
+object Only213 extends Tag("org.elasticmq.rest.sqs.Only213")
+class AmazonCliTestSuite
+    extends SqsClientServerCommunication
+    with Matchers
+    with TableDrivenPropertyChecks
+    with OptionValues
+    with Inside
+    with LoneElement {
+
+  val cliVersions = Table(
+    "cli version",
+    AWSCli(Option(System.getenv("AWS_CLI_V1_EXECUTABLE")).getOrElse("aws1"), "aws version 1"),
+    AWSCli(Option(System.getenv("AWS_CLI_V2_EXECUTABLE")).getOrElse("aws"), "aws version 2")
+  )
+
+  def createQueue(name: String)(implicit cli: AWSCli): CreateQueueResponse = {
+    val result =
+      s"""${cli.executable} sqs create-queue --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-name=$name""" !!
+
+    result.parseJson.convertTo[CreateQueueResponse]
+  }
+
+  def tag(url: String, tags: String)(implicit cli: AWSCli): Unit =
+    s"""${cli.executable} sqs tag-queue --tags="$tags" --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-url=$url""" !!
+
+  def untag(url: String, tags: String)(implicit cli: AWSCli): Unit =
+    s"""${cli.executable} sqs untag-queue --tag-keys="$tags" --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-url=$url""" !!
+
+  def listTags(url: String)(implicit cli: AWSCli): ListQueueTagsResponse = {
+    val result =
+      s"""${cli.executable} sqs list-queue-tags --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-url=$url""" !!
+
+    result.parseJson.convertTo[ListQueueTagsResponse]
+  }
+
+  def deleteQueue(url: String)(implicit cli: AWSCli): Unit = {
+    s"""${cli.executable} sqs delete-queue --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-url=$url """ !!
+  }
+
+  def getQueueUrl(name: String)(implicit cli: AWSCli): GetQueueURLResponse = {
+    val result =
+      s"""${cli.executable} sqs get-queue-url --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-name=$name""" !!
+
+    result.parseJson.convertTo[GetQueueURLResponse]
+  }
+
+  def listQueues(prefix: Option[String] = None)(implicit cli: AWSCli): ListQueuesResponse = {
+    val prefixStr = prefix.fold("")(s => s"--queue-name-prefix=$s")
+
+    val result =
+      s"""${cli.executable} sqs list-queues $prefixStr --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request""" !!
+
+    if (result.nonEmpty) {
+      result.parseJson.convertTo[ListQueuesResponse]
+    } else {
+      ListQueuesResponse(List.empty)
+    }
+  }
+
+  def sendMessage(messageBody: String, queueUrl: String)(implicit cli: AWSCli): SendMessageResponse = {
+    val result =
+      s"""${cli.executable} sqs send-message --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-url=$queueUrl --message-body=$messageBody""" !!
+
+    result.parseJson.convertTo[SendMessageResponse]
+  }
+
+  def sendMessageWithAttributes(
+      messageBody: String,
+      queueUrl: String,
+      messageAttributes: String
+  )(implicit cli: AWSCli): SendMessageResponse = {
+    val result =
+      s"""${cli.executable} sqs send-message --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-url=$queueUrl --message-body=$messageBody --message-attributes='$messageAttributes'""" !!
+
+    result.parseJson.convertTo[SendMessageResponse]
+  }
+
+  def sendMessage(
+      messageBody: String,
+      queueUrl: String,
+      messageAttributes: Option[String],
+      systemAttributes: Option[String]
+  )(implicit cli: AWSCli): SendMessageResponse = {
+    val messageAttributesStr = messageAttributes.fold("")(v => s"--message-attributes='$v'")
+    val systemAttributesStr = systemAttributes.fold("")(v => s"--message-system-attributes='$v'")
+
+    val result =
+      s"""${cli.executable} sqs send-message --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-url=$queueUrl --message-body=$messageBody $messageAttributesStr $systemAttributesStr""" !!
+
+    result.parseJson.convertTo[SendMessageResponse]
+  }
+
+  def sendMessageWithAttributesAndSystemAttributes(
+      messageBody: String,
+      queueUrl: String,
+      messageAttributes: String,
+      systemAttributes: String
+  )(implicit cli: AWSCli): SendMessageResponse =
+    sendMessage(messageBody, queueUrl, Some(messageAttributes), Some(systemAttributes))
+
+  def receiveMessage(
+      queueUrl: String,
+      attributeNames: String = "All",
+      messageAttributeNames: String = "All",
+      maxNumberOfMessages: String = "10"
+  )(implicit
+      cli: AWSCli
+  ): ReceiveMessageResponse = {
+    val attributeNamesStr = s"--attribute-names='$attributeNames'"
+    val messageAttributeNamesStr = s"--message-attribute-names='$messageAttributeNames'"
+    val maxNumberOfMessagesStr = s"--max-number-of-messages='$maxNumberOfMessages'"
+
+    val result =
+      s"""${cli.executable} sqs receive-message --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-url=$queueUrl $attributeNamesStr $messageAttributeNamesStr $maxNumberOfMessagesStr""" !!
+
+    result.parseJson.convertTo[ReceiveMessageResponse]
+  }
+
+  def sendMessageBatch(queueUrl: String, entries: String)(implicit
+      cli: AWSCli
+  ): BatchResponse[BatchMessageSendResponseEntry] = {
+    val result =
+      s"""${cli.executable} sqs send-message-batch --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-url=$queueUrl  --entries='[$entries]'""" !!
+
+    result.parseJson.convertTo[BatchResponse[BatchMessageSendResponseEntry]]
+  }
+
+  def deleteMessage(queueUrl: String, receiptHandle: String)(implicit cli: AWSCli): Unit = {
+    s"""${cli.executable} sqs delete-message --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-url=$queueUrl --receipt-handle="$receiptHandle" """ !!
+  }
+
+  def deleteMessageBatch(queueUrl: String, entries: String)(implicit
+      cli: AWSCli
+  ): List[BatchDeleteMessageResponseEntry] = {
+    val result =
+      s"""${cli.executable} sqs delete-message-batch --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-url=$queueUrl --entries='[$entries]'""" !!
+
+    result.parseJson.asJsObject.fields
+      .getOrElse("Successful", fail("couldn't find successful field"))
+      .convertTo[List[BatchDeleteMessageResponseEntry]]
+  }
+
+  forAll(cliVersions) { implicit version =>
+    test(s"should create a queue and get queue url ${version.name}") {
+
+      val create = createQueue("test-queue")
+
+      val get = getQueueUrl("test-queue")
+
+      get.QueueUrl shouldBe create.QueueUrl
+    }
+
+    test(s"should list created queues ${version.name}") {
+      createQueue("test-queue1")
+      createQueue("test-queue2")
+
+      val get = listQueues()
+
+      get.QueueUrls should contain allOf (s"$ServiceEndpoint/$awsAccountId/test-queue2", s"$ServiceEndpoint/$awsAccountId/test-queue1")
+    }
+
+    test(s"should list queues with the specified prefix ${version.name}") {
+      // Given
+      createQueue("aaa-test-queue1")
+      createQueue("aaa-test-queue2")
+      createQueue("bbb-test-queue2")
+
+      // When
+      val get = listQueues(prefix = Some("aaa"))
+
+      get.QueueUrls.length shouldBe 2
+      get.QueueUrls should contain allOf (s"$ServiceEndpoint/$awsAccountId/aaa-test-queue2", s"$ServiceEndpoint/$awsAccountId/aaa-test-queue1")
+    }
+
+    test(s"should send a single message to queue ${version.name}", Only213) {
+      // given
+      createQueue("test-queue")
+      val queueUrl = getQueueUrl("test-queue").QueueUrl
+
+      // when
+      val message = sendMessage("simpleMessage", queueUrl)
+
+      // then
+      message.MessageId.isEmpty shouldBe false
+      message.MD5OfMessageBody.isEmpty shouldBe false
+      message.MD5OfMessageAttributes shouldBe empty
+      message.MD5OfMessageSystemAttributes shouldBe empty
+      message.SequenceNumber shouldBe empty
+    }
+
+    test(s"should send a single message with Attributes to queue ${version.name}", Only213) {
+      // given
+      createQueue("test-queue")
+      val queueUrl = getQueueUrl("test-queue").QueueUrl
+      val messageAttributes = """{ "firstAttribute": { "DataType": "String", "StringValue": "hello world" } }"""
+
+      // when
+      val message = sendMessageWithAttributes("simpleMessage", queueUrl, messageAttributes)
+
+      // then
+      message.MessageId.isEmpty shouldBe false
+      message.MD5OfMessageBody.isEmpty shouldBe false
+      message.MD5OfMessageAttributes.isEmpty shouldBe false
+      message.MD5OfMessageSystemAttributes shouldBe empty
+      message.SequenceNumber shouldBe empty
+    }
+
+    test(s"should send a single message with Attributes and System Attributes to queue ${version.name}", Only213) {
+      // given
+      createQueue("test-queue")
+      val queueUrl = getQueueUrl("test-queue").QueueUrl
+      val messageAttributes = """{ "firstAttribute": { "DataType": "String", "StringValue": "hello world" } }"""
+      val systemAttributes =
+        """{ "AWSTraceHeader": { "DataType": "String", "StringValue": "your-xray-trace-header-string" } }"""
+
+      // when
+      val message =
+        sendMessageWithAttributesAndSystemAttributes("simpleMessage", queueUrl, messageAttributes, systemAttributes)
+
+      // then
+      message.MessageId.isEmpty shouldBe false
+      message.MD5OfMessageBody.isEmpty shouldBe false
+      message.MD5OfMessageAttributes.isEmpty shouldBe false
+      // message.MD5OfMessageSystemAttributes.isEmpty shouldBe false TODO it's not calculated atm
+      message.SequenceNumber shouldBe empty
+    }
+
+    test(s"should fail if message is sent to missing queue ${version.name}", Only213) {
+      // given
+      val outLines = mutable.ListBuffer.empty[String]
+      val errLines = mutable.ListBuffer.empty[String]
+
+      val logger = new ProcessLogger {
+        override def out(s: => String): Unit = outLines.append(s)
+
+        override def err(s: => String): Unit = errLines.append(s)
+
+        override def buffer[T](f: => T): T = f
+      }
+
+      // when
+      val queueUrl = s"$ServiceEndpoint/$awsAccountId/miss"
+
+      intercept[Exception] {
+        s"""${version.executable} sqs send-message --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --message-body=hello --queue-url=$queueUrl""" !! (logger)
+      }
+
+      // then
+      outLines.mkString("\n") shouldBe empty
+      errLines.mkString("\n") should include("AWS.SimpleQueueService.NonExistentQueue; see the SQS docs.")
+    }
+
+    test(s"should tag, untag and list queue tags ${version.name}", Only213) {
+      // given
+      val url = createQueue("test-queue").QueueUrl
+
+      // when
+      tag(url, "a='A', b='B'")
+
+      // then
+      listTags(url).Tags shouldBe Map("a" -> "A", "b" -> "B")
+
+      // when
+      untag(url, "b")
+
+      // then
+      listTags(url).Tags shouldBe Map("a" -> "A")
+
+      deleteQueue(url)
+    }
+
+    test(s"should add permission ${version.name}") {
+      // given
+      val url = createQueue("permission-test").QueueUrl
+
+      // when ~> then
+      s"""${version.executable} sqs add-permission --label l --aws-account-ids=$awsAccountId --actions=get --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-url=$url""" !!
+    }
+
+    test(s"should purge queue ${version.name}") {
+      // given
+      val url = createQueue("permission-test").QueueUrl
+
+      // when ~> then
+      s"""${version.executable} sqs purge-queue --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-url=$url""" !!
+    }
+
+    test(s"should delete created queue with ${version.name}") {
+      // given
+      val queue = createQueue("test-queue")
+      val listQueuesBeforeDelete = listQueues()
+      listQueuesBeforeDelete.QueueUrls.size shouldBe 1
+
+      // when
+      deleteQueue(queue.QueueUrl)
+      val listQueuesAfterDelete = listQueues()
+
+      // then
+      listQueuesAfterDelete.QueueUrls.size shouldBe 0
+    }
+
+    test(s"should receive message with ${version.name}", Only213) {
+      // given
+      val queue = createQueue("test-queue")
+      val firstMessageBody = "simpleMessageOne"
+      val secondMessageBody = "simpleMessageTwo"
+      val firstMessageAttributes =
+        """{ "firstAttribute": { "DataType": "String", "StringValue": "hello world one" } }"""
+      val secondMessageAttributes =
+        """{ "secondAttribute": { "DataType": "String", "StringValue": "hello world two" } }"""
+      val firstMessage = sendMessageWithAttributes(firstMessageBody, queue.QueueUrl, firstMessageAttributes)
+      val secondMessage = sendMessageWithAttributes(secondMessageBody, queue.QueueUrl, secondMessageAttributes)
+
+      // when
+      val receivedMessage = receiveMessage(queue.QueueUrl, "All", "All", "2")
+
+      // then
+      receivedMessage.Messages.size shouldBe 2
+      inside(receivedMessage.Messages.head) { case msg =>
+        import msg._
+        MessageId shouldBe firstMessage.MessageId
+        ReceiptHandle.nonEmpty shouldBe true
+        MD5OfBody shouldBe firstMessage.MD5OfMessageBody
+        Body shouldBe firstMessageBody
+        Attributes.size shouldBe 4
+        Attributes.get("SentTimestamp").map(v => v.nonEmpty shouldBe true)
+        Attributes.get("ApproximateReceiveCount").map(_ shouldBe "1")
+        Attributes.get("ApproximateFirstReceiveTimestamp").map(v => v.nonEmpty shouldBe true)
+        Attributes.get("SenderId").map(_ shouldBe "127.0.0.1")
+        MD5OfMessageAttributes.nonEmpty shouldBe true
+        MessageAttributes.nonEmpty shouldBe true
+        MessageAttributes.get("firstAttribute").map { msgAtt =>
+          msgAtt.getDataType() shouldBe "String"
+          msgAtt shouldBe a[StringMessageAttribute]
+          inside(msgAtt) { case stringMessageAttribute: StringMessageAttribute =>
+            stringMessageAttribute.stringValue shouldBe "hello world one"
+          }
+        }
+      }
+      inside(receivedMessage.Messages(1)) { case msg =>
+        import msg._
+        MessageId shouldBe secondMessage.MessageId
+        ReceiptHandle.nonEmpty shouldBe true
+        MD5OfBody shouldBe secondMessage.MD5OfMessageBody
+        Body shouldBe secondMessageBody
+        Attributes.size shouldBe 4
+        Attributes.get("SentTimestamp").map(v => v.nonEmpty shouldBe true)
+        Attributes.get("ApproximateReceiveCount").map(_ shouldBe "1")
+        Attributes.get("ApproximateFirstReceiveTimestamp").map(v => v.nonEmpty shouldBe true)
+        Attributes.get("SenderId").map(_ shouldBe "127.0.0.1")
+        MD5OfMessageAttributes.nonEmpty shouldBe true
+        MessageAttributes.nonEmpty shouldBe true
+        MessageAttributes.get("secondAttribute").map { msgAtt =>
+          msgAtt.getDataType() shouldBe "String"
+          msgAtt shouldBe a[StringMessageAttribute]
+          inside(msgAtt) { case stringMessageAttribute: StringMessageAttribute =>
+            stringMessageAttribute.stringValue shouldBe "hello world two"
+          }
+        }
+      }
+    }
+
+    test(s"should send message batch with ${version.name}", Only213) {
+      // given
+      val queue = createQueue("test-queue")
+      val firstMessageBody = "messageOne"
+      val entries = s"""{"Id": "1", "MessageBody": "$firstMessageBody"}, {"Id": "2", "MessageBody": ""}"""
+
+      // when
+      val batchMessages = sendMessageBatch(queue.QueueUrl, entries)
+
+      // then
+      batchMessages.Failed.size shouldBe 1
+      inside(batchMessages.Failed.head) { case failedMessage =>
+        import failedMessage._
+        Id shouldBe "2"
+        SenderFault shouldBe true
+        Code shouldBe "The request must contain the parameter MessageBody."
+        Message shouldBe "The request must contain the parameter MessageBody.; see the SQS docs."
+      }
+
+      batchMessages.Successful.size shouldBe 1
+      inside(batchMessages.Successful.head) { case successfulMessage =>
+        import successfulMessage._
+        Id shouldBe "1"
+        MessageId.nonEmpty shouldBe true
+        MD5OfMessageBody.nonEmpty shouldBe true
+      }
+    }
+
+    test(s"should delete message with ${version.name}", Only213) {
+      // given
+      val queue = createQueue("test-queue")
+      val messageAttributes =
+        """{ "firstAttribute": { "DataType": "String", "StringValue": "hello world one" } }"""
+      sendMessageWithAttributes("simpleMessage", queue.QueueUrl, messageAttributes)
+      val receivedMessage = receiveMessage(queue.QueueUrl)
+      val receiptHandle = receivedMessage.Messages.head.ReceiptHandle
+
+      // when
+      // then
+      deleteMessage(queue.QueueUrl, receiptHandle)
+    }
+
+    test(s"should delete message batch with ${version.name}", Only213) {
+      // given
+      val queue = createQueue("test-queue")
+      val firstMessageBody = "simpleMessageOne"
+      val secondMessageBody = "simpleMessageTwo"
+      val firstMessageAttributes =
+        """{ "firstAttribute": { "DataType": "String", "StringValue": "hello world one" } }"""
+      val secondMessageAttributes =
+        """{ "secondAttribute": { "DataType": "String", "StringValue": "hello world two" } }"""
+      val firstMessage = sendMessageWithAttributes(firstMessageBody, queue.QueueUrl, firstMessageAttributes)
+      val secondMessage = sendMessageWithAttributes(secondMessageBody, queue.QueueUrl, secondMessageAttributes)
+
+      val receivedMessages = receiveMessage(queue.QueueUrl)
+      val receiptHandles = receivedMessages.Messages.map(msg => msg.MessageId -> msg.ReceiptHandle).toMap
+      val entries = receiptHandles
+        .map { case (id, receiptHandle) =>
+          s"""{"Id": "$id", "ReceiptHandle": "$receiptHandle"}"""
+        }
+        .mkString(", ")
+
+      // when
+      val deleteMessageBatchEntries = deleteMessageBatch(queue.QueueUrl, entries)
+
+      // then
+      deleteMessageBatchEntries.size shouldBe 2
+      inside(deleteMessageBatchEntries.head) { case deletedMessage =>
+        deletedMessage.Id shouldBe firstMessage.MessageId
+      }
+      inside(deleteMessageBatchEntries(1)) { case deletedMessage =>
+        deletedMessage.Id shouldBe secondMessage.MessageId
+      }
+    }
+
+    test(s"change message visibility with ${version.name}", Only213) {
+      // given
+      val queue = createQueue("test-queue")
+      sendMessageWithAttributes(
+        "hello",
+        queue.QueueUrl,
+        """{ "firstAttribute": { "DataType": "String", "StringValue": "hello world one" } }"""
+      )
+
+      val msg = receiveMessage(queue.QueueUrl).Messages.loneElement
+
+      // when ~> then
+      s"""${version.executable} sqs change-message-visibility --visibility-timeout=100 --receipt-handle=${msg.ReceiptHandle} --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-url=${queue.QueueUrl}""" !!
+    }
+
+    test(s"change message visibility batch with ${version.name}", Only213) {
+      // given
+      val queue = createQueue("test-queue")
+      val attrs = """{ "firstAttribute": { "DataType": "String", "StringValue": "hello world one" } }"""
+      sendMessageWithAttributes(
+        "hello1",
+        queue.QueueUrl,
+        attrs
+      )
+      sendMessageWithAttributes(
+        "hello2",
+        queue.QueueUrl,
+        attrs
+      )
+      sendMessageWithAttributes(
+        "hello3",
+        queue.QueueUrl,
+        attrs
+      )
+
+      val entries = JsArray(
+        receiveMessage(queue.QueueUrl).Messages.map { m =>
+          JsObject(
+            "Id" -> JsString(s"${m.MessageId}"),
+            "ReceiptHandle" -> JsString(s"${m.ReceiptHandle}"),
+            "VisibilityTimeout" -> JsNumber(500)
+          )
+        }.toVector
+      ).compactPrint
+
+      // then
+      s"""${version.executable} sqs change-message-visibility-batch --entries='$entries' --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-url=${queue.QueueUrl}""" !!
+    }
+
+    test(s"should set and get queue attributes with ${version.name}", Only213) {
+      val queue = createQueue("test")
+
+      // when
+      s"${version.executable} sqs set-queue-attributes --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-url ${queue.QueueUrl} --attributes='VisibilityTimeout=99'" !!
+
+      val result =
+        s"${version.executable} sqs get-queue-attributes --endpoint=$ServiceEndpoint --region=us-west-1 --no-sign-request --queue-url ${queue.QueueUrl} --attribute-names='VisibilityTimeout'" !!
+
+      result.parseJson.convertTo[GetQueueAttributesResponse].Attributes.get("VisibilityTimeout") shouldBe Some("99")
+    }
+  }
+}
+
+case class AWSCli(executable: String, name: String)
diff --git a/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/AmazonJavaSdkTestSuite.scala b/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/AmazonJavaSdkTestSuite.scala
index afa5fd64..d147251f 100644
--- a/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/AmazonJavaSdkTestSuite.scala
+++ b/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/AmazonJavaSdkTestSuite.scala
@@ -279,7 +279,7 @@ class AmazonJavaSdkTestSuite extends SqsClientServerCommunication with Matchers
     )
   }
 
-  test("should reply with a InvalidAction error when uknown action set") {
+  test("should reply with a InvalidAction error when unknown action set") {
     // given
     val httpHost = new HttpHost("localhost", 9321)
     val req = new HttpPost()
@@ -1788,7 +1788,7 @@ class AmazonJavaSdkTestSuite extends SqsClientServerCommunication with Matchers
 
     // When
     val redrivePolicy = RedrivePolicy("dlq1", awsRegion, awsAccountId, 1).toJson.toString()
-    println(redrivePolicy)
+
     val createQueueResult = client.createQueue(
       new CreateQueueRequest("q1")
         .withAttributes(Map(defaultVisibilityTimeoutAttribute -> "1", redrivePolicyAttribute -> redrivePolicy).asJava)
diff --git a/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/SqsClientServerCommunication.scala b/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/SqsClientServerCommunication.scala
index 57ad9187..ae094cd8 100644
--- a/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/SqsClientServerCommunication.scala
+++ b/rest/rest-sqs-testing-amazon-java-sdk/src/test/scala/org/elasticmq/rest/sqs/SqsClientServerCommunication.scala
@@ -26,6 +26,8 @@ trait SqsClientServerCommunication extends AnyFunSuite with BeforeAndAfter with
   val awsAccountId = "123456789012"
   val awsRegion = "elasticmq"
 
+  val ServiceEndpoint = "http://localhost:9321"
+
   before {
     logger.info(s"\n---\nRunning test: $currentTestName\n---\n")
 
@@ -48,7 +50,7 @@ trait SqsClientServerCommunication extends AnyFunSuite with BeforeAndAfter with
     client = AmazonSQSClientBuilder
       .standard()
       .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials("x", "x")))
-      .withEndpointConfiguration(new EndpointConfiguration("http://localhost:9321", "us-east-1"))
+      .withEndpointConfiguration(new EndpointConfiguration(ServiceEndpoint, "us-east-1"))
       .build()
 
     relaxedClient = AmazonSQSClientBuilder
diff --git a/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/BatchRequestsModuleTest.scala b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/BatchRequestsModuleTest.scala
index 7b7c4910..a2650b37 100644
--- a/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/BatchRequestsModuleTest.scala
+++ b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/BatchRequestsModuleTest.scala
@@ -5,6 +5,15 @@ import org.scalatest.funsuite.AnyFunSuite
 import org.scalatest.matchers.should.Matchers
 
 class BatchRequestsModuleTest extends AnyFunSuite with Matchers {
+
+  val prefix =  "SomePrefix"
+
+  implicit val fpr = new BatchFlatParamsReader[Map[String, String]] {
+    override def batchPrefix: String = prefix
+
+    override def read(params: Map[String, String]): Map[String, String] = params
+  }
+
   test("should correctly find sub parameters") {
     // Given
     val parameters = Map(
@@ -20,7 +29,7 @@ class BatchRequestsModuleTest extends AnyFunSuite with Matchers {
     )
 
     // When
-    val subParameters = BatchRequestsModule.subParametersMaps("SomePrefix", parameters)
+    val subParameters = BatchRequestsModule.subParametersMaps(parameters)
 
     // Then
     subParameters should have length (3)
@@ -44,7 +53,7 @@ class BatchRequestsModuleTest extends AnyFunSuite with Matchers {
     )
 
     // When
-    val subParameters = BatchRequestsModule.subParametersMaps("SomePrefix", parameters)
+    val subParameters = BatchRequestsModule.subParametersMaps(parameters)
 
     // Then
     subParameters should contain theSameElementsInOrderAs List(
@@ -56,7 +65,6 @@ class BatchRequestsModuleTest extends AnyFunSuite with Matchers {
 
   test("should preserve the order for sub parameters with a size greater than 10") {
     // Given
-    val prefix = "SomePrefix"
 
     // SomePrefix.1.Key1 -> Value1-1
     // SomePrefix.1.Key2 -> Value1-2
@@ -71,7 +79,7 @@ class BatchRequestsModuleTest extends AnyFunSuite with Matchers {
       .toMap
 
     // When
-    val subParameters = BatchRequestsModule.subParametersMaps(prefix, parameters)
+    val subParameters = BatchRequestsModule.subParametersMaps(parameters)
 
     // Then
     // Key4 -> Value1-4
diff --git a/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/RedrivePolicyJsonTest.scala b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/RedrivePolicyJsonTest.scala
index dbe334e9..30327ff2 100644
--- a/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/RedrivePolicyJsonTest.scala
+++ b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/RedrivePolicyJsonTest.scala
@@ -2,7 +2,7 @@ package org.elasticmq.rest.sqs
 
 import akka.http.scaladsl.testkit.ScalatestRouteTest
 import org.elasticmq.rest.sqs.model.{GenericRedrivePolicy, RedrivePolicy}
-import org.elasticmq.rest.sqs.model.RedrivePolicy.{BackwardCompatibleRedrivePolicy, RedrivePolicy}
+import org.elasticmq.rest.sqs.model.RedrivePolicy.BackwardCompatibleRedrivePolicy
 import spray.json._
 import org.scalatest.flatspec.AnyFlatSpec
 import org.scalatest.matchers.should.Matchers
diff --git a/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/AnyParamDirectivesTest.scala b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/AnyParamDirectivesTest.scala
index 25e6ebef..90acfe23 100644
--- a/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/AnyParamDirectivesTest.scala
+++ b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/AnyParamDirectivesTest.scala
@@ -1,21 +1,28 @@
 package org.elasticmq.rest.sqs.directives
 
 import akka.http.scaladsl.model.FormData
+import akka.http.scaladsl.model.headers.RawHeader
 import akka.http.scaladsl.server.Directives
 import akka.http.scaladsl.testkit.ScalatestRouteTest
+import org.elasticmq.NodeAddress
+import org.elasticmq.rest.sqs.{AWSProtocol, ContextPathModule, QueueURLModule}
+import org.elasticmq.rest.sqs.model.JsonData
 import org.scalatest.flatspec.AnyFlatSpec
 import org.scalatest.matchers.should.Matchers
-
+import org.elasticmq.rest.sqs.model.RequestPayload.{JsonParams, QueryParams}
+import spray.json.{JsObject, JsString}
 class AnyParamDirectivesTest
     extends AnyFlatSpec
     with Matchers
     with ScalatestRouteTest
     with Directives
-    with AnyParamDirectives {
+    with AnyParamDirectives
+    with QueueURLModule
+    with ContextPathModule {
 
-  "anyParamsMap" should "extract both query and form parameters" in {
+  "anyParamsMap" should "extract both query and form parameters for query protocol" in {
     val route = path("test") {
-      anyParamsMap { map => _.complete(map.toList.sorted.toString()) }
+      anyParamsMap(AWSProtocol.AWSQueryProtocol) { map => _.complete(map.asInstanceOf[QueryParams].params.toList.sorted.toString()) }
     }
 
     Get("/test?x=1&y=2") ~> route ~> check {
@@ -30,4 +37,27 @@ class AnyParamDirectivesTest
       entityAs[String] should be("List((a,10), (b,1), (x,2), (y,20))")
     }
   }
+
+  "anyParamsMap" should "extract params from json and header for aws json protocol 1.0" in {
+    val route = path("test") {
+      anyParamsMap(AWSProtocol.`AWSJsonProtocol1.0`) { map => _.complete(map.asInstanceOf[JsonParams].params.prettyPrint) }
+    }
+
+    Post("/test", JsonData(JsObject("x" -> JsString("1"), "Tags" -> JsObject("x" -> JsString("123"))))) ~> RawHeader(
+      "X-Amz-Target",
+      "AmazonSQS.Test"
+    ) ~> route ~> check {
+      entityAs[String] should be("""{
+                                    |  "Tags": {
+                                    |    "x": "123"
+                                    |  },
+                                    |  "x": "1"
+                                    |}""".stripMargin)
+    }
+
+  }
+
+  override def contextPath: String = ""
+  override def serverAddress: NodeAddress = NodeAddress()
+  override def awsAccountId: String = "123123333"
 }
diff --git a/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/QueueDirectivesTest.scala b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/QueueDirectivesTest.scala
index 0a3caf2e..b9035d0c 100644
--- a/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/QueueDirectivesTest.scala
+++ b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/QueueDirectivesTest.scala
@@ -10,11 +10,12 @@ import org.elasticmq.rest.sqs.{ActorSystemModule, ContextPathModule, QueueManage
 import org.elasticmq.util.NowProvider
 import org.elasticmq.{CreateQueueData, MillisVisibilityTimeout, QueueData, StrictSQSLimits}
 import org.joda.time.{DateTime, Duration}
+import org.scalatest.concurrent.ScalaFutures
 import org.scalatest.flatspec.AnyFlatSpec
 import org.scalatest.matchers.should.Matchers
 
 import scala.concurrent.duration._
-import scala.concurrent.{Await, ExecutionContextExecutor}
+import scala.concurrent.ExecutionContextExecutor
 
 class QueueDirectivesTest
     extends AnyFlatSpec
@@ -23,12 +24,13 @@ class QueueDirectivesTest
     with Directives
     with QueueDirectives
     with ContextPathModule
-    with AnyParamDirectives
     with QueueManagerActorModule
     with FutureDirectives
     with ActorSystemModule
     with ExceptionDirectives
-    with RespondDirectives {
+    with RespondDirectives
+    with ScalaFutures
+    with AWSProtocolDirectives {
 
   private val maxDuration = 1.minute
   implicit val timeout: Timeout = maxDuration
@@ -38,16 +40,16 @@ class QueueDirectivesTest
     actorSystem.actorOf(Props(new QueueManagerActor(new NowProvider(), StrictSQSLimits, None)))
   lazy val contextPath = ""
 
-  "queueActorAndNameFromRequest" should "return correct queue name based on QueueName" in {
+  "queueActorFromUrl" should "return correct queue name based on QueueName" in {
     val future = queueManagerActor ? CreateQueue(
       CreateQueueData.from(
         QueueData("lol", MillisVisibilityTimeout(1L), Duration.ZERO, Duration.ZERO, DateTime.now(), DateTime.now())
       )
     )
-    Await.result(future, maxDuration)
+    future.value
     val route = {
-      queueActorAndNameFromRequest(
-        Map("QueueName" -> "lol", "QueueUrl" -> "https://eu-central-1.queue.amazonaws.com/906175111765/lol")
+      queueActorAndNameFromUrl(
+        "https://eu-central-1.queue.amazonaws.com/906175111765/lol"
       ) { (_, name) => _.complete(name) }
     }
 
@@ -56,16 +58,16 @@ class QueueDirectivesTest
     }
   }
 
-  "queueActorAndNameFromRequest" should "return correct queue name based on QueueUrl" in {
+  "queueActorAndNameFromUrl" should "return correct queue name based on QueueUrl" in {
     val future = queueManagerActor ? CreateQueue(
       CreateQueueData.from(
         QueueData("lol", MillisVisibilityTimeout(1L), Duration.ZERO, Duration.ZERO, DateTime.now(), DateTime.now())
       )
     )
-    Await.result(future, maxDuration)
+    future.value
     val route = {
-      queueActorAndNameFromRequest(
-        Map("QueueUrl" -> "https://eu-central-1.queue.amazonaws.com/906175111765/lol")
+      queueActorAndNameFromUrl(
+        "https://eu-central-1.queue.amazonaws.com/906175111765/lol"
       ) { (_, name) => _.complete(name) }
     }
 
@@ -74,18 +76,20 @@ class QueueDirectivesTest
     }
   }
 
-  "queueActorAndNameFromRequest" should "return error when invalid QueueUrl" in {
+  "queueActorAndNameFromUrl" should "return error when invalid QueueUrl" in {
     val future = queueManagerActor ? CreateQueue(
       CreateQueueData.from(
         QueueData("lol", MillisVisibilityTimeout(1L), Duration.ZERO, Duration.ZERO, DateTime.now(), DateTime.now())
       )
     )
-    Await.result(future, maxDuration)
+    future.value
     val route = {
-      handleServerExceptions {
-        queueActorAndNameFromRequest(
-          Map("QueueUrl" -> "https://eu-central-1.queue.amazonaws.com")
-        ) { (_, name) => _.complete(name) }
+      extractProtocol { protocol =>
+        handleServerExceptions(protocol) {
+          queueActorAndNameFromUrl(
+            "https://eu-central-1.queue.amazonaws.com"
+          ) { (_, name) => _.complete(name) }
+        }
       }
     }
 
diff --git a/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/QueueDirectivesWithContextPathTest.scala b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/QueueDirectivesWithContextPathTest.scala
index ae6f9c9a..d4f9441a 100644
--- a/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/QueueDirectivesWithContextPathTest.scala
+++ b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/QueueDirectivesWithContextPathTest.scala
@@ -24,12 +24,12 @@ class QueueDirectivesWithContextPathTest
     with Directives
     with QueueDirectives
     with ContextPathModule
-    with AnyParamDirectives
     with QueueManagerActorModule
     with FutureDirectives
     with ActorSystemModule
     with ExceptionDirectives
-    with RespondDirectives {
+    with RespondDirectives
+    with AWSProtocolDirectives {
 
   private val maxDuration = 1.minute
   implicit val timeout: Timeout = maxDuration
@@ -39,7 +39,7 @@ class QueueDirectivesWithContextPathTest
     actorSystem.actorOf(Props(new QueueManagerActor(new NowProvider(), StrictSQSLimits, None)))
   lazy val contextPath = "/test-context"
 
-  "queueActorAndNameFromRequest" should "return correct queue name based on QueueName" in {
+  "queueActorAndNameFromUrl" should "return correct queue name based on QueueName" in {
     val future = queueManagerActor ? CreateQueue(
       CreateQueueData.from(
         QueueData("lol", MillisVisibilityTimeout(1L), Duration.ZERO, Duration.ZERO, DateTime.now(), DateTime.now())
@@ -47,11 +47,8 @@ class QueueDirectivesWithContextPathTest
     )
     Await.result(future, maxDuration)
     val route = {
-      queueActorAndNameFromRequest(
-        Map(
-          "QueueName" -> "lol",
-          "QueueUrl" -> "https://eu-central-1.queue.amazonaws.com/test-context/906175111765/lol"
-        )
+      queueActorAndNameFromUrl(
+        "https://eu-central-1.queue.amazonaws.com/test-context/906175111765/lol"
       ) { (_, name) => _.complete(name) }
     }
 
@@ -60,7 +57,7 @@ class QueueDirectivesWithContextPathTest
     }
   }
 
-  "queueActorAndNameFromRequest" should "return correct queue name based on QueueUrl" in {
+  "queueActorAndNameFromUrl" should "return correct queue name based on QueueUrl" in {
     val future = queueManagerActor ? CreateQueue(
       CreateQueueData.from(
         QueueData("lol", MillisVisibilityTimeout(1L), Duration.ZERO, Duration.ZERO, DateTime.now(), DateTime.now())
@@ -68,8 +65,8 @@ class QueueDirectivesWithContextPathTest
     )
     Await.result(future, maxDuration)
     val route = {
-      queueActorAndNameFromRequest(
-        Map("QueueUrl" -> "https://eu-central-1.queue.amazonaws.com/test-context/906175111765/lol")
+      queueActorAndNameFromUrl(
+        "https://eu-central-1.queue.amazonaws.com/test-context/906175111765/lol"
       ) { (_, name) => _.complete(name) }
     }
 
diff --git a/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/UnmatchedActionRoutesTest.scala b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/UnmatchedActionRoutesTest.scala
index d1fe5912..180be50c 100644
--- a/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/UnmatchedActionRoutesTest.scala
+++ b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/sqs/directives/UnmatchedActionRoutesTest.scala
@@ -3,6 +3,8 @@ package org.elasticmq.rest.sqs.directives
 import akka.http.scaladsl.model.StatusCodes.BadRequest
 import akka.http.scaladsl.server.Directives
 import akka.http.scaladsl.testkit.ScalatestRouteTest
+import akka.util.ByteString
+import org.elasticmq.rest.sqs.model.RequestPayload
 import org.scalatest.flatspec.AnyFlatSpec
 import org.scalatest.matchers.should.Matchers
 
@@ -13,12 +15,15 @@ class UnmatchedActionRoutesTest
     with Directives
     with RespondDirectives
     with UnmatchedActionRoutes
-    with ExceptionDirectives {
+    with ExceptionDirectives
+    with AWSProtocolDirectives {
 
   "handleUnmatchedAction" should "return invalid action error if an action is unknown" in {
     val route = {
-      handleServerExceptions {
-        unmatchedAction(Map("Action" -> "Whatever"))
+      extractProtocol { protocol =>
+        handleServerExceptions(protocol) {
+          unmatchedAction(RequestPayload.QueryParams(Map("Action" -> "Whatever")))
+        }
       }
     }
 
@@ -26,12 +31,15 @@ class UnmatchedActionRoutesTest
       status shouldBe BadRequest
       responseAs[String] should include("<Code>InvalidAction</Code>")
     }
+
   }
 
   it should "return invalid action error if an action is empty" in {
     val route = {
-      handleServerExceptions {
-        unmatchedAction(Map("Action" -> ""))
+      extractProtocol { protocol =>
+        handleServerExceptions(protocol) {
+          unmatchedAction(RequestPayload.QueryParams(Map("Action" -> "")))
+        }
       }
     }
 
@@ -43,8 +51,10 @@ class UnmatchedActionRoutesTest
 
   it should "return missing action error if there's no action" in {
     val route = {
-      handleServerExceptions {
-        unmatchedAction(Map.empty)
+      extractProtocol { protocol =>
+        handleServerExceptions(protocol) {
+          unmatchedAction(RequestPayload.QueryParams(Map.empty))
+        }
       }
     }
 
@@ -52,5 +62,10 @@ class UnmatchedActionRoutesTest
       status shouldBe BadRequest
       responseAs[String] should include("<Code>MissingAction</Code>")
     }
+
+    Post("/").withEntity(AmzJsonProtocol.contentType("1.0"), ByteString.empty) ~> route ~> check {
+      status shouldBe BadRequest
+      responseAs[String] should include(""""Message":"MissingAction; see the SQS docs."""")
+    }
   }
 }
diff --git a/rest/rest-sqs/src/test/scala/org/elasticmq/rest/stats/StatisticsDirectivesTest.scala b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/stats/StatisticsDirectivesTest.scala
index 1c6e14f3..2f27e675 100644
--- a/rest/rest-sqs/src/test/scala/org/elasticmq/rest/stats/StatisticsDirectivesTest.scala
+++ b/rest/rest-sqs/src/test/scala/org/elasticmq/rest/stats/StatisticsDirectivesTest.scala
@@ -25,7 +25,6 @@ class StatisticsDirectivesTest
     with ScalatestRouteTest
     with Directives
     with QueueDirectives
-    with AnyParamDirectives
     with QueueManagerActorModule
     with FutureDirectives
     with ActorSystemModule