diff --git a/cmd/policy/main.go b/cmd/policy/main.go
index 100261acdf3a11b0c346ead3548e52daecf52993..1eec5678d6967bcb8ff3652f59ed47e16b02db50 100644
--- a/cmd/policy/main.go
+++ b/cmd/policy/main.go
@@ -25,6 +25,7 @@ import (
 	goapolicysrv "code.vereign.com/gaiax/tsa/policy/gen/http/policy/server"
 	"code.vereign.com/gaiax/tsa/policy/gen/openapi"
 	goapolicy "code.vereign.com/gaiax/tsa/policy/gen/policy"
+	"code.vereign.com/gaiax/tsa/policy/internal/clients/cache"
 	"code.vereign.com/gaiax/tsa/policy/internal/config"
 	"code.vereign.com/gaiax/tsa/policy/internal/regocache"
 	"code.vereign.com/gaiax/tsa/policy/internal/regofunc"
@@ -65,6 +66,8 @@ func main() {
 	}
 	defer db.Disconnect(context.Background()) //nolint:errcheck
 
+	httpClient := httpClient()
+
 	// create storage
 	storage, err := storage.New(db, cfg.Mongo.DB, cfg.Mongo.Collection, logger)
 	if err != nil {
@@ -76,7 +79,6 @@ func main() {
 
 	// register rego extension functions
 	{
-		httpClient := httpClient()
 		cacheFuncs := regofunc.NewCacheFuncs(cfg.Cache.Addr, httpClient)
 		didResolverFuncs := regofunc.NewDIDResolverFuncs(cfg.DIDResolver.Addr, httpClient)
 		taskFuncs := regofunc.NewTaskFuncs(cfg.Task.Addr, httpClient)
@@ -90,13 +92,16 @@ func main() {
 	// subscribe the cache for policy data changes
 	storage.AddPolicyChangeSubscriber(regocache)
 
+	// create cache client
+	cache := cache.New(cfg.Cache.Addr, cache.WithHTTPClient(httpClient))
+
 	// create services
 	var (
 		policySvc goapolicy.Service
 		healthSvc goahealth.Service
 	)
 	{
-		policySvc = policy.New(storage, regocache, logger)
+		policySvc = policy.New(storage, regocache, cache, logger)
 		healthSvc = health.New()
 	}
 
diff --git a/design/types.go b/design/types.go
index 3c83c2c64b5e35d1746b72ce1dbdbc9d4192ff44..26996205e55069980fe530a538f7b7f0e66f86c6 100644
--- a/design/types.go
+++ b/design/types.go
@@ -4,9 +4,15 @@ package design
 import . "goa.design/goa/v3/dsl"
 
 var EvaluateRequest = Type("EvaluateRequest", func() {
-	Field(1, "group", String, "Policy group.")
-	Field(2, "policyName", String, "Policy name.")
-	Field(3, "version", String, "Policy version.")
+	Field(1, "group", String, "Policy group.", func() {
+		Example("example")
+	})
+	Field(2, "policyName", String, "Policy name.", func() {
+		Example("example")
+	})
+	Field(3, "version", String, "Policy version.", func() {
+		Example("1.0")
+	})
 	Field(4, "input", Any, "Input data passed to the policy execution runtime.")
 	Required("group", "policyName", "version", "input")
 })
diff --git a/gen/http/cli/policy/cli.go b/gen/http/cli/policy/cli.go
index ca3c7841a6acdfe4e3a1c21494ad084dac3d3e6f..431940373d2895bc45f21758efb31617747abb04 100644
--- a/gen/http/cli/policy/cli.go
+++ b/gen/http/cli/policy/cli.go
@@ -32,7 +32,7 @@ policy (evaluate|lock|unlock)
 // UsageExamples produces an example of a valid invocation of the CLI tool.
 func UsageExamples() string {
 	return os.Args[0] + ` health liveness` + "\n" +
-		os.Args[0] + ` policy evaluate --body "Similique quisquam optio." --group "Repellat velit omnis." --policy-name "Vitae qui." --version "Provident fugiat at cupiditate."` + "\n" +
+		os.Args[0] + ` policy evaluate --body "Ab accusamus voluptatem et est." --group "example" --policy-name "example" --version "1.0"` + "\n" +
 		""
 }
 
@@ -247,7 +247,7 @@ Evaluate executes a policy with the given 'data' as input.
     -version STRING: Policy version.
 
 Example:
-    %[1]s policy evaluate --body "Similique quisquam optio." --group "Repellat velit omnis." --policy-name "Vitae qui." --version "Provident fugiat at cupiditate."
+    %[1]s policy evaluate --body "Ab accusamus voluptatem et est." --group "example" --policy-name "example" --version "1.0"
 `, os.Args[0])
 }
 
@@ -260,7 +260,7 @@ Lock a policy so that it cannot be evaluated.
     -version STRING: Policy version.
 
 Example:
-    %[1]s policy lock --group "Deleniti non nihil dolor aut sed." --policy-name "Incidunt unde consequatur voluptas dolorem nisi temporibus." --version "Omnis quasi aut consequuntur."
+    %[1]s policy lock --group "Vitae qui." --policy-name "Provident fugiat at cupiditate." --version "Commodi vitae voluptatem."
 `, os.Args[0])
 }
 
@@ -273,6 +273,6 @@ Unlock a policy so it can be evaluated again.
     -version STRING: Policy version.
 
 Example:
-    %[1]s policy unlock --group "Aut facere veniam repudiandae id." --policy-name "Aut minus alias." --version "At eos facilis molestias in voluptas rem."
+    %[1]s policy unlock --group "Aut ut fuga quae eius minus." --policy-name "Architecto quibusdam ab." --version "In illum est et hic."
 `, os.Args[0])
 }
diff --git a/gen/http/openapi3.json b/gen/http/openapi3.json
index 157c1dd6d45193244c4688d15e7c1e0f5dfcda32..87f8be3859ea0679e8151b3d3e9cd10c22973d63 100644
--- a/gen/http/openapi3.json
+++ b/gen/http/openapi3.json
@@ -1 +1 @@
-{"openapi":"3.0.3","info":{"title":"Policy Service","description":"The policy service exposes HTTP API for executing policies.","version":"1.0"},"servers":[{"url":"http://localhost:8081","description":"Policy Server"}],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}}}},"/policy/{group}/{policyName}/{version}/evaluation":{"post":{"tags":["policy"],"summary":"Evaluate policy","description":"Evaluate executes a policy with the given 'data' as input.","operationId":"policy#Evaluate","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"Sint nam voluptatem ea consequatur similique et."},"example":"Non mollitia nesciunt impedit facere."},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"Ut commodi perspiciatis corporis."},"example":"Accusamus autem sequi."},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"Et nulla."},"example":"In quis nesciunt autem et."}],"requestBody":{"description":"Input data passed to the policy execution runtime.","required":true,"content":{"application/json":{"schema":{"type":"string","description":"Input data passed to the policy execution runtime.","example":"Ab accusantium ut ut aliquid sint animi.","format":"binary"},"example":"Aut voluptas."}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Dolorem cumque laborum quis nesciunt.","format":"binary"},"example":"Sunt in et quia cum."}}}}}},"/policy/{group}/{policyName}/{version}/lock":{"delete":{"tags":["policy"],"summary":"Unlock policy","description":"Unlock a policy so it can be evaluated again.","operationId":"policy#Unlock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"Accusamus enim."},"example":"Recusandae est rerum corrupti quia."},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"Quam dolores architecto itaque."},"example":"Voluptas ad corporis adipisci inventore ipsum."},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"Recusandae dolorum nisi distinctio vitae ad."},"example":"Perspiciatis voluptatem."}],"responses":{"200":{"description":"OK response."}}},"post":{"tags":["policy"],"summary":"Lock policy","description":"Lock a policy so that it cannot be evaluated.","operationId":"policy#Lock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"Commodi nemo fugiat id praesentium accusantium expedita."},"example":"Qui non quia."},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"Error maxime quasi quia non voluptatibus error."},"example":"Optio quia et laborum."},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"In libero perspiciatis voluptatum ut soluta."},"example":"Ut amet."}],"responses":{"200":{"description":"OK response."}}}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}}}}},"components":{},"tags":[{"name":"health","description":"Health service provides health check endpoints."},{"name":"policy","description":"Policy Service provides evaluation of policies through Open Policy Agent."}]}
\ No newline at end of file
+{"openapi":"3.0.3","info":{"title":"Policy Service","description":"The policy service exposes HTTP API for executing policies.","version":"1.0"},"servers":[{"url":"http://localhost:8081","description":"Policy Server"}],"paths":{"/liveness":{"get":{"tags":["health"],"summary":"Liveness health","operationId":"health#Liveness","responses":{"200":{"description":"OK response."}}}},"/policy/{group}/{policyName}/{version}/evaluation":{"post":{"tags":["policy"],"summary":"Evaluate policy","description":"Evaluate executes a policy with the given 'data' as input.","operationId":"policy#Evaluate","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"example"},"example":"example"},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"example"},"example":"example"},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"1.0"},"example":"1.0"}],"requestBody":{"description":"Input data passed to the policy execution runtime.","required":true,"content":{"application/json":{"schema":{"type":"string","description":"Input data passed to the policy execution runtime.","example":"Deleniti non nihil dolor aut sed.","format":"binary"},"example":"Omnis quasi aut consequuntur."}}},"responses":{"200":{"description":"OK response.","content":{"application/json":{"schema":{"type":"string","example":"Incidunt unde consequatur voluptas dolorem nisi temporibus.","format":"binary"},"example":"Tempore minus."}}}}}},"/policy/{group}/{policyName}/{version}/lock":{"delete":{"tags":["policy"],"summary":"Unlock policy","description":"Unlock a policy so it can be evaluated again.","operationId":"policy#Unlock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"Dolorem cumque laborum quis nesciunt."},"example":"Aut voluptas."},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"Sint nam voluptatem ea consequatur similique et."},"example":"Non mollitia nesciunt impedit facere."},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"Ut commodi perspiciatis corporis."},"example":"Accusamus autem sequi."}],"responses":{"200":{"description":"OK response."}}},"post":{"tags":["policy"],"summary":"Lock policy","description":"Lock a policy so that it cannot be evaluated.","operationId":"policy#Lock","parameters":[{"name":"group","in":"path","description":"Policy group.","required":true,"schema":{"type":"string","description":"Policy group.","example":"Quis quos qui earum velit illum."},"example":"Aliquam atque voluptatum ut dolorem."},{"name":"policyName","in":"path","description":"Policy name.","required":true,"schema":{"type":"string","description":"Policy name.","example":"Aut facere veniam repudiandae id."},"example":"Aut minus alias."},{"name":"version","in":"path","description":"Policy version.","required":true,"schema":{"type":"string","description":"Policy version.","example":"At eos facilis molestias in voluptas rem."},"example":"Ab accusantium ut ut aliquid sint animi."}],"responses":{"200":{"description":"OK response."}}}},"/readiness":{"get":{"tags":["health"],"summary":"Readiness health","operationId":"health#Readiness","responses":{"200":{"description":"OK response."}}}}},"components":{},"tags":[{"name":"health","description":"Health service provides health check endpoints."},{"name":"policy","description":"Policy Service provides evaluation of policies through Open Policy Agent."}]}
\ No newline at end of file
diff --git a/gen/http/openapi3.yaml b/gen/http/openapi3.yaml
index f594026729efd68d14d29221bec6581f88a586e7..f5a2f231aada97499f8271846caddaf3886f66c1 100644
--- a/gen/http/openapi3.yaml
+++ b/gen/http/openapi3.yaml
@@ -31,8 +31,8 @@ paths:
         schema:
           type: string
           description: Policy group.
-          example: Sint nam voluptatem ea consequatur similique et.
-        example: Non mollitia nesciunt impedit facere.
+          example: example
+        example: example
       - name: policyName
         in: path
         description: Policy name.
@@ -40,8 +40,8 @@ paths:
         schema:
           type: string
           description: Policy name.
-          example: Ut commodi perspiciatis corporis.
-        example: Accusamus autem sequi.
+          example: example
+        example: example
       - name: version
         in: path
         description: Policy version.
@@ -49,8 +49,8 @@ paths:
         schema:
           type: string
           description: Policy version.
-          example: Et nulla.
-        example: In quis nesciunt autem et.
+          example: "1.0"
+        example: "1.0"
       requestBody:
         description: Input data passed to the policy execution runtime.
         required: true
@@ -59,9 +59,9 @@ paths:
             schema:
               type: string
               description: Input data passed to the policy execution runtime.
-              example: Ab accusantium ut ut aliquid sint animi.
+              example: Deleniti non nihil dolor aut sed.
               format: binary
-            example: Aut voluptas.
+            example: Omnis quasi aut consequuntur.
       responses:
         "200":
           description: OK response.
@@ -69,9 +69,9 @@ paths:
             application/json:
               schema:
                 type: string
-                example: Dolorem cumque laborum quis nesciunt.
+                example: Incidunt unde consequatur voluptas dolorem nisi temporibus.
                 format: binary
-              example: Sunt in et quia cum.
+              example: Tempore minus.
   /policy/{group}/{policyName}/{version}/lock:
     delete:
       tags:
@@ -87,8 +87,8 @@ paths:
         schema:
           type: string
           description: Policy group.
-          example: Accusamus enim.
-        example: Recusandae est rerum corrupti quia.
+          example: Dolorem cumque laborum quis nesciunt.
+        example: Aut voluptas.
       - name: policyName
         in: path
         description: Policy name.
@@ -96,8 +96,8 @@ paths:
         schema:
           type: string
           description: Policy name.
-          example: Quam dolores architecto itaque.
-        example: Voluptas ad corporis adipisci inventore ipsum.
+          example: Sint nam voluptatem ea consequatur similique et.
+        example: Non mollitia nesciunt impedit facere.
       - name: version
         in: path
         description: Policy version.
@@ -105,8 +105,8 @@ paths:
         schema:
           type: string
           description: Policy version.
-          example: Recusandae dolorum nisi distinctio vitae ad.
-        example: Perspiciatis voluptatem.
+          example: Ut commodi perspiciatis corporis.
+        example: Accusamus autem sequi.
       responses:
         "200":
           description: OK response.
@@ -124,8 +124,8 @@ paths:
         schema:
           type: string
           description: Policy group.
-          example: Commodi nemo fugiat id praesentium accusantium expedita.
-        example: Qui non quia.
+          example: Quis quos qui earum velit illum.
+        example: Aliquam atque voluptatum ut dolorem.
       - name: policyName
         in: path
         description: Policy name.
@@ -133,8 +133,8 @@ paths:
         schema:
           type: string
           description: Policy name.
-          example: Error maxime quasi quia non voluptatibus error.
-        example: Optio quia et laborum.
+          example: Aut facere veniam repudiandae id.
+        example: Aut minus alias.
       - name: version
         in: path
         description: Policy version.
@@ -142,8 +142,8 @@ paths:
         schema:
           type: string
           description: Policy version.
-          example: In libero perspiciatis voluptatum ut soluta.
-        example: Ut amet.
+          example: At eos facilis molestias in voluptas rem.
+        example: Ab accusantium ut ut aliquid sint animi.
       responses:
         "200":
           description: OK response.
diff --git a/gen/http/policy/client/cli.go b/gen/http/policy/client/cli.go
index 8b74da63e55e748a960e35ff3a512a48bb008aa1..931cc1ad0c56cd7647ed6924e700826f5f6b0c10 100644
--- a/gen/http/policy/client/cli.go
+++ b/gen/http/policy/client/cli.go
@@ -22,7 +22,7 @@ func BuildEvaluatePayload(policyEvaluateBody string, policyEvaluateGroup string,
 	{
 		err = json.Unmarshal([]byte(policyEvaluateBody), &body)
 		if err != nil {
-			return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "\"Similique quisquam optio.\"")
+			return nil, fmt.Errorf("invalid JSON for body, \nerror: %s, \nexample of valid JSON:\n%s", err, "\"Ab accusamus voluptatem et est.\"")
 		}
 	}
 	var group string
diff --git a/go.mod b/go.mod
index 6ebaaeedfa8b0d377ee0b3e173e7c7b7161e44c3..db4c411fdb0dcd0e9837a6aa74501a8f5f3ec80c 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.17
 
 require (
 	code.vereign.com/gaiax/tsa/golib v0.0.0-20220321093827-5fdf8f34aad9
+	github.com/google/uuid v1.3.0
 	github.com/kelseyhightower/envconfig v1.4.0
 	github.com/open-policy-agent/opa v0.38.1
 	github.com/stretchr/testify v1.7.0
@@ -22,7 +23,6 @@ require (
 	github.com/go-stack/stack v1.8.0 // indirect
 	github.com/gobwas/glob v0.2.3 // indirect
 	github.com/golang/snappy v0.0.4 // indirect
-	github.com/google/uuid v1.3.0 // indirect
 	github.com/gopherjs/gopherjs v0.0.0-20220221023154-0b2280d3ff96 // indirect
 	github.com/gorilla/websocket v1.5.0 // indirect
 	github.com/jtolds/gls v4.20.0+incompatible // indirect
diff --git a/internal/clients/cache/client.go b/internal/clients/cache/client.go
new file mode 100644
index 0000000000000000000000000000000000000000..7ad51dceaab26442febc5d8afffb29eba1812727
--- /dev/null
+++ b/internal/clients/cache/client.go
@@ -0,0 +1,84 @@
+package cache
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+	"io"
+	"net/http"
+
+	"code.vereign.com/gaiax/tsa/golib/errors"
+)
+
+// Client for the Cache service.
+type Client struct {
+	addr       string
+	httpClient *http.Client
+}
+
+func New(addr string, opts ...Option) *Client {
+	c := &Client{
+		addr:       addr,
+		httpClient: http.DefaultClient,
+	}
+
+	for _, opt := range opts {
+		opt(c)
+	}
+
+	return c
+}
+
+func (c *Client) Set(ctx context.Context, key, namespace, scope string, value []byte) error {
+	req, err := http.NewRequestWithContext(ctx, "POST", c.addr+"/v1/cache", bytes.NewReader(value))
+	if err != nil {
+		return err
+	}
+
+	req.Header = http.Header{
+		"x-cache-key":       []string{key},
+		"x-cache-namespace": []string{namespace},
+		"x-cache-scope":     []string{scope},
+	}
+
+	resp, err := c.httpClient.Do(req)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close() // nolint:errcheck
+
+	if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
+		msg := fmt.Sprintf("unexpected response: %d %s", resp.StatusCode, resp.Status)
+		return errors.New(errors.GetKind(resp.StatusCode), msg)
+	}
+
+	return nil
+}
+
+func (c *Client) Get(ctx context.Context, key, namespace, scope string) ([]byte, error) {
+	req, err := http.NewRequestWithContext(ctx, "GET", c.addr+"/v1/cache", nil)
+	req.Header = http.Header{
+		"x-cache-key":       []string{key},
+		"x-cache-namespace": []string{namespace},
+		"x-cache-scope":     []string{scope},
+	}
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := c.httpClient.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close() // nolint:errcheck
+
+	if resp.StatusCode != http.StatusOK {
+		if resp.StatusCode == http.StatusNotFound {
+			return nil, errors.New(errors.NotFound)
+		}
+		msg := fmt.Sprintf("unexpected response: %d %s", resp.StatusCode, resp.Status)
+		return nil, errors.New(errors.GetKind(resp.StatusCode), msg)
+	}
+
+	return io.ReadAll(resp.Body)
+}
diff --git a/internal/clients/cache/option.go b/internal/clients/cache/option.go
new file mode 100644
index 0000000000000000000000000000000000000000..10ef93337d96c9319a0d4dff4333024a581fa921
--- /dev/null
+++ b/internal/clients/cache/option.go
@@ -0,0 +1,13 @@
+package cache
+
+import (
+	"net/http"
+)
+
+type Option func(*Client)
+
+func WithHTTPClient(client *http.Client) Option {
+	return func(c *Client) {
+		c.httpClient = client
+	}
+}
diff --git a/internal/service/policy/policyfakes/fake_cache.go b/internal/service/policy/policyfakes/fake_cache.go
new file mode 100644
index 0000000000000000000000000000000000000000..a8583cb47e02f7f86935fbe1a18298f2b48858bd
--- /dev/null
+++ b/internal/service/policy/policyfakes/fake_cache.go
@@ -0,0 +1,210 @@
+// Code generated by counterfeiter. DO NOT EDIT.
+package policyfakes
+
+import (
+	"context"
+	"sync"
+
+	"code.vereign.com/gaiax/tsa/policy/internal/service/policy"
+)
+
+type FakeCache struct {
+	GetStub        func(context.Context, string, string, string) ([]byte, error)
+	getMutex       sync.RWMutex
+	getArgsForCall []struct {
+		arg1 context.Context
+		arg2 string
+		arg3 string
+		arg4 string
+	}
+	getReturns struct {
+		result1 []byte
+		result2 error
+	}
+	getReturnsOnCall map[int]struct {
+		result1 []byte
+		result2 error
+	}
+	SetStub        func(context.Context, string, string, string, []byte) error
+	setMutex       sync.RWMutex
+	setArgsForCall []struct {
+		arg1 context.Context
+		arg2 string
+		arg3 string
+		arg4 string
+		arg5 []byte
+	}
+	setReturns struct {
+		result1 error
+	}
+	setReturnsOnCall map[int]struct {
+		result1 error
+	}
+	invocations      map[string][][]interface{}
+	invocationsMutex sync.RWMutex
+}
+
+func (fake *FakeCache) Get(arg1 context.Context, arg2 string, arg3 string, arg4 string) ([]byte, error) {
+	fake.getMutex.Lock()
+	ret, specificReturn := fake.getReturnsOnCall[len(fake.getArgsForCall)]
+	fake.getArgsForCall = append(fake.getArgsForCall, struct {
+		arg1 context.Context
+		arg2 string
+		arg3 string
+		arg4 string
+	}{arg1, arg2, arg3, arg4})
+	stub := fake.GetStub
+	fakeReturns := fake.getReturns
+	fake.recordInvocation("Get", []interface{}{arg1, arg2, arg3, arg4})
+	fake.getMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3, arg4)
+	}
+	if specificReturn {
+		return ret.result1, ret.result2
+	}
+	return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *FakeCache) GetCallCount() int {
+	fake.getMutex.RLock()
+	defer fake.getMutex.RUnlock()
+	return len(fake.getArgsForCall)
+}
+
+func (fake *FakeCache) GetCalls(stub func(context.Context, string, string, string) ([]byte, error)) {
+	fake.getMutex.Lock()
+	defer fake.getMutex.Unlock()
+	fake.GetStub = stub
+}
+
+func (fake *FakeCache) GetArgsForCall(i int) (context.Context, string, string, string) {
+	fake.getMutex.RLock()
+	defer fake.getMutex.RUnlock()
+	argsForCall := fake.getArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4
+}
+
+func (fake *FakeCache) GetReturns(result1 []byte, result2 error) {
+	fake.getMutex.Lock()
+	defer fake.getMutex.Unlock()
+	fake.GetStub = nil
+	fake.getReturns = struct {
+		result1 []byte
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *FakeCache) GetReturnsOnCall(i int, result1 []byte, result2 error) {
+	fake.getMutex.Lock()
+	defer fake.getMutex.Unlock()
+	fake.GetStub = nil
+	if fake.getReturnsOnCall == nil {
+		fake.getReturnsOnCall = make(map[int]struct {
+			result1 []byte
+			result2 error
+		})
+	}
+	fake.getReturnsOnCall[i] = struct {
+		result1 []byte
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *FakeCache) Set(arg1 context.Context, arg2 string, arg3 string, arg4 string, arg5 []byte) error {
+	var arg5Copy []byte
+	if arg5 != nil {
+		arg5Copy = make([]byte, len(arg5))
+		copy(arg5Copy, arg5)
+	}
+	fake.setMutex.Lock()
+	ret, specificReturn := fake.setReturnsOnCall[len(fake.setArgsForCall)]
+	fake.setArgsForCall = append(fake.setArgsForCall, struct {
+		arg1 context.Context
+		arg2 string
+		arg3 string
+		arg4 string
+		arg5 []byte
+	}{arg1, arg2, arg3, arg4, arg5Copy})
+	stub := fake.SetStub
+	fakeReturns := fake.setReturns
+	fake.recordInvocation("Set", []interface{}{arg1, arg2, arg3, arg4, arg5Copy})
+	fake.setMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3, arg4, arg5)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *FakeCache) SetCallCount() int {
+	fake.setMutex.RLock()
+	defer fake.setMutex.RUnlock()
+	return len(fake.setArgsForCall)
+}
+
+func (fake *FakeCache) SetCalls(stub func(context.Context, string, string, string, []byte) error) {
+	fake.setMutex.Lock()
+	defer fake.setMutex.Unlock()
+	fake.SetStub = stub
+}
+
+func (fake *FakeCache) SetArgsForCall(i int) (context.Context, string, string, string, []byte) {
+	fake.setMutex.RLock()
+	defer fake.setMutex.RUnlock()
+	argsForCall := fake.setArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4, argsForCall.arg5
+}
+
+func (fake *FakeCache) SetReturns(result1 error) {
+	fake.setMutex.Lock()
+	defer fake.setMutex.Unlock()
+	fake.SetStub = nil
+	fake.setReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *FakeCache) SetReturnsOnCall(i int, result1 error) {
+	fake.setMutex.Lock()
+	defer fake.setMutex.Unlock()
+	fake.SetStub = nil
+	if fake.setReturnsOnCall == nil {
+		fake.setReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.setReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *FakeCache) Invocations() map[string][][]interface{} {
+	fake.invocationsMutex.RLock()
+	defer fake.invocationsMutex.RUnlock()
+	fake.getMutex.RLock()
+	defer fake.getMutex.RUnlock()
+	fake.setMutex.RLock()
+	defer fake.setMutex.RUnlock()
+	copiedInvocations := map[string][][]interface{}{}
+	for key, value := range fake.invocations {
+		copiedInvocations[key] = value
+	}
+	return copiedInvocations
+}
+
+func (fake *FakeCache) recordInvocation(key string, args []interface{}) {
+	fake.invocationsMutex.Lock()
+	defer fake.invocationsMutex.Unlock()
+	if fake.invocations == nil {
+		fake.invocations = map[string][][]interface{}{}
+	}
+	if fake.invocations[key] == nil {
+		fake.invocations[key] = [][]interface{}{}
+	}
+	fake.invocations[key] = append(fake.invocations[key], args)
+}
+
+var _ policy.Cache = new(FakeCache)
diff --git a/internal/service/policy/service.go b/internal/service/policy/service.go
index d453ef8e07d88c4f1a43566ed5b4ad1ede80f9a1..bf01650102a1934b4dc268b2a6c2eb666627faa7 100644
--- a/internal/service/policy/service.go
+++ b/internal/service/policy/service.go
@@ -2,8 +2,10 @@ package policy
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 
+	"github.com/google/uuid"
 	"github.com/open-policy-agent/opa/rego"
 	"go.uber.org/zap"
 
@@ -13,9 +15,15 @@ import (
 	"code.vereign.com/gaiax/tsa/policy/internal/storage"
 )
 
+//go:generate counterfeiter . Cache
 //go:generate counterfeiter . Storage
 //go:generate counterfeiter . RegoCache
 
+type Cache interface {
+	Set(ctx context.Context, key, namespace, scope string, value []byte) error
+	Get(ctx context.Context, key, namespace, scope string) ([]byte, error)
+}
+
 type Storage interface {
 	Policy(ctx context.Context, group, name, version string) (*storage.Policy, error)
 	SetPolicyLock(ctx context.Context, group, name, version string, lock bool) error
@@ -29,13 +37,15 @@ type RegoCache interface {
 type Service struct {
 	storage    Storage
 	queryCache RegoCache
+	cache      Cache
 	logger     *zap.Logger
 }
 
-func New(storage Storage, queryCache RegoCache, logger *zap.Logger) *Service {
+func New(storage Storage, queryCache RegoCache, cache Cache, logger *zap.Logger) *Service {
 	return &Service{
 		storage:    storage,
 		queryCache: queryCache,
+		cache:      cache,
 		logger:     logger,
 	}
 }
@@ -47,12 +57,14 @@ func New(storage Storage, queryCache RegoCache, logger *zap.Logger) *Service {
 // be exactly the same as 'group.policy'. For example:
 // Evaluating the URL: `.../policies/mygroup/example/1.0/evaluation` will
 // return results correctly, only if the package declaration inside the policy is:
-// `package mygroup.example`
+// `package mygroup.example`.
 func (s *Service) Evaluate(ctx context.Context, req *policy.EvaluateRequest) (interface{}, error) {
+	evaluationID := uuid.NewString()
 	logger := s.logger.With(
 		zap.String("group", req.Group),
 		zap.String("name", req.PolicyName),
 		zap.String("version", req.Version),
+		zap.String("evaluationID", evaluationID),
 	)
 
 	query, err := s.prepareQuery(ctx, req.Group, req.PolicyName, req.Version)
@@ -77,7 +89,23 @@ func (s *Service) Evaluate(ctx context.Context, req *policy.EvaluateRequest) (in
 		return nil, errors.New("policy evaluation result expressions are empty")
 	}
 
-	return resultSet[0].Expressions[0].Value, nil
+	jsonValue, err := json.Marshal(resultSet[0].Expressions[0].Value)
+	if err != nil {
+		logger.Error("error encoding result to json", zap.Error(err))
+		return nil, errors.New("error encoding result to json")
+	}
+
+	if err := s.cache.Set(ctx, evaluationID, "", "", jsonValue); err != nil {
+		logger.Error("error storing policy result in cache", zap.Error(err))
+		return nil, errors.New("error storing policy result in cache")
+	}
+
+	result := map[string]interface{}{
+		"evaluationID": evaluationID,
+		"result":       resultSet[0].Expressions[0].Value,
+	}
+
+	return result, nil
 }
 
 // Lock a policy so that it cannot be evaluated.
diff --git a/internal/service/policy/service_test.go b/internal/service/policy/service_test.go
index 5b6949a50f091c42308aa88fd89e69cd755f3008..69c25d3c5694e39221706fcf5c08b4fed7b01ea3 100644
--- a/internal/service/policy/service_test.go
+++ b/internal/service/policy/service_test.go
@@ -17,9 +17,7 @@ import (
 )
 
 func TestNew(t *testing.T) {
-	storage := &policyfakes.FakeStorage{}
-	regocache := &policyfakes.FakeRegoCache{}
-	svc := policy.New(storage, regocache, zap.NewNop())
+	svc := policy.New(nil, nil, nil, zap.NewNop())
 	assert.Implements(t, (*goapolicy.Service)(nil), svc)
 }
 
@@ -50,7 +48,7 @@ func TestService_Evaluate(t *testing.T) {
 		req       *goapolicy.EvaluateRequest
 		storage   policy.Storage
 		regocache policy.RegoCache
-
+		cache     policy.Cache
 		// expected result
 		res     interface{}
 		errkind errors.Kind
@@ -65,6 +63,11 @@ func TestService_Evaluate(t *testing.T) {
 					return &q, true
 				},
 			},
+			cache: &policyfakes.FakeCache{
+				SetStub: func(ctx context.Context, s string, s2 string, s3 string, bytes []byte) error {
+					return nil
+				},
+			},
 			res: map[string]interface{}{"allow": true},
 		},
 		{
@@ -138,17 +141,55 @@ func TestService_Evaluate(t *testing.T) {
 					}, nil
 				},
 			},
+			cache: &policyfakes.FakeCache{
+				SetStub: func(ctx context.Context, s string, s2 string, s3 string, bytes []byte) error {
+					return nil
+				},
+			},
 			res: map[string]interface{}{"allow": true},
 		},
+		{
+			name: "policy is executed successfully, but storing the result in cache fails",
+			req:  testReq(),
+			regocache: &policyfakes.FakeRegoCache{
+				GetStub: func(key string) (*rego.PreparedEvalQuery, bool) {
+					return nil, false
+				},
+			},
+			storage: &policyfakes.FakeStorage{
+				PolicyStub: func(ctx context.Context, s string, s2 string, s3 string) (*storage.Policy, error) {
+					return &storage.Policy{
+						Name:       "example",
+						Group:      "testgroup",
+						Version:    "1.0",
+						Rego:       testPolicy,
+						Locked:     false,
+						LastUpdate: time.Now(),
+					}, nil
+				},
+			},
+			cache: &policyfakes.FakeCache{
+				SetStub: func(ctx context.Context, s string, s2 string, s3 string, bytes []byte) error {
+					return errors.New("some error")
+				},
+			},
+			errkind: errors.Unknown,
+			errtext: "error storing policy result in cache",
+		},
 	}
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			svc := policy.New(test.storage, test.regocache, zap.NewNop())
+			svc := policy.New(test.storage, test.regocache, test.cache, zap.NewNop())
 			res, err := svc.Evaluate(context.Background(), test.req)
 			if err == nil {
 				assert.Empty(t, test.errtext)
-				assert.Equal(t, test.res, res)
+				assert.NotNil(t, res)
+
+				result, ok := res.(map[string]interface{})
+				assert.True(t, ok)
+				assert.Equal(t, test.res, result["result"])
+				assert.NotEmpty(t, result["evaluationID"])
 			} else {
 				e, ok := err.(*errors.Error)
 				assert.True(t, ok)
@@ -243,7 +284,7 @@ func TestService_Lock(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			svc := policy.New(test.storage, nil, zap.NewNop())
+			svc := policy.New(test.storage, nil, nil, zap.NewNop())
 			err := svc.Lock(context.Background(), test.req)
 			if err == nil {
 				assert.Empty(t, test.errtext)
@@ -340,7 +381,7 @@ func TestService_Unlock(t *testing.T) {
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			svc := policy.New(test.storage, nil, zap.NewNop())
+			svc := policy.New(test.storage, nil, nil, zap.NewNop())
 			err := svc.Unlock(context.Background(), test.req)
 			if err == nil {
 				assert.Empty(t, test.errtext)