diff --git a/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt b/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt
index 11f3338..9a99f0b 100644
--- a/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt
+++ b/src/test/kotlin/org/prebid/cache/functional/GeneralCacheSpec.kt
@@ -17,7 +17,6 @@ import org.prebid.cache.functional.util.getRandomLong
 import org.prebid.cache.functional.util.getRandomString
 import org.prebid.cache.functional.util.getRandomUuid
 import org.springframework.http.HttpStatus.BAD_REQUEST
-import org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR
 import org.springframework.http.HttpStatus.UNSUPPORTED_MEDIA_TYPE
 
 class GeneralCacheSpec : ShouldSpec({
diff --git a/src/test/kotlin/org/prebid/cache/functional/ModuleStorageSpec.kt b/src/test/kotlin/org/prebid/cache/functional/ModuleStorageSpec.kt
new file mode 100644
index 0000000..57ca7d3
--- /dev/null
+++ b/src/test/kotlin/org/prebid/cache/functional/ModuleStorageSpec.kt
@@ -0,0 +1,279 @@
+package org.prebid.cache.functional
+
+import io.kotest.assertions.assertSoftly
+import io.kotest.assertions.throwables.shouldThrowExactly
+import io.kotest.core.spec.style.ShouldSpec
+import io.kotest.matchers.nulls.beNull
+import io.kotest.matchers.shouldBe
+import io.kotest.matchers.string.beEmpty
+import io.kotest.matchers.string.shouldContain
+import java.util.Locale
+import org.prebid.cache.functional.BaseSpec.Companion.prebidCacheConfig
+import org.prebid.cache.functional.model.request.PayloadTransfer
+import org.prebid.cache.functional.service.ApiException
+import org.prebid.cache.functional.service.PrebidCacheApi
+import org.prebid.cache.functional.util.getRandomString
+import org.springframework.http.HttpStatus.BAD_REQUEST
+import org.springframework.http.HttpStatus.NOT_FOUND
+import org.springframework.http.HttpStatus.UNAUTHORIZED
+
+class ModuleStorageSpec : ShouldSpec({
+
+    lateinit var apiKey: String
+    lateinit var applicationName: String
+    lateinit var cacheApi: PrebidCacheApi
+
+    beforeSpec {
+        apiKey = getRandomString()
+        applicationName = getRandomString().lowercase(Locale.getDefault())
+        val config = prebidCacheConfig.getBaseModuleStorageConfig(applicationName, apiKey)
+        cacheApi = BaseSpec.getPrebidCacheApi(config)
+    }
+
+    should("return the same text transfer value which was saved to module-storage") {
+        //given: default text payload with application
+        val payloadKey = getRandomString()
+        val payloadTransfer = PayloadTransfer.getDefaultTextPayloadTransfer().apply {
+            key = payloadKey
+            application = applicationName
+        }
+
+        // when: POST module-storage endpoint is called
+        cacheApi.postModuleStorageCache(payloadTransfer, apiKey)
+
+        // then: recorded payload should contain the same type and value
+        val savedPayload = cacheApi.getModuleStorageCache(payloadKey, applicationName, apiKey)
+        savedPayload.type shouldBe payloadTransfer.type
+        savedPayload.value shouldBe payloadTransfer.value
+
+        // and: shouldn't contain information about application
+        savedPayload.application?.should(beNull())
+    }
+
+    should("return the same xml transfer value which was saved to module-storage") {
+        //given: default xml payload with application
+        val payloadKey = getRandomString()
+        val payloadTransfer = PayloadTransfer.getDefaultXmlPayloadTransfer().apply {
+            key = payloadKey
+            application = applicationName
+        }
+
+        // when: POST module-storage endpoint is called
+        cacheApi.postModuleStorageCache(payloadTransfer, apiKey)
+
+        // then: recorded payload should contain the same type and value
+        val savedPayload = cacheApi.getModuleStorageCache(payloadKey, applicationName, apiKey)
+        savedPayload.type shouldBe payloadTransfer.type
+        savedPayload.value shouldBe payloadTransfer.value
+
+        // and: shouldn't contain information about application
+        savedPayload.application?.should(beNull())
+    }
+
+    should("return the same json transfer value which was saved to module-storage") {
+        //given: default json payload with application
+        val payloadKey = getRandomString()
+        val payloadTransfer = PayloadTransfer.getDefaultJsonPayloadTransfer().apply {
+            key = payloadKey
+            application = applicationName
+        }
+
+        // when: POST module-storage endpoint is called
+        cacheApi.postModuleStorageCache(payloadTransfer, apiKey)
+
+        // then: recorded payload should contain the same type and value
+        val savedPayload = cacheApi.getModuleStorageCache(payloadKey, applicationName, apiKey)
+        savedPayload.type shouldBe payloadTransfer.type
+        savedPayload.value shouldBe payloadTransfer.value
+
+        // and: shouldn't contain information about application
+        savedPayload.application?.should(beNull())
+    }
+
+    should("throw an exception when post request have nonexistent PBC application name") {
+        //given: default text payload with nonexistent application name
+        val payloadKey = getRandomString()
+        val randomApplication = getRandomString()
+        val payloadTransfer = PayloadTransfer.getDefaultTextPayloadTransfer().apply {
+            key = payloadKey
+            application = randomApplication
+        }
+
+        // when: POST module-storage endpoint is called
+        val exception = shouldThrowExactly<ApiException> {
+            cacheApi.postModuleStorageCache(payloadTransfer, apiKey) }
+
+        // then: Not found exception is thrown
+        assertSoftly {
+            exception.statusCode shouldBe NOT_FOUND.value()
+            exception.responseBody shouldContain "\"path\":\"/module-storage\""
+            exception.responseBody shouldContain "\"message\":\"Invalid application: ${randomApplication}\""
+        }
+    }
+
+    should("throw an exception when post request have null application name") {
+        //given: default text payload with null application name
+        val payloadKey = getRandomString()
+        val payloadTransfer = PayloadTransfer.getDefaultTextPayloadTransfer().apply {
+            key = payloadKey
+            application = null
+        }
+
+        // when: POST module-storage endpoint is called
+        val exception = shouldThrowExactly<ApiException> { cacheApi.postModuleStorageCache(payloadTransfer, apiKey) }
+
+        // then: Bad request exception is thrown
+        assertSoftly {
+            exception.statusCode shouldBe BAD_REQUEST.value()
+            exception.responseBody shouldContain "\"path\":\"/module-storage\""
+            exception.responseBody shouldContain "application must not be empty"
+        }
+    }
+
+    should("throw an exception when post request have empty application name") {
+        //given: default text payload with empty application name
+        val payloadKey = getRandomString()
+        val payloadTransfer = PayloadTransfer.getDefaultTextPayloadTransfer().apply {
+            key = payloadKey
+            application = ""
+        }
+
+        // when: POST module-storage endpoint is called
+        val exception = shouldThrowExactly<ApiException> { cacheApi.postModuleStorageCache(payloadTransfer, apiKey) }
+
+        // then: Bad request exception is thrown
+        assertSoftly {
+            exception.statusCode shouldBe BAD_REQUEST.value()
+            exception.responseBody shouldContain "\"path\":\"/module-storage\""
+            exception.responseBody shouldContain "application must not be empty"
+        }
+    }
+
+    should("throw an exception when post request have null key name") {
+        //given: default text payload with empty payloadKey
+        val payloadTransfer = PayloadTransfer.getDefaultTextPayloadTransfer().apply {
+            key = null
+            application = applicationName
+        }
+
+        // when: POST module-storage endpoint is called
+        val exception = shouldThrowExactly<ApiException> { cacheApi.postModuleStorageCache(payloadTransfer, apiKey) }
+
+        // then: Bad request exception is thrown
+        assertSoftly {
+            exception.statusCode shouldBe BAD_REQUEST.value()
+            exception.responseBody shouldContain "\"path\":\"/module-storage\""
+            exception.responseBody shouldContain "key must not be empty"
+        }
+    }
+
+    should("throw an exception when post request have empty key name") {
+        //given: default text payload with empty payloadKey
+        val payloadTransfer = PayloadTransfer.getDefaultTextPayloadTransfer().apply {
+            key = ""
+            application = applicationName
+        }
+
+        // when: POST module-storage endpoint is called
+        val exception = shouldThrowExactly<ApiException> { cacheApi.postModuleStorageCache(payloadTransfer, apiKey) }
+
+        // then: Bad request exception is thrown
+        assertSoftly {
+            exception.statusCode shouldBe BAD_REQUEST.value()
+            exception.responseBody shouldContain "\"path\":\"/module-storage\""
+            exception.responseBody shouldContain "key must not be empty"
+        }
+    }
+
+    should("throw an exception when post request have invalid PBC apiKey") {
+        //given: default text payload with application
+        val payloadKey = getRandomString()
+        val payloadTransfer = PayloadTransfer.getDefaultTextPayloadTransfer().apply {
+            key = payloadKey
+            application = applicationName
+        }
+
+        // when: POST module-storage endpoint is called
+        val exception =
+            shouldThrowExactly<ApiException> { cacheApi.postModuleStorageCache(payloadTransfer, getRandomString()) }
+
+        // then: Not found exception is thrown
+        assertSoftly {
+            exception.statusCode shouldBe UNAUTHORIZED.value()
+            exception.responseBody should beEmpty()
+        }
+    }
+
+    should("throw an exception when get request contain invalid payload key") {
+        //given: default text payload with application
+        val payloadTransfer = PayloadTransfer.getDefaultTextPayloadTransfer().apply {
+            key = getRandomString()
+            application = applicationName
+        }
+
+        // and: POST module-storage endpoint is called
+        cacheApi.postModuleStorageCache(payloadTransfer, apiKey)
+
+        // when: GET module-storage endpoint is called with invalid data
+        val exception = shouldThrowExactly<ApiException> {
+            cacheApi.getModuleStorageCache(getRandomString(), applicationName, apiKey)
+        }
+
+        // then: Not found exception is thrown
+        assertSoftly {
+            exception.statusCode shouldBe NOT_FOUND.value()
+            exception.responseBody shouldContain "\"path\":\"/module-storage\""
+            exception.responseBody shouldContain "Invalid application or key"
+        }
+    }
+
+    should("throw an exception when get request contain invalid application name") {
+        //given: default text payload with application
+        val payloadKey = getRandomString()
+        val payloadTransfer = PayloadTransfer.getDefaultTextPayloadTransfer().apply {
+            key = payloadKey
+            application = applicationName
+        }
+
+        // and: POST module-storage endpoint is called
+        cacheApi.postModuleStorageCache(payloadTransfer, apiKey)
+
+        //and: random application name
+        val randomApplication = getRandomString()
+
+        // when: GET module-storage endpoint is called with invalid data
+        val exception = shouldThrowExactly<ApiException> {
+            cacheApi.getModuleStorageCache(payloadKey, randomApplication, apiKey)
+        }
+
+        // then: Not found exception is thrown
+        assertSoftly {
+            exception.statusCode shouldBe NOT_FOUND.value()
+            exception.responseBody shouldContain "\"path\":\"/module-storage\""
+            exception.responseBody shouldContain "\"message\":\"Invalid application: ${randomApplication}\""
+        }
+    }
+
+    should("throw an exception when get request contain invalid apiKey") {
+        //given: default text payload with application
+        val payloadKey = getRandomString()
+        val payloadTransfer = PayloadTransfer.getDefaultTextPayloadTransfer().apply {
+            key = payloadKey
+            application = applicationName
+        }
+
+        // and: POST module-storage endpoint is called
+        cacheApi.postModuleStorageCache(payloadTransfer, apiKey)
+
+        // when: GET module-storage endpoint is called with invalid data
+        val exception = shouldThrowExactly<ApiException> {
+            cacheApi.getModuleStorageCache(payloadKey, applicationName, getRandomString())
+        }
+
+        // then: Not found exception is thrown
+        assertSoftly {
+            exception.statusCode shouldBe UNAUTHORIZED.value()
+            exception.responseBody should beEmpty()
+        }
+    }
+})
diff --git a/src/test/kotlin/org/prebid/cache/functional/model/request/MediaType.kt b/src/test/kotlin/org/prebid/cache/functional/model/request/MediaType.kt
index 4afba48..1aee478 100644
--- a/src/test/kotlin/org/prebid/cache/functional/model/request/MediaType.kt
+++ b/src/test/kotlin/org/prebid/cache/functional/model/request/MediaType.kt
@@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonValue
 
 enum class MediaType {
 
-    JSON, XML, UNSUPPORTED;
+    JSON, XML, TEXT, UNSUPPORTED;
 
     @JsonValue
     fun getValue(): String = name.lowercase()
diff --git a/src/test/kotlin/org/prebid/cache/functional/model/request/PayloadTransfer.kt b/src/test/kotlin/org/prebid/cache/functional/model/request/PayloadTransfer.kt
index 259614c..2c71d3d 100644
--- a/src/test/kotlin/org/prebid/cache/functional/model/request/PayloadTransfer.kt
+++ b/src/test/kotlin/org/prebid/cache/functional/model/request/PayloadTransfer.kt
@@ -4,7 +4,9 @@ import com.fasterxml.jackson.annotation.JsonInclude
 import com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL
 import org.prebid.cache.functional.mapper.objectMapper
 import org.prebid.cache.functional.model.request.MediaType.JSON
+import org.prebid.cache.functional.model.request.MediaType.TEXT
 import org.prebid.cache.functional.model.request.MediaType.XML
+import org.prebid.cache.functional.util.getRandomString
 
 @JsonInclude(NON_NULL)
 data class PayloadTransfer(
@@ -14,6 +16,7 @@ data class PayloadTransfer(
     var expiry: Long? = null,
     var ttlseconds: Long? = null,
     var prefix: String? = null,
+    var application: String? = null,
 
     //Fields not affected on PBC
     var timestamp: Long? = null,
@@ -23,18 +26,18 @@ data class PayloadTransfer(
 ) {
 
     companion object {
+        private const val DEFAULT_EXPIRY = 300L
+
+        private fun getDefaultPayloadTransfer(type: MediaType, value: String): PayloadTransfer =
+            PayloadTransfer(type = type, value = value, expiry = DEFAULT_EXPIRY, ttlseconds = 3000L)
+
         fun getDefaultJsonPayloadTransfer(): PayloadTransfer =
-            PayloadTransfer(
-                type = JSON,
-                value = objectMapper.writeValueAsString(TransferValue.getDefaultJsonValue()),
-                expiry = 300
-            )
+            getDefaultPayloadTransfer(JSON, objectMapper.writeValueAsString(TransferValue.getDefaultJsonValue()))
 
         fun getDefaultXmlPayloadTransfer(): PayloadTransfer =
-            PayloadTransfer(
-                type = XML,
-                value = objectMapper.writeValueAsString(TransferValue.getDefaultXmlValue()),
-                expiry = 300
-            )
+            getDefaultPayloadTransfer(XML, objectMapper.writeValueAsString(TransferValue.getDefaultXmlValue()))
+
+        fun getDefaultTextPayloadTransfer(): PayloadTransfer =
+            getDefaultPayloadTransfer(TEXT, getRandomString())
     }
 }
diff --git a/src/test/kotlin/org/prebid/cache/functional/model/request/RequestObject.kt b/src/test/kotlin/org/prebid/cache/functional/model/request/RequestObject.kt
index 2d08fb8..b027194 100644
--- a/src/test/kotlin/org/prebid/cache/functional/model/request/RequestObject.kt
+++ b/src/test/kotlin/org/prebid/cache/functional/model/request/RequestObject.kt
@@ -11,5 +11,8 @@ data class RequestObject(var puts: List<PayloadTransfer>) {
 
         fun getDefaultXmlRequestObject(): RequestObject =
             RequestObject(puts = listOf(PayloadTransfer.getDefaultXmlPayloadTransfer()))
+
+        fun getDefaultTextRequestObject(): RequestObject =
+            RequestObject(puts = listOf(PayloadTransfer.getDefaultTextPayloadTransfer()))
     }
 }
diff --git a/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt b/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt
index b164ffe..67c21f0 100644
--- a/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt
+++ b/src/test/kotlin/org/prebid/cache/functional/service/PrebidCacheApi.kt
@@ -16,12 +16,38 @@ import io.ktor.client.statement.HttpResponse
 import io.ktor.client.statement.bodyAsText
 import io.ktor.http.ContentType.Application.Json
 import io.ktor.http.HttpHeaders.ContentType
+import io.ktor.http.HttpStatusCode
 import io.ktor.serialization.jackson.jackson
+import org.prebid.cache.functional.model.request.PayloadTransfer
 import org.prebid.cache.functional.model.request.RequestObject
 import org.prebid.cache.functional.model.response.ResponseObject
 
 class PrebidCacheApi(prebidCacheHost: String, prebidCachePort: Int) {
 
+    suspend fun getCache(uuid: String?, proxyCacheHost: String? = null): HttpResponse =
+        get(CACHE_ENDPOINT, mapOf(UUID_QUERY_PARAMETER to uuid, PROXY_CACHE_HOST_QUERY_PARAMETER to proxyCacheHost))
+
+    suspend fun postCache(requestObject: RequestObject, secondaryCache: String? = null): ResponseObject =
+        post(CACHE_ENDPOINT, requestObject, mapOf(SECONDARY_CACHE_QUERY_PARAMETER to secondaryCache)).body()
+
+    suspend fun getModuleStorageCache(
+        payloadTransferKey: String?,
+        application: String?,
+        apiKey: String?
+    ): PayloadTransfer =
+        get(
+            MODULE_STORAGE_ENDPOINT,
+            mapOf(KEY_PARAMETER to payloadTransferKey, APPLICATION_PARAMETER to application),
+            mapOf(API_KEY_PARAMETER to apiKey)
+        ).body()
+
+    suspend fun postModuleStorageCache(requestObject: PayloadTransfer, apiKey: String? = null): Boolean =
+        post(
+            MODULE_STORAGE_ENDPOINT,
+            requestObject,
+            headers = mapOf(API_KEY_PARAMETER to apiKey)
+        ).status == HttpStatusCode.NoContent
+
     private val client = HttpClient(Apache) {
         expectSuccess = true
         defaultRequest {
@@ -41,22 +67,44 @@ class PrebidCacheApi(prebidCacheHost: String, prebidCachePort: Int) {
         }
     }
 
-    suspend fun getCache(uuid: String?, proxyCacheHost: String? = null): HttpResponse =
-        client.get(CACHE_ENDPOINT) {
-            if (uuid != null) parameter(UUID_QUERY_PARAMETER, uuid)
-            if (proxyCacheHost != null) parameter(PROXY_CACHE_HOST_QUERY_PARAMETER, proxyCacheHost)
+    private suspend fun get(
+        endpoint: String,
+        parameters: Map<String, String?> = emptyMap(),
+        headers: Map<String, String?> = emptyMap()
+    ): HttpResponse = client.get(endpoint) {
+        parameters.forEach { (key, value) ->
+            value?.let { parameter(key, value) }
         }
+        headers.forEach { (key, value) ->
+            value?.let { header(key, value) }
+        }
+    }
 
-    suspend fun postCache(requestObject: RequestObject, secondaryCache: String? = null): ResponseObject =
-        client.post(CACHE_ENDPOINT) {
-            if (secondaryCache != null) parameter(SECONDARY_CACHE_QUERY_PARAMETER, secondaryCache)
+    private suspend fun post(
+        endpoint: String,
+        requestObject: Any,
+        parameters: Map<String, String?> = emptyMap(),
+        headers: Map<String, String?> = emptyMap()
+    ): HttpResponse =
+        client.post(endpoint) {
             setBody(requestObject)
-        }.body()
+            parameters.forEach { (key, value) ->
+                value?.let { parameter(key, value) }
+            }
+            headers.forEach { (key, value) ->
+                value?.let { header(key, value) }
+            }
+        }
 
     companion object {
         private const val CACHE_ENDPOINT = "/cache"
         private const val UUID_QUERY_PARAMETER = "uuid"
         private const val PROXY_CACHE_HOST_QUERY_PARAMETER = "ch"
         private const val SECONDARY_CACHE_QUERY_PARAMETER = "secondaryCache"
+
+        private const val MODULE_STORAGE_ENDPOINT = "/module-storage"
+        private const val API_KEY_PARAMETER = "x-pbc-api-key"
+        private const val KEY_PARAMETER = "k"
+        private const val APPLICATION_PARAMETER = "a"
     }
 }
diff --git a/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt b/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt
index 8ad125e..9fb389f 100644
--- a/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt
+++ b/src/test/kotlin/org/prebid/cache/functional/testcontainers/PrebidCacheContainerConfig.kt
@@ -13,6 +13,9 @@ class PrebidCacheContainerConfig(private val redisHost: String, private val aero
     fun getBaseAerospikeConfig(allowExternalUuid: String, aerospikeNamespace: String = NAMESPACE): Map<String, String> =
         getBaseConfig(allowExternalUuid) + getAerospikeConfig(aerospikeNamespace)
 
+    fun getBaseModuleStorageConfig(applicationName: String, apiKey: String): Map<String, String> =
+        getBaseConfig("true") + getModuleStorageRedisConfig(apiKey, applicationName) + getRedisConfig()
+
     fun getCacheExpiryConfig(minExpiry: String = "15", maxExpiry: String = "28800"): Map<String, String> =
         mapOf(
             "cache.min.expiry" to minExpiry,
@@ -57,11 +60,22 @@ class PrebidCacheContainerConfig(private val redisHost: String, private val aero
             "spring.aerospike.namespace" to aerospikeNamespace
         )
 
+    private fun getModuleStorageRedisConfig(
+        apiKey: String,
+        applicationName: String,
+        timeoutMs: Long = 9999L
+    ): Map<String, String> =
+        mapOf(
+            "api.api-key" to apiKey,
+            "module.storage.redis.${applicationName}.port" to RedisContainer.PORT.toString(),
+            "module.storage.redis.${applicationName}.host" to redisHost,
+            "module.storage.redis.${applicationName}.timeout" to timeoutMs.toString(),
+        )
+
     private fun getBaseConfig(allowExternalUuid: String): Map<String, String> =
         getCachePrefixConfig() + getCacheExpiryConfig() + getAllowExternalUuidConfig(allowExternalUuid) +
                 getCacheTimeoutConfig("2500")
 
-
     private fun getCachePrefixConfig(): Map<String, String> = mapOf("cache.prefix" to "prebid_")
 
     private fun getAllowExternalUuidConfig(allowExternalUuid: String): Map<String, String> =
