Repository: TBD54566975/ssi-service
Pull request #91 (2022-09-08 23:38:21)

SIP 2 - Create Credential Manifests CRUD
This is the implementation of the Credential Manifest CRUD functionality according to SIP 2
https://github.com/TBD54566975/ssi-service/tree/main/sip/sips/sip_2


<img width="765" alt="image" src="https://user-images.githubusercontent.com/5314059/189243460-9080ec2e-fc42-4ee2-92df-7ef68468c5fc.png">



**Postman Data**d
PUT Manifest

Input: 
```
{
  "@context": "",
  "issuer" : "did:example:123",
  "outputDescriptors": [
            {
                "id": "id1z1",
                "schema": "https://test.com/schema",
                "name": "good ID",
                "description": "it's all good"
            },
            {
                "id": "id2z",
                "schema": "https://test.com/schema",
                "name": "good ID",
                "description": "it's all good"
            }
        ],
   "presentationDefinition": {
    "id": "test",
    "input_descriptors": [
      {
        "id": "test input descriptor",
        "constraints" : 
            {
                "fields" : [ 
                    {
                    "path" : [
                        "path1"
                    ]
                    }
                ]
            }
        

      }
    ]
  }
}
```


Output
```
{
    "manifest": {
        "id": "a2170fed-81cd-42db-ad79-3d65ea720878",
        "issuer": {
            "id": "did:example:123",
            "name": "did:example:123"
        },
        "output_descriptors": [
            {
                "id": "id1z1",
                "schema": "https://test.com/schema",
                "name": "good ID",
                "description": "it's all good"
            },
            {
                "id": "id2z",
                "schema": "https://test.com/schema",
                "name": "good ID",
                "description": "it's all good"
            }
        ],
        "format": {
            "jwt": {
                "alg": [
                    "EdDSA"
                ]
            }
        },
        "presentation_definition": {
            "id": "test",
            "input_descriptors": [
                {
                    "id": "test input descriptor",
                    "constraints": {
                        "fields": [
                            {
                                "path": [
                                    "path1"
                                ]
                            }
                        ]
                    }
                }
            ]
        }
    }
}
```

codecov-commenter: # [Codecov](https://codecov.io/gh/TBD54566975/ssi-service/pull/91?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975) Report
> Merging [#91](https://codecov.io/gh/TBD54566975/ssi-service/pull/91?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975) (2b58f55) into [main](https://codecov.io/gh/TBD54566975/ssi-service/commit/07227366d96ef5ab0dd74371f31e12d2f29ffab9?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975) (0722736) will **decrease** coverage by `0.68%`.
> The diff coverage is `20.43%`.

```diff
@@            Coverage Diff             @@
##             main      #91      +/-   ##
==========================================
- Coverage   26.63%   25.94%   -0.69%     
==========================================
  Files          13       14       +1     
  Lines         751      844      +93     
==========================================
+ Hits          200      219      +19     
- Misses        521      594      +73     
- Partials       30       31       +1     
```


| [Impacted Files](https://codecov.io/gh/TBD54566975/ssi-service/pull/91?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975) | Coverage Δ | |
|---|---|---|
| [config/config.go](https://codecov.io/gh/TBD54566975/ssi-service/pull/91/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975#diff-Y29uZmlnL2NvbmZpZy5nbw==) | `12.50% <ø> (ø)` | |
| [pkg/server/router/manifest.go](https://codecov.io/gh/TBD54566975/ssi-service/pull/91/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975#diff-cGtnL3NlcnZlci9yb3V0ZXIvbWFuaWZlc3QuZ28=) | `10.12% <10.12%> (ø)` | |
| [pkg/server/server.go](https://codecov.io/gh/TBD54566975/ssi-service/pull/91/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975#diff-cGtnL3NlcnZlci9zZXJ2ZXIuZ28=) | `68.86% <78.57%> (+1.47%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TBD54566975)


Patch:
diff --git a/config/config.go b/config/config.go
index 3e85631..d2b26fe 100644
--- a/config/config.go
+++ b/config/config.go
@@ -52,6 +52,7 @@ type ServicesConfig struct {
 	SchemaConfig     SchemaServiceConfig     `toml:"schema,omitempty"`
 	CredentialConfig CredentialServiceConfig `toml:"credential,omitempty"`
 	KeyStoreConfig   KeyStoreServiceConfig   `toml:"keystore,omitempty"`
+	ManifestConfig   ManifestServiceConfig   `toml:"manifest,omitempty"`
 }
 
 // BaseServiceConfig represents configurable properties for a specific component of the SSI Service
@@ -88,6 +89,10 @@ type CredentialServiceConfig struct {
 	// TODO(gabe) supported key and signature types
 }
 
+type ManifestServiceConfig struct {
+	*BaseServiceConfig
+}
+
 type KeyStoreServiceConfig struct {
 	*BaseServiceConfig
 	// Service key password. Used by a KDF whose key is used by a symmetric cypher for key encryption.
diff --git a/go.mod b/go.mod
index d8dfe13..176345f 100644
--- a/go.mod
+++ b/go.mod
@@ -24,6 +24,8 @@ require (
 
 require go.etcd.io/bbolt v1.3.6
 
+require github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 // indirect
+
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
diff --git a/go.sum b/go.sum
index b8df68d..bde0148 100644
--- a/go.sum
+++ b/go.sum
@@ -75,6 +75,7 @@ github.com/multiformats/go-multicodec v0.5.0 h1:EgU6cBe/D7WRwQb1KmnBvU7lrcFGMggZ
 github.com/multiformats/go-multicodec v0.5.0/go.mod h1:DiY2HFaEp5EhEXb/iYzVAunmyX/aSFMxq2KMKfWEues=
 github.com/multiformats/go-varint v0.0.6 h1:gk85QWKxh3TazbLxED/NlDVv8+q+ReFJk7Y2W/KhfNY=
 github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
+github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 h1:Yl0tPBa8QPjGmesFh1D0rDy+q1Twx6FyU7VWHi8wZbI=
 github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4=
 github.com/piprate/json-gold v0.4.1 h1:JYbYN36n6YcAYipKy3ttv3X2HDQPeqWqmwta35NPj04=
 github.com/piprate/json-gold v0.4.1/go.mod h1:OK1z7UgtBZk06n2cDE2OSq1kffmjFFp5/2yhLLCz9UM=
diff --git a/pkg/server/router/manifest.go b/pkg/server/router/manifest.go
new file mode 100644
index 0000000..6a05892
--- /dev/null
+++ b/pkg/server/router/manifest.go
@@ -0,0 +1,184 @@
+package router
+
+import (
+	"context"
+	"fmt"
+	"github.com/TBD54566975/ssi-sdk/credential/exchange"
+	manifestsdk "github.com/TBD54566975/ssi-sdk/credential/manifest"
+	"github.com/tbd54566975/ssi-service/pkg/service/manifest"
+	"net/http"
+
+	"github.com/pkg/errors"
+	"github.com/sirupsen/logrus"
+
+	"github.com/tbd54566975/ssi-service/pkg/server/framework"
+	svcframework "github.com/tbd54566975/ssi-service/pkg/service/framework"
+)
+
+type ManifestRouter struct {
+	service *manifest.Service
+}
+
+func NewManifestRouter(s svcframework.Service) (*ManifestRouter, error) {
+	if s == nil {
+		return nil, errors.New("service cannot be nil")
+	}
+	credService, ok := s.(*manifest.Service)
+	if !ok {
+		return nil, fmt.Errorf("could not create manifest router with service type: %s", s.Type())
+	}
+	return &ManifestRouter{
+		service: credService,
+	}, nil
+}
+
+type CreateManifestRequest struct {
+	Issuer string `json:"issuer" validate:"required"`
+	// A context is optional. If not present, we'll apply default, required context values.
+	Context                string                          `json:"@context"`
+	OutputDescriptors      []manifestsdk.OutputDescriptor  `json:"outputDescriptors" validate:"required"`
+	PresentationDefinition exchange.PresentationDefinition `json:"presentationDefinition" validate:"required"`
+}
+
+func (c CreateManifestRequest) ToServiceRequest() manifest.CreateManifestRequest {
+	return manifest.CreateManifestRequest{
+		Issuer:                 c.Issuer,
+		Context:                c.Context,
+		OutputDescriptors:      c.OutputDescriptors,
+		PresentationDefinition: c.PresentationDefinition,
+	}
+}
+
+type CreateManifestResponse struct {
+	Manifest manifestsdk.CredentialManifest `json:"manifest"`
+}
+
+// CreateManifest godoc
+// @Summary      Create manifest
+// @Description  Create manifest
+// @Tags         ManifestAPI
+// @Accept       json
+// @Produce      json
+// @Param        request  body      CreateManifestRequest  true  "request body"
+// @Success      201      {object}  CreateManifestResponse
+// @Failure      400      {string}  string  "Bad request"
+// @Failure      500      {string}  string  "Internal server error"
+// @Router       /v1/manifests [put]
+func (mr ManifestRouter) CreateManifest(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
+	var request CreateManifestRequest
+	if err := framework.Decode(r, &request); err != nil {
+		errMsg := "invalid create manifest request"
+		logrus.WithError(err).Error(errMsg)
+		return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest)
+	}
+
+	req := request.ToServiceRequest()
+	createManifestResponse, err := mr.service.CreateManifest(req)
+	if err != nil {
+		errMsg := "could not create manifest"
+		logrus.WithError(err).Error(errMsg)
+		return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusInternalServerError)
+	}
+
+	resp := CreateManifestResponse{Manifest: createManifestResponse.Manifest}
+
+	return framework.Respond(ctx, w, resp, http.StatusCreated)
+}
+
+type GetManifestResponse struct {
+	ID       string                         `json:"id"`
+	Manifest manifestsdk.CredentialManifest `json:"manifest"`
+}
+
+// GetManifest godoc
+// @Summary      Get manifest
+// @Description  Get manifest by id
+// @Tags         ManifestAPI
+// @Accept       json
+// @Produce      json
+// @Param        id   path      string  true  "ID"
+// @Success      200  {object}  GetManifestResponse
+// @Failure      400  {string}  string  "Bad request"
+// @Router       /v1/manifests/{id} [get]
+func (mr ManifestRouter) GetManifest(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
+	id := framework.GetParam(ctx, IDParam)
+	if id == nil {
+		errMsg := "cannot get manifest without ID parameter"
+		logrus.Error(errMsg)
+		return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest)
+	}
+
+	gotManifest, err := mr.service.GetManifest(manifest.GetManifestRequest{ID: *id})
+	if err != nil {
+		errMsg := fmt.Sprintf("could not get manifest with id: %s", *id)
+		logrus.WithError(err).Error(errMsg)
+		return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest)
+	}
+
+	resp := GetManifestResponse{
+		ID:       gotManifest.Manifest.ID,
+		Manifest: gotManifest.Manifest,
+	}
+	return framework.Respond(ctx, w, resp, http.StatusOK)
+}
+
+type GetManifestsResponse struct {
+	Manifests []manifestsdk.CredentialManifest `json:"manifests"`
+}
+
+// GetManifests godoc
+// @Summary      Get manifests
+// @Description  Checks for the presence of a query parameter and calls the associated filtered get method
+// @Tags         ManifestAPI
+// @Accept       json
+// @Produce      json
+// @Param        issuer   query     string  false  "string issuer"
+// @Param        schema   query     string  false  "string schema"
+// @Param        subject  query     string  false  "string subject"
+// @Success      200      {object}  GetManifestsResponse
+// @Failure      400      {string}  string  "Bad request"
+// @Failure      500      {string}  string  "Internal server error"
+// @Router       /v1/manifests [get]
+func (mr ManifestRouter) GetManifests(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
+	gotManifests, err := mr.service.GetManifests()
+
+	if err != nil {
+		errMsg := fmt.Sprintf("could not get manifests")
+		logrus.WithError(err).Error(errMsg)
+		return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest)
+	}
+
+	resp := GetManifestsResponse{
+		Manifests: gotManifests.Manifests,
+	}
+
+	return framework.Respond(ctx, w, resp, http.StatusOK)
+}
+
+// DeleteManifest godoc
+// @Summary      Delete manifests
+// @Description  Delete manifest by ID
+// @Tags         ManifestAPI
+// @Accept       json
+// @Produce      json
+// @Param        id   path      string  true  "ID"
+// @Success      200  {string}  string  "OK"
+// @Failure      400  {string}  string  "Bad request"
+// @Failure      500  {string}  string  "Internal server error"
+// @Router       /v1/manifests/{id} [delete]
+func (mr ManifestRouter) DeleteManifest(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
+	id := framework.GetParam(ctx, IDParam)
+	if id == nil {
+		errMsg := "cannot delete manifest without ID parameter"
+		logrus.Error(errMsg)
+		return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest)
+	}
+
+	if err := mr.service.DeleteManifest(manifest.DeleteManifestRequest{ID: *id}); err != nil {
+		errMsg := fmt.Sprintf("could not delete manifest with id: %s", *id)
+		logrus.WithError(err).Error(errMsg)
+		return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusInternalServerError)
+	}
+
+	return framework.Respond(ctx, w, nil, http.StatusOK)
+}
diff --git a/pkg/server/router/schema.go b/pkg/server/router/schema.go
index a5093e6..29020bd 100644
--- a/pkg/server/router/schema.go
+++ b/pkg/server/router/schema.go
@@ -100,7 +100,7 @@ type GetSchemaResponse struct {
 	Schema schemalib.VCJSONSchema `json:"schema,omitempty"`
 }
 
-// GetSchemaByID godoc
+// GetSchema godoc
 // @Summary      Get Schema
 // @Description  Get schema by ID
 // @Tags         SchemaAPI
@@ -110,7 +110,7 @@ type GetSchemaResponse struct {
 // @Success      200  {object}  GetSchemaResponse
 // @Failure      400  {string}  string  "Bad request"
 // @Router       /v1/schemas/{id} [get]
-func (sr SchemaRouter) GetSchemaByID(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
+func (sr SchemaRouter) GetSchema(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
 	id := framework.GetParam(ctx, IDParam)
 	if id == nil {
 		errMsg := "cannot get schema without ID parameter"
@@ -119,7 +119,7 @@ func (sr SchemaRouter) GetSchemaByID(ctx context.Context, w http.ResponseWriter,
 	}
 
 	// TODO(gabe) differentiate between internal errors and not found schemas
-	gotSchema, err := sr.service.GetSchemaByID(schema.GetSchemaByIDRequest{ID: *id})
+	gotSchema, err := sr.service.GetSchema(schema.GetSchemaRequest{ID: *id})
 	if err != nil {
 		errMsg := fmt.Sprintf("could not get schema with id: %s", *id)
 		logrus.WithError(err).Error(errMsg)
diff --git a/pkg/server/server.go b/pkg/server/server.go
index 8c59798..96bda5e 100644
--- a/pkg/server/server.go
+++ b/pkg/server/server.go
@@ -26,6 +26,7 @@ const (
 	DIDsPrefix        = "/dids"
 	SchemasPrefix     = "/schemas"
 	CredentialsPrefix = "/credentials"
+	ManifestsPrefix   = "/manifests"
 	KeyStorePrefix    = "/keys"
 )
 
@@ -91,6 +92,8 @@ func (s *SSIServer) instantiateRouter(service svcframework.Service) error {
 		return s.CredentialAPI(service)
 	case svcframework.KeyStore:
 		return s.KeyStoreAPI(service)
+	case svcframework.Manifest:
+		return s.ManifestAPI(service)
 	default:
 		return fmt.Errorf("could not instantiate API for service: %s", serviceType)
 	}
@@ -122,7 +125,7 @@ func (s *SSIServer) SchemaAPI(service svcframework.Service) (err error) {
 
 	s.Handle(http.MethodPut, handlerPath, schemaRouter.CreateSchema)
 	s.Handle(http.MethodGet, handlerPath, schemaRouter.GetSchemas)
-	s.Handle(http.MethodGet, path.Join(handlerPath, "/:id"), schemaRouter.GetSchemaByID)
+	s.Handle(http.MethodGet, path.Join(handlerPath, "/:id"), schemaRouter.GetSchema)
 	return
 }
 
@@ -153,3 +156,18 @@ func (s *SSIServer) KeyStoreAPI(service svcframework.Service) (err error) {
 	s.Handle(http.MethodGet, path.Join(handlerPath, "/:id"), keyStoreRouter.GetKeyDetails)
 	return
 }
+
+func (s *SSIServer) ManifestAPI(service svcframework.Service) (err error) {
+	manifestRouter, err := router.NewManifestRouter(service)
+	if err != nil {
+		return util.LoggingErrorMsg(err, "could not create manifest router")
+	}
+
+	handlerPath := V1Prefix + ManifestsPrefix
+
+	s.Handle(http.MethodPut, handlerPath, manifestRouter.CreateManifest)
+	s.Handle(http.MethodGet, handlerPath, manifestRouter.GetManifests)
+	s.Handle(http.MethodGet, path.Join(handlerPath, "/:id"), manifestRouter.GetManifest)
+	s.Handle(http.MethodDelete, path.Join(handlerPath, "/:id"), manifestRouter.DeleteManifest)
+	return
+}
diff --git a/pkg/service/framework/framework.go b/pkg/service/framework/framework.go
index 8b6b25c..416f020 100644
--- a/pkg/service/framework/framework.go
+++ b/pkg/service/framework/framework.go
@@ -12,6 +12,7 @@ const (
 	Schema     Type = "schema"
 	Credential Type = "credential"
 	KeyStore   Type = "keystore"
+	Manifest   Type = "manifest"
 
 	StatusReady    StatusState = "ready"
 	StatusNotReady StatusState = "not_ready"
diff --git a/pkg/service/manifest/manifest.go b/pkg/service/manifest/manifest.go
new file mode 100644
index 0000000..8abb735
--- /dev/null
+++ b/pkg/service/manifest/manifest.go
@@ -0,0 +1,165 @@
+package manifest
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/TBD54566975/ssi-sdk/credential/exchange"
+	"github.com/TBD54566975/ssi-sdk/credential/manifest"
+	"github.com/TBD54566975/ssi-sdk/crypto"
+	"github.com/sirupsen/logrus"
+	"github.com/tbd54566975/ssi-service/config"
+	"github.com/tbd54566975/ssi-service/internal/util"
+	"github.com/tbd54566975/ssi-service/pkg/service/framework"
+	manifeststorage "github.com/tbd54566975/ssi-service/pkg/service/manifest/storage"
+	"github.com/tbd54566975/ssi-service/pkg/storage"
+)
+
+type Service struct {
+	storage manifeststorage.Storage
+	config  config.ManifestServiceConfig
+}
+
+func (s Service) Type() framework.Type {
+	return framework.Manifest
+}
+
+func (s Service) Status() framework.Status {
+	if s.storage == nil {
+		return framework.Status{
+			Status:  framework.StatusNotReady,
+			Message: "no storage",
+		}
+	}
+	return framework.Status{Status: framework.StatusReady}
+}
+
+func (s Service) Config() config.ManifestServiceConfig {
+	return s.config
+}
+
+func NewManifestService(config config.ManifestServiceConfig, s storage.ServiceStorage) (*Service, error) {
+	manifestStorage, err := manifeststorage.NewManifestStorage(s)
+	if err != nil {
+		errMsg := "could not instantiate storage for the manifest service"
+		return nil, util.LoggingErrorMsg(err, errMsg)
+	}
+	return &Service{
+		storage: manifestStorage,
+		config:  config,
+	}, nil
+}
+
+func (s Service) CreateManifest(request CreateManifestRequest) (*CreateManifestResponse, error) {
+	logrus.Debugf("creating manifest: %+v", request)
+
+	builder := manifest.NewCredentialManifestBuilder()
+	issuer := manifest.Issuer{ID: request.Issuer, Name: request.Issuer}
+
+	if err := builder.SetIssuer(issuer); err != nil {
+		errMsg := fmt.Sprintf("could not build manifest when setting issuer: %s", request.Issuer)
+		return nil, util.LoggingErrorMsg(err, errMsg)
+	}
+
+	// TODO: (Neal) Add dynamic claim formats
+	if err := builder.SetClaimFormat(exchange.ClaimFormat{
+		JWT: &exchange.JWTType{Alg: []crypto.SignatureAlgorithm{crypto.EdDSA}},
+	}); err != nil {
+		errMsg := fmt.Sprintf("could not build manifest when setting claim format")
+		return nil, util.LoggingErrorMsg(err, errMsg)
+	}
+
+	// parse OutputDescriptors
+	odJsonString, err := json.Marshal(request.OutputDescriptors)
+	if err != nil {
+		errMsg := "could not marshal request output descriptors"
+		return nil, util.LoggingErrorMsg(err, errMsg)
+	}
+
+	od := []manifest.OutputDescriptor{}
+	err = json.Unmarshal(odJsonString, &od)
+	if err != nil {
+		errMsg := "could not unmarshal output descriptors"
+		return nil, util.LoggingErrorMsg(err, errMsg)
+	}
+
+	builder.SetOutputDescriptors(od)
+
+	// parse PresentationDefinition
+	pdJsonString, err := json.Marshal(request.PresentationDefinition)
+	if err != nil {
+		errMsg := "could not marshal request presentation definition"
+		return nil, util.LoggingErrorMsg(err, errMsg)
+	}
+
+	pd := exchange.PresentationDefinition{}
+	err = json.Unmarshal(pdJsonString, &pd)
+	if err != nil {
+		errMsg := "could not unmarshal presentation definition"
+		return nil, util.LoggingErrorMsg(err, errMsg)
+	}
+	builder.SetPresentationDefinition(pd)
+
+	mfst, err := builder.Build()
+	if err != nil {
+		errMsg := "could not build manifest"
+		return nil, util.LoggingErrorMsg(err, errMsg)
+	}
+
+	// store the manifest
+	storageRequest := manifeststorage.StoredManifest{
+		ID:       mfst.ID,
+		Manifest: *mfst,
+		Issuer:   request.Issuer,
+	}
+
+	if err := s.storage.StoreManifest(storageRequest); err != nil {
+		errMsg := "could not store manifest"
+		return nil, util.LoggingErrorMsg(err, errMsg)
+	}
+
+	// return the result
+	response := CreateManifestResponse{Manifest: *mfst}
+	return &response, nil
+}
+
+func (s Service) GetManifest(request GetManifestRequest) (*GetManifestResponse, error) {
+
+	logrus.Debugf("getting manifest: %s", request.ID)
+
+	gotManifest, err := s.storage.GetManifest(request.ID)
+	if err != nil {
+		errMsg := fmt.Sprintf("could not get manifest: %s", request.ID)
+		return nil, util.LoggingErrorMsg(err, errMsg)
+	}
+
+	response := GetManifestResponse{Manifest: gotManifest.Manifest}
+	return &response, nil
+}
+
+func (s Service) GetManifests() (*GetManifestsResponse, error) {
+	gotManifests, err := s.storage.GetManifests()
+
+	if err != nil {
+		errMsg := fmt.Sprintf("could not get manifests(s)")
+		return nil, util.LoggingErrorMsg(err, errMsg)
+	}
+
+	var manifests []manifest.CredentialManifest
+	for _, manifest := range gotManifests {
+		manifests = append(manifests, manifest.Manifest)
+	}
+	response := GetManifestsResponse{Manifests: manifests}
+	return &response, nil
+}
+
+func (s Service) DeleteManifest(request DeleteManifestRequest) error {
+
+	logrus.Debugf("deleting manifest: %s", request.ID)
+
+	if err := s.storage.DeleteManifest(request.ID); err != nil {
+		errMsg := fmt.Sprintf("could not delete manifest with id: %s", request.ID)
+		return util.LoggingErrorMsg(err, errMsg)
+	}
+
+	return nil
+}
diff --git a/pkg/service/manifest/model.go b/pkg/service/manifest/model.go
new file mode 100644
index 0000000..8ad3105
--- /dev/null
+++ b/pkg/service/manifest/model.go
@@ -0,0 +1,38 @@
+package manifest
+
+import (
+	"github.com/TBD54566975/ssi-sdk/credential/exchange"
+	manifestsdk "github.com/TBD54566975/ssi-sdk/credential/manifest"
+)
+
+type CreateManifestRequest struct {
+	Issuer string
+	// A context is optional. If not present, we'll apply default, required context values.
+	Context                string
+	OutputDescriptors      []manifestsdk.OutputDescriptor
+	PresentationDefinition exchange.PresentationDefinition
+}
+
+type CreateManifestResponse struct {
+	Manifest manifestsdk.CredentialManifest
+}
+
+type GetManifestRequest struct {
+	ID string
+}
+
+type GetManifestResponse struct {
+	Manifest manifestsdk.CredentialManifest
+}
+
+type GetManifestByIssuerRequest struct {
+	Issuer string
+}
+
+type GetManifestsResponse struct {
+	Manifests []manifestsdk.CredentialManifest
+}
+
+type DeleteManifestRequest struct {
+	ID string
+}
diff --git a/pkg/service/manifest/storage/bolt.go b/pkg/service/manifest/storage/bolt.go
new file mode 100644
index 0000000..6be43ed
--- /dev/null
+++ b/pkg/service/manifest/storage/bolt.go
@@ -0,0 +1,92 @@
+package storage
+
+import (
+	"fmt"
+	"github.com/goccy/go-json"
+	"github.com/pkg/errors"
+	"github.com/sirupsen/logrus"
+	"github.com/tbd54566975/ssi-service/pkg/storage"
+)
+
+const (
+	namespace = "manifest"
+)
+
+type BoltManifestStorage struct {
+	db *storage.BoltDB
+}
+
+func NewBoltManifestStorage(db *storage.BoltDB) (*BoltManifestStorage, error) {
+	if db == nil {
+		return nil, errors.New("bolt db reference is nil")
+	}
+	return &BoltManifestStorage{db: db}, nil
+}
+
+func (b BoltManifestStorage) StoreManifest(manifest StoredManifest) error {
+	id := manifest.Manifest.ID
+	if id == "" {
+		err := errors.New("could not store manifest without an ID")
+		logrus.WithError(err).Error()
+		return err
+	}
+	manifestBytes, err := json.Marshal(manifest)
+	if err != nil {
+		errMsg := fmt.Sprintf("could not store manifest: %s", id)
+		logrus.WithError(err).Error(errMsg)
+		return errors.Wrapf(err, errMsg)
+	}
+	return b.db.Write(namespace, id, manifestBytes)
+}
+
+func (b BoltManifestStorage) GetManifest(id string) (*StoredManifest, error) {
+	manifestBytes, err := b.db.Read(namespace, id)
+	if err != nil {
+		errMsg := fmt.Sprintf("could not get manifest: %s", id)
+		logrus.WithError(err).Error(errMsg)
+		return nil, errors.Wrapf(err, errMsg)
+	}
+	if len(manifestBytes) == 0 {
+		err := fmt.Errorf("manifest not found with id: %s", id)
+		logrus.WithError(err).Error("could not get manifest from storage")
+		return nil, err
+	}
+	var stored StoredManifest
+	if err := json.Unmarshal(manifestBytes, &stored); err != nil {
+		errMsg := fmt.Sprintf("could not unmarshal stored manifest: %s", id)
+		logrus.WithError(err).Error(errMsg)
+		return nil, errors.Wrapf(err, errMsg)
+	}
+	return &stored, nil
+}
+
+// GetManifests attempts to get all stored manifests. It will return those it can even if it has trouble with some.
+func (b BoltManifestStorage) GetManifests() ([]StoredManifest, error) {
+	gotManifests, err := b.db.ReadAll(namespace)
+	if err != nil {
+		errMsg := "could not get all manifests"
+		logrus.WithError(err).Error(errMsg)
+		return nil, errors.Wrap(err, errMsg)
+	}
+	if len(gotManifests) == 0 {
+		logrus.Info("no manifests to get")
+		return nil, nil
+	}
+	var stored []StoredManifest
+	for _, manifestBytes := range gotManifests {
+		var nextManifest StoredManifest
+		if err := json.Unmarshal(manifestBytes, &nextManifest); err == nil {
+			stored = append(stored, nextManifest)
+		}
+	}
+	return stored, nil
+}
+
+func (b BoltManifestStorage) DeleteManifest(id string) error {
+	if err := b.db.Delete(namespace, id); err != nil {
+		errMsg := fmt.Sprintf("could not delete manifest: %s", id)
+		logrus.WithError(err).Error(errMsg)
+		return errors.Wrapf(err, errMsg)
+	}
+	return nil
+}
diff --git a/pkg/service/manifest/storage/storage.go b/pkg/service/manifest/storage/storage.go
new file mode 100644
index 0000000..b78a39c
--- /dev/null
+++ b/pkg/service/manifest/storage/storage.go
@@ -0,0 +1,42 @@
+package storage
+
+import (
+	"fmt"
+	"github.com/TBD54566975/ssi-sdk/credential/manifest"
+
+	"github.com/tbd54566975/ssi-service/internal/util"
+	"github.com/tbd54566975/ssi-service/pkg/storage"
+)
+
+type StoredManifest struct {
+	ID       string                      `json:"id"`
+	Manifest manifest.CredentialManifest `json:"manifest"`
+	Issuer   string                      `json:"issuer"`
+}
+
+type Storage interface {
+	StoreManifest(manifest StoredManifest) error
+	GetManifest(id string) (*StoredManifest, error)
+	GetManifests() ([]StoredManifest, error)
+	DeleteManifest(id string) error
+}
+
+// NewManifestStorage finds the manifest storage impl for a given ServiceStorage value
+func NewManifestStorage(s storage.ServiceStorage) (Storage, error) {
+	switch s.Type() {
+	case storage.Bolt:
+		gotBolt, ok := s.(*storage.BoltDB)
+		if !ok {
+			errMsg := fmt.Sprintf("trouble instantiating : %s", s.Type())
+			return nil, util.LoggingNewError(errMsg)
+		}
+		boltStorage, err := NewBoltManifestStorage(gotBolt)
+		if err != nil {
+			return nil, util.LoggingErrorMsg(err, "could not instantiate manifest bolt storage")
+		}
+		return boltStorage, err
+	default:
+		errMsg := fmt.Errorf("unsupported storage type: %s", s.Type())
+		return nil, util.LoggingError(errMsg)
+	}
+}
diff --git a/pkg/service/schema/model.go b/pkg/service/schema/model.go
index c083a50..9037e7f 100644
--- a/pkg/service/schema/model.go
+++ b/pkg/service/schema/model.go
@@ -6,10 +6,6 @@ const (
 	Version1 string = "1.0.0"
 )
 
-type GetSchemasResponse struct {
-	Schemas []schema.VCJSONSchema `json:"schemas,omitempty"`
-}
-
 type CreateSchemaRequest struct {
 	Author string            `json:"author" validate:"required"`
 	Name   string            `json:"name" validate:"required"`
@@ -21,10 +17,14 @@ type CreateSchemaResponse struct {
 	Schema schema.VCJSONSchema `json:"schema"`
 }
 
-type GetSchemaByIDRequest struct {
+type GetSchemasResponse struct {
+	Schemas []schema.VCJSONSchema `json:"schemas,omitempty"`
+}
+
+type GetSchemaRequest struct {
 	ID string `json:"id" validate:"required"`
 }
 
-type GetSchemaByIDResponse struct {
+type GetSchemaResponse struct {
 	Schema schema.VCJSONSchema `json:"schema"`
 }
diff --git a/pkg/service/schema/schema.go b/pkg/service/schema/schema.go
index 7749932..40c52c5 100644
--- a/pkg/service/schema/schema.go
+++ b/pkg/service/schema/schema.go
@@ -95,7 +95,7 @@ func (s Service) GetSchemas() (*GetSchemasResponse, error) {
 	}, nil
 }
 
-func (s Service) GetSchemaByID(request GetSchemaByIDRequest) (*GetSchemaByIDResponse, error) {
+func (s Service) GetSchema(request GetSchemaRequest) (*GetSchemaResponse, error) {
 	gotSchema, err := s.storage.GetSchema(request.ID)
 	if err != nil {
 		err := errors.Wrapf(err, "error getting schema: %s", request.ID)
@@ -105,5 +105,5 @@ func (s Service) GetSchemaByID(request GetSchemaByIDRequest) (*GetSchemaByIDResp
 		err := fmt.Errorf("schema with id<%s> could not be found", request.ID)
 		return nil, util.LoggingError(err)
 	}
-	return &GetSchemaByIDResponse{Schema: gotSchema.Schema}, nil
+	return &GetSchemaResponse{Schema: gotSchema.Schema}, nil
 }
diff --git a/pkg/service/service.go b/pkg/service/service.go
index c71e15e..8945daa 100644
--- a/pkg/service/service.go
+++ b/pkg/service/service.go
@@ -2,6 +2,7 @@ package service
 
 import (
 	"fmt"
+	"github.com/tbd54566975/ssi-service/pkg/service/manifest"
 
 	"github.com/tbd54566975/ssi-service/config"
 	"github.com/tbd54566975/ssi-service/internal/util"
@@ -77,5 +78,11 @@ func instantiateServices(config config.ServicesConfig) ([]framework.Service, err
 		return nil, util.LoggingErrorMsg(err, errMsg)
 	}
 
-	return []framework.Service{didService, schemaService, credentialService}, nil
+	manifestService, err := manifest.NewManifestService(config.ManifestConfig, storageProvider)
+	if err != nil {
+		errMsg := "could not instantiate the manifest service"
+		return nil, util.LoggingErrorMsg(err, errMsg)
+	}
+
+	return []framework.Service{didService, schemaService, credentialService, manifestService}, nil
 }


Test patch:
diff --git a/pkg/server/router/manifest_test.go b/pkg/server/router/manifest_test.go
new file mode 100644
index 0000000..57b564b
--- /dev/null
+++ b/pkg/server/router/manifest_test.go
@@ -0,0 +1,95 @@
+package router
+
+import (
+	"github.com/TBD54566975/ssi-sdk/credential/exchange"
+	manifestsdk "github.com/TBD54566975/ssi-sdk/credential/manifest"
+	"github.com/stretchr/testify/assert"
+	"github.com/tbd54566975/ssi-service/config"
+	"github.com/tbd54566975/ssi-service/pkg/service/framework"
+	"github.com/tbd54566975/ssi-service/pkg/service/manifest"
+	"github.com/tbd54566975/ssi-service/pkg/storage"
+	"os"
+	"testing"
+)
+
+func TestManifestRouter(t *testing.T) {
+	// remove the db file after the test
+	t.Cleanup(func() {
+		_ = os.Remove(storage.DBFile)
+	})
+
+	t.Run("Nil Service", func(tt *testing.T) {
+		manifestRouter, err := NewManifestRouter(nil)
+		assert.Error(tt, err)
+		assert.Empty(tt, manifestRouter)
+		assert.Contains(tt, err.Error(), "service cannot be nil")
+	})
+
+	t.Run("Bad Service", func(tt *testing.T) {
+		manifestRouter, err := NewManifestRouter(&testService{})
+		assert.Error(tt, err)
+		assert.Empty(tt, manifestRouter)
+		assert.Contains(tt, err.Error(), "could not create manifest router with service type: test")
+	})
+
+	t.Run("Manifest Service Test", func(tt *testing.T) {
+		bolt, err := storage.NewBoltDB()
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, bolt)
+
+		serviceConfig := config.ManifestServiceConfig{BaseServiceConfig: &config.BaseServiceConfig{Name: "manifest"}}
+		manifestService, err := manifest.NewManifestService(serviceConfig, bolt)
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, manifestService)
+
+		// check type and status
+		assert.Equal(tt, framework.Manifest, manifestService.Type())
+		assert.Equal(tt, framework.StatusReady, manifestService.Status().Status)
+
+		// good request
+		createManifestRequest := getValidManifestRequest()
+
+		createdManifest, err := manifestService.CreateManifest(createManifestRequest)
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, createdManifest)
+		assert.NotEmpty(tt, createdManifest.Manifest)
+	})
+}
+
+func getValidManifestRequest() manifest.CreateManifestRequest {
+	createManifestRequest := manifest.CreateManifestRequest{
+		Issuer:  "did:abc:123",
+		Context: "context123",
+		PresentationDefinition: exchange.PresentationDefinition{
+			ID: "pres-def-id",
+			InputDescriptors: []exchange.InputDescriptor{
+				{
+					ID: "test-id",
+					Constraints: &exchange.Constraints{
+						Fields: []exchange.Field{
+							{
+								Path: []string{".vc.id"},
+							},
+						},
+					},
+				},
+			},
+		},
+		OutputDescriptors: []manifestsdk.OutputDescriptor{
+			{
+				ID:          "id1",
+				Schema:      "https://test.com/schema",
+				Name:        "good ID",
+				Description: "it's all good",
+			},
+			{
+				ID:          "id2",
+				Schema:      "https://test.com/schema",
+				Name:        "good ID",
+				Description: "it's all good",
+			},
+		},
+	}
+
+	return createManifestRequest
+}
diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go
index 5d29aaa..7af4d71 100644
--- a/pkg/server/router/router_test.go
+++ b/pkg/server/router/router_test.go
@@ -25,5 +25,6 @@ func (s *testService) Config() config.ServicesConfig {
 		SchemaConfig:     config.SchemaServiceConfig{},
 		CredentialConfig: config.CredentialServiceConfig{},
 		KeyStoreConfig:   config.KeyStoreServiceConfig{},
+		ManifestConfig:   config.ManifestServiceConfig{},
 	}
 }
diff --git a/pkg/server/router/schema_test.go b/pkg/server/router/schema_test.go
index 5c9aaf3..4c7151e 100644
--- a/pkg/server/router/schema_test.go
+++ b/pkg/server/router/schema_test.go
@@ -52,7 +52,7 @@ func TestSchemaRouter(t *testing.T) {
 		assert.Equal(tt, 0, len(gotSchemas.Schemas))
 
 		// get schema that doesn't exist
-		_, err = schemaService.GetSchemaByID(schema.GetSchemaByIDRequest{ID: "bad"})
+		_, err = schemaService.GetSchema(schema.GetSchemaRequest{ID: "bad"})
 		assert.Error(tt, err)
 		assert.Contains(tt, err.Error(), "error getting schema")
 
@@ -75,7 +75,7 @@ func TestSchemaRouter(t *testing.T) {
 		assert.Equal(tt, "simple schema", createdSchema.Schema.Name)
 
 		// get schema by ID
-		gotSchema, err := schemaService.GetSchemaByID(schema.GetSchemaByIDRequest{ID: createdSchema.ID})
+		gotSchema, err := schemaService.GetSchema(schema.GetSchemaRequest{ID: createdSchema.ID})
 		assert.NoError(tt, err)
 		assert.NotEmpty(tt, gotSchema)
 		assert.EqualValues(tt, createdSchema.Schema, gotSchema.Schema)
diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go
index 2966d89..6854dc8 100644
--- a/pkg/server/server_test.go
+++ b/pkg/server/server_test.go
@@ -4,6 +4,8 @@ import (
 	"bytes"
 	"context"
 	"fmt"
+	"github.com/TBD54566975/ssi-sdk/credential/exchange"
+
 	"io"
 	"net/http"
 	"net/http/httptest"
@@ -12,6 +14,7 @@ import (
 	"time"
 
 	credsdk "github.com/TBD54566975/ssi-sdk/credential"
+	manifestsdk "github.com/TBD54566975/ssi-sdk/credential/manifest"
 	"github.com/TBD54566975/ssi-sdk/crypto"
 	"github.com/dimfeld/httptreemux/v5"
 	"github.com/goccy/go-json"
@@ -27,6 +30,7 @@ import (
 	"github.com/tbd54566975/ssi-service/pkg/service/did"
 	svcframework "github.com/tbd54566975/ssi-service/pkg/service/framework"
 	"github.com/tbd54566975/ssi-service/pkg/service/keystore"
+	"github.com/tbd54566975/ssi-service/pkg/service/manifest"
 	"github.com/tbd54566975/ssi-service/pkg/service/schema"
 	"github.com/tbd54566975/ssi-service/pkg/storage"
 )
@@ -309,7 +313,7 @@ func TestSchemaAPI(t *testing.T) {
 		// get schema that doesn't exist
 		w := httptest.NewRecorder()
 		req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/schemas/bad", nil)
-		err = schemaService.GetSchemaByID(newRequestContext(), w, req)
+		err = schemaService.GetSchema(newRequestContext(), w, req)
 		assert.Error(tt, err)
 		assert.Contains(tt, err.Error(), "cannot get schema without ID parameter")
 
@@ -318,7 +322,7 @@ func TestSchemaAPI(t *testing.T) {
 
 		// get schema with invalid id
 		req = httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/schemas/bad", nil)
-		err = schemaService.GetSchemaByID(newRequestContextWithParams(map[string]string{"id": "bad"}), w, req)
+		err = schemaService.GetSchema(newRequestContextWithParams(map[string]string{"id": "bad"}), w, req)
 		assert.Error(tt, err)
 		assert.Contains(tt, err.Error(), "could not get schema with id: bad")
 
@@ -367,7 +371,7 @@ func TestSchemaAPI(t *testing.T) {
 
 		// get it back
 		req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/schemas/%s", createResp.ID), nil)
-		err = schemaService.GetSchemaByID(newRequestContextWithParams(map[string]string{"id": createResp.ID}), w, req)
+		err = schemaService.GetSchema(newRequestContextWithParams(map[string]string{"id": createResp.ID}), w, req)
 		assert.NoError(tt, err)
 
 		var gotSchemaResp router.GetSchemaResponse
@@ -732,6 +736,242 @@ func newCredentialService(t *testing.T, bolt *storage.BoltDB) *router.Credential
 	return credentialRouter
 }
 
+func TestManifestAPI(t *testing.T) {
+	t.Run("Test Create Manifest", func(tt *testing.T) {
+		bolt, err := storage.NewBoltDB()
+
+		// remove the db file after the test
+		tt.Cleanup(func() {
+			_ = bolt.Close()
+			_ = os.Remove(storage.DBFile)
+		})
+
+		manifestService := newManifestService(tt, bolt)
+
+		// missing required field: OutputDescriptors
+		badManifestRequest := router.CreateManifestRequest{
+			Issuer: "did:abc:123",
+		}
+
+		badRequestValue := newRequestValue(tt, badManifestRequest)
+		req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests", badRequestValue)
+		w := httptest.NewRecorder()
+
+		err = manifestService.CreateManifest(newRequestContext(), w, req)
+		assert.Error(tt, err)
+		assert.Contains(tt, err.Error(), "invalid create manifest request")
+
+		// reset the http recorder
+		w.Flush()
+
+		// good request
+		createManifestRequest := router.CreateManifestRequest{
+			Issuer:  "did:abc:123",
+			Context: "context123",
+			PresentationDefinition: exchange.PresentationDefinition{
+				ID: "pres-def-id",
+				InputDescriptors: []exchange.InputDescriptor{
+					{
+						ID: "test-id",
+						Constraints: &exchange.Constraints{
+							Fields: []exchange.Field{
+								{
+									Path: []string{".vc.id"},
+								},
+							},
+						},
+					},
+				},
+			},
+			OutputDescriptors: []manifestsdk.OutputDescriptor{
+				{
+					ID:          "id1",
+					Schema:      "https://test.com/schema",
+					Name:        "good ID",
+					Description: "it's all good",
+				},
+				{
+					ID:          "id2",
+					Schema:      "https://test.com/schema",
+					Name:        "good ID",
+					Description: "it's all good",
+				},
+			},
+		}
+
+		requestValue := newRequestValue(tt, createManifestRequest)
+		req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests", requestValue)
+		err = manifestService.CreateManifest(newRequestContext(), w, req)
+		assert.NoError(tt, err)
+
+		var resp router.CreateManifestResponse
+		err = json.NewDecoder(w.Body).Decode(&resp)
+		assert.NoError(tt, err)
+
+		assert.NotEmpty(tt, resp.Manifest)
+		assert.Equal(tt, resp.Manifest.Issuer.ID, "did:abc:123")
+	})
+
+	t.Run("Test Get Manifest By ID", func(tt *testing.T) {
+		bolt, err := storage.NewBoltDB()
+
+		// remove the db file after the test
+		tt.Cleanup(func() {
+			_ = bolt.Close()
+			_ = os.Remove(storage.DBFile)
+		})
+
+		manifestService := newManifestService(tt, bolt)
+
+		w := httptest.NewRecorder()
+
+		// get a manifest that doesn't exit
+		req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/manifests/bad", nil)
+		err = manifestService.GetManifest(newRequestContext(), w, req)
+		assert.Error(tt, err)
+		assert.Contains(tt, err.Error(), "cannot get manifest without ID parameter")
+
+		// reset recorder between calls
+		w.Flush()
+
+		// get a manifest with an invalid id parameter
+		req = httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/manifests/bad", nil)
+		err = manifestService.GetManifest(newRequestContextWithParams(map[string]string{"id": "bad"}), w, req)
+		assert.Error(tt, err)
+		assert.Contains(tt, err.Error(), "could not get manifest with id: bad")
+
+		// reset recorder between calls
+		w.Flush()
+
+		// good request
+		createManifestRequest := getValidManifestRequest()
+
+		requestValue := newRequestValue(tt, createManifestRequest)
+		req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests", requestValue)
+		err = manifestService.CreateManifest(newRequestContext(), w, req)
+		assert.NoError(tt, err)
+
+		var resp router.CreateManifestResponse
+		err = json.NewDecoder(w.Body).Decode(&resp)
+		assert.NoError(tt, err)
+
+		// get manifest by id
+		req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/%s", resp.Manifest.ID), nil)
+		err = manifestService.GetManifest(newRequestContextWithParams(map[string]string{"id": resp.Manifest.ID}), w, req)
+		assert.NoError(tt, err)
+
+		var getManifestResp router.GetManifestResponse
+		err = json.NewDecoder(w.Body).Decode(&getManifestResp)
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, getManifestResp)
+		assert.Equal(tt, resp.Manifest.ID, getManifestResp.ID)
+	})
+
+	t.Run("Test Get Manifests", func(tt *testing.T) {
+		bolt, err := storage.NewBoltDB()
+
+		// remove the db file after the test
+		tt.Cleanup(func() {
+			_ = bolt.Close()
+			_ = os.Remove(storage.DBFile)
+		})
+
+		manifestService := newManifestService(tt, bolt)
+
+		w := httptest.NewRecorder()
+
+		// good request
+		createManifestRequest := getValidManifestRequest()
+
+		requestValue := newRequestValue(tt, createManifestRequest)
+		req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests", requestValue)
+		err = manifestService.CreateManifest(newRequestContext(), w, req)
+		assert.NoError(tt, err)
+
+		var resp router.CreateManifestResponse
+		err = json.NewDecoder(w.Body).Decode(&resp)
+		assert.NoError(tt, err)
+
+		// get manifest by id
+		req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests"), nil)
+		err = manifestService.GetManifests(newRequestContext(), w, req)
+		assert.NoError(tt, err)
+
+		var getManifestsResp router.GetManifestsResponse
+		err = json.NewDecoder(w.Body).Decode(&getManifestsResp)
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, getManifestsResp)
+		assert.Len(tt, getManifestsResp.Manifests, 1)
+		assert.Equal(tt, resp.Manifest.ID, getManifestsResp.Manifests[0].ID)
+	})
+
+	t.Run("Test Delete Manifest", func(tt *testing.T) {
+		bolt, err := storage.NewBoltDB()
+
+		// remove the db file after the test
+		tt.Cleanup(func() {
+			_ = bolt.Close()
+			_ = os.Remove(storage.DBFile)
+		})
+
+		manifestService := newManifestService(tt, bolt)
+
+		// good request
+		createManifestRequest := getValidManifestRequest()
+
+		requestValue := newRequestValue(tt, createManifestRequest)
+		req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/manifests", requestValue)
+		w := httptest.NewRecorder()
+		err = manifestService.CreateManifest(newRequestContext(), w, req)
+		assert.NoError(tt, err)
+
+		var resp router.CreateManifestResponse
+		err = json.NewDecoder(w.Body).Decode(&resp)
+		assert.NoError(tt, err)
+
+		w.Flush()
+
+		// get credential by id
+		req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/%s", resp.Manifest.ID), nil)
+		err = manifestService.GetManifest(newRequestContextWithParams(map[string]string{"id": resp.Manifest.ID}), w, req)
+		assert.NoError(tt, err)
+
+		var getManifestResp router.GetCredentialResponse
+		err = json.NewDecoder(w.Body).Decode(&getManifestResp)
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, getManifestResp)
+		assert.Equal(tt, resp.Manifest.ID, getManifestResp.ID)
+
+		w.Flush()
+
+		// delete it
+		req = httptest.NewRequest(http.MethodDelete, fmt.Sprintf("https://ssi-service.com/v1/manifests/%s", resp.Manifest.ID), nil)
+		err = manifestService.DeleteManifest(newRequestContextWithParams(map[string]string{"id": resp.Manifest.ID}), w, req)
+		assert.NoError(tt, err)
+
+		w.Flush()
+
+		// get it back
+		req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/manifests/%s", resp.Manifest.ID), nil)
+		err = manifestService.GetManifest(newRequestContextWithParams(map[string]string{"id": resp.Manifest.ID}), w, req)
+		assert.Error(tt, err)
+		assert.Contains(tt, err.Error(), fmt.Sprintf("could not get manifest with id: %s", resp.Manifest.ID))
+	})
+}
+
+func newManifestService(t *testing.T, bolt *storage.BoltDB) *router.ManifestRouter {
+	manifestService, err := manifest.NewManifestService(config.ManifestServiceConfig{}, bolt)
+	require.NoError(t, err)
+	require.NotEmpty(t, manifestService)
+
+	// create router for service
+	manifestRouter, err := router.NewManifestRouter(manifestService)
+	require.NoError(t, err)
+	require.NotEmpty(t, manifestRouter)
+
+	return manifestRouter
+}
+
 func TestKeyStoreAPI(t *testing.T) {
 	t.Run("Test Store Key", func(tt *testing.T) {
 		bolt, err := storage.NewBoltDB()
@@ -870,3 +1110,41 @@ func newRequestContextWithParams(params map[string]string) context.Context {
 	})
 	return httptreemux.AddParamsToContext(ctx, params)
 }
+
+func getValidManifestRequest() router.CreateManifestRequest {
+	createManifestRequest := router.CreateManifestRequest{
+		Issuer:  "did:abc:123",
+		Context: "context123",
+		PresentationDefinition: exchange.PresentationDefinition{
+			ID: "pres-def-id",
+			InputDescriptors: []exchange.InputDescriptor{
+				{
+					ID: "test-id",
+					Constraints: &exchange.Constraints{
+						Fields: []exchange.Field{
+							{
+								Path: []string{".vc.id"},
+							},
+						},
+					},
+				},
+			},
+		},
+		OutputDescriptors: []manifestsdk.OutputDescriptor{
+			{
+				ID:          "id1",
+				Schema:      "https://test.com/schema",
+				Name:        "good ID",
+				Description: "it's all good",
+			},
+			{
+				ID:          "id2",
+				Schema:      "https://test.com/schema",
+				Name:        "good ID",
+				Description: "it's all good",
+			},
+		},
+	}
+
+	return createManifestRequest
+}