Repository: TBD54566975/ssi-service
Pull request #23 (2022-04-06 18:40:16)

VC JSON Schema Support
Add support for creating and getting VC JSON Schemas

decentralgabe: merging. please leave comments if you have them and I will return to them in a future PR.

Patch:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index b7b9710..ebbfeef 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -28,6 +28,8 @@ jobs:
       actions: read
       contents: read
       security-events: write
+    env:
+      GOFLAGS: "-tags=jwx_es256k"
 
     strategy:
       fail-fast: false
@@ -55,16 +57,5 @@ jobs:
     - name: Autobuild
       uses: github/codeql-action/autobuild@v2
 
-    # ℹ️ Command-line programs to run using the OS shell.
-    # 📚 https://git.io/JvXDl
-
-    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
-    #    and modify them (or add more) to build your code if your project
-    #    uses a compiled language
-
-    #- run: |
-    #   make bootstrap
-    #   make release
-
     - name: Perform CodeQL Analysis
       uses: github/codeql-action/analyze@v2
diff --git a/go.mod b/go.mod
index ef0550f..ecf7e34 100644
--- a/go.mod
+++ b/go.mod
@@ -3,7 +3,7 @@ module github.com/tbd54566975/ssi-service
 go 1.17
 
 require (
-	github.com/TBD54566975/ssi-sdk v0.0.0-20220403173431-39ff8ebd3825
+	github.com/TBD54566975/ssi-sdk v0.0.0-20220419032604-6292424ef7ae
 	github.com/ardanlabs/conf v1.5.0
 	github.com/boltdb/bolt v1.3.1
 	github.com/dimfeld/httptreemux/v5 v5.4.0
@@ -15,25 +15,25 @@ require (
 	github.com/pkg/errors v0.9.1
 	github.com/stretchr/testify v1.7.1
 	go.opentelemetry.io/otel/trace v1.6.3
-	golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
+	golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
 	gopkg.in/go-playground/validator.v9 v9.31.0
 )
 
 require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
-	github.com/go-playground/validator/v10 v10.10.0 // indirect
+	github.com/go-playground/validator/v10 v10.10.1 // indirect
 	github.com/gobuffalo/logger v1.0.6 // indirect
 	github.com/gobuffalo/packd v1.0.1 // indirect
 	github.com/gobuffalo/packr/v2 v2.8.3 // indirect
-	github.com/goccy/go-json v0.9.4 // indirect
+	github.com/goccy/go-json v0.9.6 // indirect
 	github.com/karrick/godirwalk v1.16.1 // indirect
 	github.com/leodido/go-urn v1.2.1 // indirect
 	github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
 	github.com/lestrrat-go/blackmagic v1.0.0 // indirect
-	github.com/lestrrat-go/httpcc v1.0.0 // indirect
+	github.com/lestrrat-go/httpcc v1.0.1 // indirect
 	github.com/lestrrat-go/iter v1.0.1 // indirect
-	github.com/lestrrat-go/jwx v1.2.19 // indirect
+	github.com/lestrrat-go/jwx v1.2.23 // indirect
 	github.com/lestrrat-go/option v1.0.0 // indirect
 	github.com/markbates/errx v1.1.0 // indirect
 	github.com/markbates/oncer v1.0.0 // indirect
@@ -41,16 +41,19 @@ require (
 	github.com/multiformats/go-base32 v0.0.3 // indirect
 	github.com/multiformats/go-base36 v0.1.0 // indirect
 	github.com/multiformats/go-multibase v0.0.3 // indirect
-	github.com/multiformats/go-multicodec v0.4.0 // indirect
+	github.com/multiformats/go-multicodec v0.4.1 // indirect
 	github.com/multiformats/go-varint v0.0.6 // indirect
 	github.com/piprate/json-gold v0.4.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
 	github.com/sirupsen/logrus v1.8.1 // indirect
+	github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
+	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
+	github.com/xeipuuv/gojsonschema v1.2.0 // indirect
 	go.opentelemetry.io/otel v1.6.3 // indirect
-	golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f // indirect
-	golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
-	golang.org/x/text v0.3.6 // indirect
+	golang.org/x/sys v0.0.0-20220412211240-33da011f77ad // indirect
+	golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect
+	golang.org/x/text v0.3.7 // indirect
 	gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 )
diff --git a/go.sum b/go.sum
index f37c433..82c0422 100644
--- a/go.sum
+++ b/go.sum
@@ -39,8 +39,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
-github.com/TBD54566975/ssi-sdk v0.0.0-20220403173431-39ff8ebd3825 h1:IRup2wdgV5IvPZKx+XlCbJVDy28Pa60rxdA147NuE5I=
-github.com/TBD54566975/ssi-sdk v0.0.0-20220403173431-39ff8ebd3825/go.mod h1:6dpqc6LQ8EQVhZvrk1beTosnLqD5iR7jEorZHLpKUF0=
+github.com/TBD54566975/ssi-sdk v0.0.0-20220419032604-6292424ef7ae h1:8f4RIwlopUS2hmj1ohvRjVnftZGVQzubNenZb/9q+sY=
+github.com/TBD54566975/ssi-sdk v0.0.0-20220419032604-6292424ef7ae/go.mod h1:dZf0M3fNzzqFH31ePuA8XfWQTS0RKEn6cDSjcwS+Ls0=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/ardanlabs/conf v1.5.0 h1:5TwP6Wu9Xi07eLFEpiCUF3oQXh9UzHMDVnD3u/I5d5c=
 github.com/ardanlabs/conf v1.5.0/go.mod h1:ILsMo9dMqYzCxDjDXTiwMI0IgxOJd0MOiucbQY2wlJw=
@@ -100,8 +100,8 @@ github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb
 github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
 github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
 github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
-github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0=
-github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
+github.com/go-playground/validator/v10 v10.10.1 h1:uA0+amWMiglNZKZ9FJRKUAe9U3RX91eVn1JYXMWt7ig=
+github.com/go-playground/validator/v10 v10.10.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
 github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
 github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
 github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU=
@@ -114,8 +114,8 @@ github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIavi
 github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
 github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY=
 github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc=
-github.com/goccy/go-json v0.9.4 h1:L8MLKG2mvVXiQu07qB6hmfqeSYQdOnqPot2GhsIwIaI=
-github.com/goccy/go-json v0.9.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
+github.com/goccy/go-json v0.9.6 h1:5/4CtRQdtsX0sal8fdVhTaiMN01Ri8BExZZ8iRmHQ6E=
+github.com/goccy/go-json v0.9.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -235,12 +235,12 @@ github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf
 github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
 github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4=
 github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
-github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkOpYc=
-github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
+github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
+github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
 github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A=
 github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
-github.com/lestrrat-go/jwx v1.2.19 h1:qxxLmAXNwZpTTvjc4PH21nT7I4wPK6lVv3lVNcZPnUk=
-github.com/lestrrat-go/jwx v1.2.19/go.mod h1:bWTBO7IHHVMtNunM8so9MT8wD+euEY1PzGEyCnuI2qM=
+github.com/lestrrat-go/jwx v1.2.23 h1:8oP5fY1yzCRraUNNyfAVdOkLCqY7xMZz11lVcvHqC1Y=
+github.com/lestrrat-go/jwx v1.2.23/go.mod h1:sAXjRwzSvCN6soO4RLoWWm1bVPpb8iOuv0IYfH8OWd8=
 github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
 github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
 github.com/magefile/mage v1.13.0 h1:XtLJl8bcCM7EFoO8FyH8XK3t7G5hQAeK+i4tq+veT9M=
@@ -278,8 +278,8 @@ github.com/multiformats/go-base36 v0.1.0 h1:JR6TyF7JjGd3m6FbLU2cOxhC0Li8z8dLNGQ8
 github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM=
 github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77RblWplfIqk=
 github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc=
-github.com/multiformats/go-multicodec v0.4.0 h1:fbqb6ky7erjdD+/zaEBJgZWu1i8D6i/wmPywGK7sdow=
-github.com/multiformats/go-multicodec v0.4.0/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ=
+github.com/multiformats/go-multicodec v0.4.1 h1:BSJbf+zpghcZMZrwTYBGwy0CPcVZGWiC72Cp8bBd4R4=
+github.com/multiformats/go-multicodec v0.4.1/go.mod h1:1Hj/eHRaVWSXiSNNfcEPcwZleTmdNP81xlxDLnWU9GQ=
 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/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4=
@@ -339,8 +339,11 @@ github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMT
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
 github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
 github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -375,11 +378,10 @@ golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8U
 golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
-golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
-golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
+golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -526,13 +528,12 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f h1:rlezHXNlxYWvBCzNses9Dlc7nGFaNMJeqLolcmQSSZY=
-golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
+golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -540,8 +541,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff --git a/internal/util/util.go b/internal/util/util.go
new file mode 100644
index 0000000..0fed26c
--- /dev/null
+++ b/internal/util/util.go
@@ -0,0 +1,15 @@
+package util
+
+import (
+	"fmt"
+	"strings"
+)
+
+// GetMethodForDID gets a DID method from a did, the second part of the did (e.g. did:test:abcd, the method is 'test')
+func GetMethodForDID(did string) (string, error) {
+	split := strings.Split(did, ":")
+	if len(split) < 3 {
+		return "", fmt.Errorf("malformed did: %s", did)
+	}
+	return split[1], nil
+}
diff --git a/pkg/server/router/did.go b/pkg/server/router/did.go
index 3507fd0..4c52729 100644
--- a/pkg/server/router/did.go
+++ b/pkg/server/router/did.go
@@ -20,8 +20,8 @@ const (
 
 // DIDRouter represents the dependencies required to instantiate a DID-HTTP service
 type DIDRouter struct {
-	did.Service
-	*log.Logger
+	service *did.Service
+	logger  *log.Logger
 }
 
 // NewDIDRouter creates an HTP router for the DID Service
@@ -34,8 +34,8 @@ func NewDIDRouter(s svcframework.Service, l *log.Logger) (*DIDRouter, error) {
 		return nil, fmt.Errorf("could not create DID router with service type: %s", s.Type())
 	}
 	return &DIDRouter{
-		Service: *didService,
-		Logger:  l,
+		service: didService,
+		logger:  l,
 	}, nil
 }
 
@@ -43,9 +43,9 @@ type GetDIDMethodsResponse struct {
 	DIDMethods []did.Method `json:"didMethods,omitempty"`
 }
 
-func (s DIDRouter) GetDIDMethods(ctx context.Context, w http.ResponseWriter, _ *http.Request) error {
-	methods := s.GetSupportedMethods()
-	response := GetDIDMethodsResponse{DIDMethods: methods}
+func (dr DIDRouter) GetDIDMethods(ctx context.Context, w http.ResponseWriter, _ *http.Request) error {
+	methods := dr.service.GetSupportedMethods()
+	response := GetDIDMethodsResponse{DIDMethods: methods.Methods}
 	return framework.Respond(ctx, w, response, http.StatusOK)
 }
 
@@ -58,34 +58,27 @@ type CreateDIDByMethodResponse struct {
 	PrivateKey string             `json:"privateKeyBase58,omitempty"`
 }
 
-func (s DIDRouter) CreateDIDByMethod(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
+func (dr DIDRouter) CreateDIDByMethod(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
 	method := framework.GetParam(ctx, MethodParam)
 	if method == nil {
 		errMsg := "create DID request missing method parameter"
-		s.Logger.Printf(errMsg)
+		dr.logger.Printf(errMsg)
 		return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest)
 	}
 
 	var request CreateDIDByMethodRequest
 	if err := framework.Decode(r, &request); err != nil {
 		errMsg := "invalid create DID request"
-		s.Logger.Printf(errMsg)
-		return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest)
-	}
-
-	// TODO(gabe) check if the method is supported, to tell whether this is a bad req or internal error
-	handler, err := s.GetHandler(did.Method(*method))
-	if err != nil {
-		errMsg := fmt.Sprintf("could not get handler for method<%s>", *method)
-		s.Logger.Printf(errMsg)
+		dr.logger.Printf(errors.Wrap(err, errMsg).Error())
 		return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest)
 	}
 
 	// TODO(gabe) check if the key type is supported for the method, to tell whether this is a bad req or internal error
-	createDIDResponse, err := handler.CreateDID(did.CreateDIDRequest{KeyType: request.KeyType})
+	createDIDRequest := did.CreateDIDRequest{Method: did.Method(*method), KeyType: request.KeyType}
+	createDIDResponse, err := dr.service.CreateDIDByMethod(createDIDRequest)
 	if err != nil {
 		errMsg := fmt.Sprintf("could not create DID for method<%s> with key type: %s", *method, request.KeyType)
-		s.Logger.Printf(errMsg)
+		dr.logger.Printf(errMsg)
 		return framework.NewRequestErrorMsg(errMsg, http.StatusInternalServerError)
 	}
 
@@ -93,41 +86,35 @@ func (s DIDRouter) CreateDIDByMethod(ctx context.Context, w http.ResponseWriter,
 		DID:        createDIDResponse.DID,
 		PrivateKey: createDIDResponse.PrivateKey,
 	}
-	return framework.Respond(ctx, w, resp, http.StatusOK)
+	return framework.Respond(ctx, w, resp, http.StatusCreated)
 }
 
 type GetDIDByMethodResponse struct {
 	DID didsdk.DIDDocument `json:"did,omitempty"`
 }
 
-func (s DIDRouter) GetDIDByMethod(ctx context.Context, w http.ResponseWriter, _ *http.Request) error {
+func (dr DIDRouter) GetDIDByMethod(ctx context.Context, w http.ResponseWriter, _ *http.Request) error {
 	method := framework.GetParam(ctx, MethodParam)
 	if method == nil {
 		errMsg := "get DID by method request missing method parameter"
-		s.Logger.Printf(errMsg)
+		dr.logger.Printf(errMsg)
 		return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest)
 	}
 	id := framework.GetParam(ctx, IDParam)
 	if id == nil {
 		errMsg := fmt.Sprintf("get DID request missing id parameter for method: %s", *method)
-		s.Logger.Printf(errMsg)
+		dr.logger.Printf(errMsg)
 		return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest)
 	}
 
 	// TODO(gabe) check if the method is supported, to tell whether this is a bad req or internal error
-	handler, err := s.GetHandler(did.Method(*method))
-	if err != nil {
-		errMsg := fmt.Sprintf("could not get handler for method<%s>", *method)
-		s.Logger.Printf(errMsg)
-		return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest)
-	}
-
 	// TODO(gabe) differentiate between internal errors and not found DIDs
-	gotDID, err := handler.GetDID(*id)
+	getDIDRequest := did.GetDIDRequest{Method: did.Method(*method), ID: *id}
+	gotDID, err := dr.service.GetDIDByMethod(getDIDRequest)
 	if err != nil {
 		errMsg := fmt.Sprintf("could not get DID for method<%s> with id: %s", *method, *id)
-		s.Logger.Printf(errMsg)
-		return framework.NewRequestErrorMsg(errMsg, http.StatusNotFound)
+		dr.logger.Printf(errors.Wrap(err, errMsg).Error())
+		return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest)
 	}
 
 	resp := GetDIDByMethodResponse{DID: gotDID.DID}
diff --git a/pkg/server/router/schema.go b/pkg/server/router/schema.go
new file mode 100644
index 0000000..c6eee4b
--- /dev/null
+++ b/pkg/server/router/schema.go
@@ -0,0 +1,102 @@
+package router
+
+import (
+	"context"
+	"fmt"
+	schemalib "github.com/TBD54566975/ssi-sdk/credential/schema"
+	"github.com/pkg/errors"
+	"github.com/tbd54566975/ssi-service/pkg/server/framework"
+	svcframework "github.com/tbd54566975/ssi-service/pkg/service/framework"
+	"github.com/tbd54566975/ssi-service/pkg/service/schema"
+	"log"
+	"net/http"
+)
+
+type SchemaRouter struct {
+	service *schema.Service
+	logger  *log.Logger
+}
+
+func NewSchemaRouter(s svcframework.Service, l *log.Logger) (*SchemaRouter, error) {
+	if s == nil {
+		return nil, errors.New("service cannot be nil")
+	}
+	schemaService, ok := s.(*schema.Service)
+	if !ok {
+		return nil, fmt.Errorf("could not create schema router with service type: %s", s.Type())
+	}
+	return &SchemaRouter{
+		service: schemaService,
+		logger:  l,
+	}, nil
+}
+
+type CreateSchemaRequest struct {
+	Author string               `json:"author" validate:"required"`
+	Name   string               `json:"name" validate:"required"`
+	Schema schemalib.JSONSchema `json:"schema" validate:"required"`
+}
+
+type CreateSchemaResponse struct {
+	ID     string                 `json:"id"`
+	Schema schemalib.VCJSONSchema `json:"schema"`
+}
+
+func (sr SchemaRouter) CreateSchema(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
+	var request CreateSchemaRequest
+	if err := framework.Decode(r, &request); err != nil {
+		errMsg := "invalid create schema request"
+		sr.logger.Printf(errors.Wrap(err, errMsg).Error())
+		return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest)
+	}
+
+	req := schema.CreateSchemaRequest{Author: request.Author, Name: request.Name, Schema: request.Schema}
+	createSchemaResponse, err := sr.service.CreateSchema(req)
+	if err != nil {
+		errMsg := fmt.Sprintf("could not create schema with authoring DID: %s", request.Author)
+		sr.logger.Printf(errMsg)
+		return framework.NewRequestErrorMsg(errMsg, http.StatusInternalServerError)
+	}
+
+	resp := CreateSchemaResponse{ID: createSchemaResponse.ID, Schema: createSchemaResponse.Schema}
+	return framework.Respond(ctx, w, resp, http.StatusCreated)
+}
+
+type GetSchemasResponse struct {
+	Schemas []schemalib.VCJSONSchema `json:"schemas,omitempty"`
+}
+
+func (sr SchemaRouter) GetSchemas(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
+	gotSchemas, err := sr.service.GetSchemas()
+	if err != nil {
+		errMsg := "could not get schemas"
+		sr.logger.Printf(errors.Wrap(err, errMsg).Error())
+		return framework.NewRequestErrorMsg(errMsg, http.StatusInternalServerError)
+	}
+	resp := GetSchemasResponse{Schemas: gotSchemas.Schemas}
+	return framework.Respond(ctx, w, resp, http.StatusOK)
+}
+
+type GetSchemaResponse struct {
+	Schema schemalib.VCJSONSchema `json:"schema,omitempty"`
+}
+
+func (sr SchemaRouter) GetSchemaByID(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"
+		sr.logger.Printf(errMsg)
+		return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest)
+	}
+
+	// TODO(gabe) differentiate between internal errors and not found schemas
+	gotSchema, err := sr.service.GetSchemaByID(schema.GetSchemaByIDRequest{ID: *id})
+	if err != nil {
+		errMsg := fmt.Sprintf("could not get schema with id: %s", *id)
+		sr.logger.Printf(errors.Wrap(err, errMsg).Error())
+		return framework.NewRequestErrorMsg(errMsg, http.StatusBadRequest)
+	}
+
+	resp := GetSchemaResponse{Schema: gotSchema.Schema}
+	return framework.Respond(ctx, w, resp, http.StatusOK)
+}
diff --git a/pkg/server/server.go b/pkg/server/server.go
index 4571bfb..874cf29 100644
--- a/pkg/server/server.go
+++ b/pkg/server/server.go
@@ -18,8 +18,9 @@ import (
 )
 
 const (
-	V1Prefix   = "/v1"
-	DIDsPrefix = "/dids"
+	V1Prefix      = "/v1"
+	DIDsPrefix    = "/dids"
+	SchemasPrefix = "/schemas"
 )
 
 // SSIServer exposes all dependencies needed to run a http server and all its services
@@ -74,6 +75,8 @@ func (s *SSIServer) instantiateRouter(service svcframework.Service) error {
 	switch serviceType {
 	case svcframework.DID:
 		return s.DecentralizedIdentityAPI(service)
+	case svcframework.Schema:
+		return s.SchemaAPI(service)
 	default:
 		return fmt.Errorf("could not instantiate API for service: %s", serviceType)
 	}
@@ -93,3 +96,18 @@ func (s *SSIServer) DecentralizedIdentityAPI(service svcframework.Service) error
 	s.Handle(http.MethodGet, path.Join(handlerPath, "/:method/:id"), didRouter.GetDIDByMethod)
 	return nil
 }
+
+// SchemaAPI registers all HTTP router for the Schema Service
+func (s *SSIServer) SchemaAPI(service svcframework.Service) error {
+	schemaRouter, err := router.NewSchemaRouter(service, s.Logger)
+	if err != nil {
+		return errors.Wrap(err, "could not create Schema router")
+	}
+
+	handlerPath := V1Prefix + SchemasPrefix
+
+	s.Handle(http.MethodPut, handlerPath, schemaRouter.CreateSchema)
+	s.Handle(http.MethodGet, handlerPath, schemaRouter.GetSchemas)
+	s.Handle(http.MethodGet, path.Join(handlerPath, "/:id"), schemaRouter.GetSchemaByID)
+	return nil
+}
diff --git a/pkg/service/did/did.go b/pkg/service/did/did.go
index bfd70b7..f0edfcd 100644
--- a/pkg/service/did/did.go
+++ b/pkg/service/did/did.go
@@ -19,7 +19,7 @@ type Service struct {
 	// supported DID methods
 	handlers map[Method]MethodHandler
 	storage  didstorage.Storage
-	log      *log.Logger
+	logger   *log.Logger
 }
 
 func (s Service) Type() framework.Type {
@@ -37,15 +37,35 @@ func (s Service) Status() framework.Status {
 	return framework.Status{Status: framework.StatusReady}
 }
 
-func (s Service) GetSupportedMethods() []Method {
+func (s Service) GetSupportedMethods() GetSupportedMethodsResponse {
 	var methods []Method
 	for method := range s.handlers {
 		methods = append(methods, method)
 	}
-	return methods
+	return GetSupportedMethodsResponse{Methods: methods}
 }
 
-func (s Service) GetHandler(method Method) (MethodHandler, error) {
+func (s Service) CreateDIDByMethod(request CreateDIDRequest) (*CreateDIDResponse, error) {
+	handler, err := s.getHandler(request.Method)
+	if err != nil {
+		errMsg := fmt.Sprintf("could not get handler for method<%s>", request.Method)
+		s.logger.Printf(errMsg)
+		return nil, errors.New(errMsg)
+	}
+	return handler.CreateDID(request)
+}
+
+func (s Service) GetDIDByMethod(request GetDIDRequest) (*GetDIDResponse, error) {
+	handler, err := s.getHandler(request.Method)
+	if err != nil {
+		errMsg := fmt.Sprintf("could not get handler for method<%s>", request.Method)
+		s.logger.Printf(errMsg)
+		return nil, errors.New(errMsg)
+	}
+	return handler.GetDID(request)
+}
+
+func (s Service) getHandler(method Method) (MethodHandler, error) {
 	handler, ok := s.handlers[method]
 	if !ok {
 		return nil, fmt.Errorf("could not get handler for DID method: %s", method)
@@ -56,18 +76,18 @@ func (s Service) GetHandler(method Method) (MethodHandler, error) {
 // MethodHandler describes the functionality of *all* possible DID service, regardless of method
 type MethodHandler interface {
 	CreateDID(request CreateDIDRequest) (*CreateDIDResponse, error)
-	GetDID(id string) (*GetDIDResponse, error)
+	GetDID(request GetDIDRequest) (*GetDIDResponse, error)
 }
 
 func NewDIDService(log *log.Logger, methods []Method, s storage.ServiceStorage) (*Service, error) {
 	didStorage, err := didstorage.NewDIDStorage(s)
 	if err != nil {
-		return nil, errors.Wrap(err, "could not instantiate DID storage for DID service")
+		return nil, errors.Wrap(err, "could not instantiate DID storage for the DID service")
 	}
 	svc := Service{
 		storage:  didStorage,
 		handlers: make(map[Method]MethodHandler),
-		log:      log,
+		logger:   log,
 	}
 
 	// instantiate all handlers for DID methods
diff --git a/pkg/service/did/key.go b/pkg/service/did/key.go
index ff90e91..6397145 100644
--- a/pkg/service/did/key.go
+++ b/pkg/service/did/key.go
@@ -49,17 +49,16 @@ func (h *keyDIDHandler) CreateDID(request CreateDIDRequest) (*CreateDIDResponse,
 	}, nil
 }
 
-func (h *keyDIDHandler) GetDID(id string) (*GetDIDResponse, error) {
+func (h *keyDIDHandler) GetDID(request GetDIDRequest) (*GetDIDResponse, error) {
+	id := request.ID
 	gotDID, err := h.storage.GetDID(id)
 	if err != nil {
 		return nil, fmt.Errorf("error getting DID: %s", id)
 	}
 	if gotDID == nil {
-		return nil, fmt.Errorf("DID with id<%s> could not be found", id)
+		return nil, fmt.Errorf("did with id<%s> could not be found", id)
 	}
-	return &GetDIDResponse{
-		DID: gotDID.DID,
-	}, nil
+	return &GetDIDResponse{DID: gotDID.DID}, nil
 }
 
 func privateKeyToBase58(privKey interface{}) (string, error) {
diff --git a/pkg/service/did/models.go b/pkg/service/did/model.go
similarity index 65%
rename from pkg/service/did/models.go
rename to pkg/service/did/model.go
index f695994..7633976 100644
--- a/pkg/service/did/models.go
+++ b/pkg/service/did/model.go
@@ -5,8 +5,13 @@ import (
 	sdkdid "github.com/TBD54566975/ssi-sdk/did"
 )
 
-// CreateDIDRequest is the  SON-serializable request for creating a DID across DID methods
+type GetSupportedMethodsResponse struct {
+	Methods []Method `json:"methods"`
+}
+
+// CreateDIDRequest is the JSON-serializable request for creating a DID across DID methods
 type CreateDIDRequest struct {
+	Method  Method         `json:"method" validate:"required"`
 	KeyType crypto.KeyType `validate:"required"`
 }
 
@@ -17,6 +22,11 @@ type CreateDIDResponse struct {
 	PrivateKey string `json:"base58PrivateKey"`
 }
 
+type GetDIDRequest struct {
+	Method Method `json:"method" validate:"required"`
+	ID     string `json:"id" validate:"required"`
+}
+
 // GetDIDResponse is the JSON-serializable response for getting a DID
 type GetDIDResponse struct {
 	DID sdkdid.DIDDocument `json:"did"`
diff --git a/pkg/service/did/storage/bolt.go b/pkg/service/did/storage/bolt.go
index 9b43cbb..d86ba5f 100644
--- a/pkg/service/did/storage/bolt.go
+++ b/pkg/service/did/storage/bolt.go
@@ -4,8 +4,8 @@ import (
 	"encoding/json"
 	"fmt"
 	"github.com/pkg/errors"
+	"github.com/tbd54566975/ssi-service/internal/util"
 	"github.com/tbd54566975/ssi-service/pkg/storage"
-	"strings"
 )
 
 const (
@@ -54,7 +54,7 @@ func (b BoltDIDStorage) GetDID(id string) (*StoredDID, error) {
 		return nil, errors.Wrap(err, couldNotGetDIDErr)
 	}
 	if len(docBytes) == 0 {
-		return nil, fmt.Errorf("DID not found: %s", id)
+		return nil, fmt.Errorf("did not found: %s", id)
 	}
 	var stored StoredDID
 	if err := json.Unmarshal(docBytes, &stored); err != nil {
@@ -63,7 +63,7 @@ func (b BoltDIDStorage) GetDID(id string) (*StoredDID, error) {
 	return &stored, nil
 }
 
-// GetDIDs attempts to get all DIDs for a given method. It will return those it can, even if it has trouble with some.
+// GetDIDs attempts to get all DIDs for a given method. It will return those it can even if it has trouble with some.
 func (b BoltDIDStorage) GetDIDs(method string) ([]StoredDID, error) {
 	couldNotGetDIDsErr := fmt.Sprintf("could not get DIDs for method: %s", method)
 	namespace, err := getNamespaceForMethod(method)
@@ -93,11 +93,14 @@ func (b BoltDIDStorage) DeleteDID(id string) error {
 	if err != nil {
 		return errors.Wrap(err, couldNotGetDIDErr)
 	}
-	return b.db.Delete(namespace, id)
+	if err := b.db.Delete(namespace, id); err != nil {
+		return errors.Wrapf(err, "could not delete DID: %s", id)
+	}
+	return nil
 }
 
 func getNamespaceForDID(id string) (string, error) {
-	method, err := getMethod(id)
+	method, err := util.GetMethodForDID(id)
 	if err != nil {
 		return "", err
 	}
@@ -108,15 +111,6 @@ func getNamespaceForDID(id string) (string, error) {
 	return namespace, nil
 }
 
-// getMethod gets a DID method from a did, the second part of the did (e.g. did:test:abcd, the method is 'test')
-func getMethod(did string) (string, error) {
-	split := strings.Split(did, ":")
-	if len(split) < 3 {
-		return "", fmt.Errorf("malformed did: %s", did)
-	}
-	return split[1], nil
-}
-
 func getNamespaceForMethod(method string) (string, error) {
 	namespace, ok := didMethodToNamespace[method]
 	if !ok {
diff --git a/pkg/service/did/storage/storage.go b/pkg/service/did/storage/storage.go
index 2de509b..4349248 100644
--- a/pkg/service/did/storage/storage.go
+++ b/pkg/service/did/storage/storage.go
@@ -7,8 +7,8 @@ import (
 )
 
 type StoredDID struct {
-	DID              did.DIDDocument
-	PrivateKeyBase58 string
+	DID              did.DIDDocument `json:"did"`
+	PrivateKeyBase58 string          `json:"privateKeyBase58"`
 }
 
 type Storage interface {
diff --git a/pkg/service/framework/framework.go b/pkg/service/framework/framework.go
index cd6a931..5e1b68a 100644
--- a/pkg/service/framework/framework.go
+++ b/pkg/service/framework/framework.go
@@ -8,10 +8,11 @@ type (
 const (
 	// List of all service
 
-	DID Type = "did-service"
+	DID    Type = "did-service"
+	Schema Type = "schema-service"
 
 	StatusReady    StatusState = "ready"
-	StatusNotReady StatusState = "not ready"
+	StatusNotReady StatusState = "not_ready"
 )
 
 // Status is for service reporting on their status
diff --git a/pkg/service/schema/model.go b/pkg/service/schema/model.go
new file mode 100644
index 0000000..c083a50
--- /dev/null
+++ b/pkg/service/schema/model.go
@@ -0,0 +1,30 @@
+package schema
+
+import "github.com/TBD54566975/ssi-sdk/credential/schema"
+
+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"`
+	Schema schema.JSONSchema `json:"schema" validate:"required"`
+}
+
+type CreateSchemaResponse struct {
+	ID     string              `json:"id"`
+	Schema schema.VCJSONSchema `json:"schema"`
+}
+
+type GetSchemaByIDRequest struct {
+	ID string `json:"id" validate:"required"`
+}
+
+type GetSchemaByIDResponse struct {
+	Schema schema.VCJSONSchema `json:"schema"`
+}
diff --git a/pkg/service/schema/schema.go b/pkg/service/schema/schema.go
new file mode 100644
index 0000000..c35a81d
--- /dev/null
+++ b/pkg/service/schema/schema.go
@@ -0,0 +1,101 @@
+package schema
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/TBD54566975/ssi-sdk/credential/schema"
+	schemalib "github.com/TBD54566975/ssi-sdk/schema"
+	"github.com/google/uuid"
+	"github.com/pkg/errors"
+	"github.com/tbd54566975/ssi-service/pkg/service/framework"
+	schemastorage "github.com/tbd54566975/ssi-service/pkg/service/schema/storage"
+	"github.com/tbd54566975/ssi-service/pkg/storage"
+	"log"
+	"time"
+)
+
+type Service struct {
+	storage schemastorage.Storage
+	log     *log.Logger
+}
+
+func (s Service) Type() framework.Type {
+	return framework.Schema
+}
+
+func (s Service) Status() framework.Status {
+	if s.storage == nil {
+		return framework.Status{
+			Status:  framework.StatusNotReady,
+			Message: "storage not loaded",
+		}
+	}
+	return framework.Status{Status: framework.StatusReady}
+}
+
+func NewSchemaService(logger *log.Logger, s storage.ServiceStorage) (*Service, error) {
+	schemaStorage, err := schemastorage.NewSchemaStorage(s)
+	if err != nil {
+		return nil, errors.Wrap(err, "could not instantiate Schema storage for the Schema service")
+	}
+	return &Service{
+		storage: schemaStorage,
+		log:     logger,
+	}, nil
+}
+
+// CreateSchema houses the main service logic for schema creation. It validates the input, and
+// produces a schema value that conforms with the VC JSON Schema specification.
+// TODO(gabe) support proof generation on schemas, versioning, and more
+func (s Service) CreateSchema(request CreateSchemaRequest) (*CreateSchemaResponse, error) {
+	schemaBytes, err := json.Marshal(request.Schema)
+	if err != nil {
+		return nil, errors.Wrap(err, "could not marshal schema in request")
+	}
+	if err := schemalib.IsValidJSONSchema(string(schemaBytes)); err != nil {
+		return nil, errors.Wrap(err, "provided value is not a valid JSON schema")
+	}
+
+	schemaID := uuid.NewString()
+	schema := schema.VCJSONSchema{
+		Type:     schema.VCJSONSchemaType,
+		Version:  Version1,
+		ID:       schemaID,
+		Name:     request.Name,
+		Author:   request.Author,
+		Authored: time.Now().Format(time.RFC3339),
+		Schema:   request.Schema,
+	}
+
+	storedSchema := schemastorage.StoredSchema{Schema: schema}
+	if err := s.storage.StoreSchema(storedSchema); err != nil {
+		return nil, errors.Wrap(err, "could not store schema")
+	}
+
+	return &CreateSchemaResponse{ID: schemaID, Schema: schema}, nil
+}
+
+func (s Service) GetSchemas() (*GetSchemasResponse, error) {
+	storedSchemas, err := s.storage.GetSchemas()
+	if err != nil {
+		return nil, errors.Wrap(err, "error getting schemas")
+	}
+	var schemas []schema.VCJSONSchema
+	for _, stored := range storedSchemas {
+		schemas = append(schemas, stored.Schema)
+	}
+	return &GetSchemasResponse{
+		Schemas: schemas,
+	}, nil
+}
+
+func (s Service) GetSchemaByID(request GetSchemaByIDRequest) (*GetSchemaByIDResponse, error) {
+	gotSchema, err := s.storage.GetSchema(request.ID)
+	if err != nil {
+		return nil, fmt.Errorf("error getting schema: %s", request.ID)
+	}
+	if gotSchema == nil {
+		return nil, fmt.Errorf("schema with id<%s> could not be found", request.ID)
+	}
+	return &GetSchemaByIDResponse{Schema: gotSchema.Schema}, nil
+}
diff --git a/pkg/service/schema/storage/bolt.go b/pkg/service/schema/storage/bolt.go
new file mode 100644
index 0000000..daacfad
--- /dev/null
+++ b/pkg/service/schema/storage/bolt.go
@@ -0,0 +1,76 @@
+package storage
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/pkg/errors"
+	"github.com/tbd54566975/ssi-service/pkg/storage"
+)
+
+const (
+	namespace = "schema"
+)
+
+type BoltSchemaStorage struct {
+	db *storage.BoltDB
+}
+
+func NewBoltSchemaStorage(db *storage.BoltDB) (*BoltSchemaStorage, error) {
+	if db == nil {
+		return nil, errors.New("bolt db reference is nil")
+	}
+	return &BoltSchemaStorage{db: db}, nil
+}
+
+func (b BoltSchemaStorage) StoreSchema(schema StoredSchema) error {
+	id := schema.Schema.ID
+	if id == "" {
+		return errors.New("could not store schema without an ID")
+	}
+	schemaBytes, err := json.Marshal(schema)
+	if err != nil {
+		return errors.Wrapf(err, "could not store Schema: %s", id)
+	}
+	return b.db.Write(namespace, id, schemaBytes)
+}
+
+func (b BoltSchemaStorage) GetSchema(id string) (*StoredSchema, error) {
+	schemaBytes, err := b.db.Read(namespace, id)
+	if err != nil {
+		return nil, errors.Wrapf(err, "could not get schema: %s", id)
+	}
+	if len(schemaBytes) == 0 {
+		return nil, fmt.Errorf("schema not found with id: %s", id)
+	}
+	var stored StoredSchema
+	if err := json.Unmarshal(schemaBytes, &stored); err != nil {
+		return nil, errors.Wrapf(err, "could not unmarshal stored schema: %s", id)
+	}
+	return &stored, nil
+}
+
+// GetSchemas attempts to get all stored schemas. It will return those it can even if it has trouble with some.
+func (b BoltSchemaStorage) GetSchemas() ([]StoredSchema, error) {
+	gotSchemas, err := b.db.ReadAll(namespace)
+	if err != nil {
+		return nil, errors.Wrap(err, "could not get all schemas")
+	}
+	if len(gotSchemas) == 0 {
+		return nil, nil
+	}
+	var stored []StoredSchema
+	for _, schemaBytes := range gotSchemas {
+		var nextSchema StoredSchema
+		if err := json.Unmarshal(schemaBytes, &nextSchema); err == nil {
+			stored = append(stored, nextSchema)
+		}
+	}
+	return stored, nil
+}
+
+func (b BoltSchemaStorage) DeleteSchema(id string) error {
+	if err := b.db.Delete(namespace, id); err != nil {
+		return errors.Wrapf(err, "could not delete schema: %s", id)
+	}
+	return nil
+}
diff --git a/pkg/service/schema/storage/storage.go b/pkg/service/schema/storage/storage.go
new file mode 100644
index 0000000..db58776
--- /dev/null
+++ b/pkg/service/schema/storage/storage.go
@@ -0,0 +1,32 @@
+package storage
+
+import (
+	"github.com/TBD54566975/ssi-sdk/credential/schema"
+	"github.com/pkg/errors"
+	"github.com/tbd54566975/ssi-service/pkg/storage"
+)
+
+type StoredSchema struct {
+	Schema schema.VCJSONSchema `json:"schema"`
+}
+
+type Storage interface {
+	StoreSchema(schema StoredSchema) error
+	GetSchema(id string) (*StoredSchema, error)
+	// TODO(gabe) consider get schemas by DID, or more advanced querying
+	GetSchemas() ([]StoredSchema, error)
+	DeleteSchema(id string) error
+}
+
+// NewSchemaStorage finds the schema storage impl for a given ServiceStorage value
+func NewSchemaStorage(s storage.ServiceStorage) (Storage, error) {
+	gotBolt, ok := s.(*storage.BoltDB)
+	if !ok {
+		return nil, errors.New("unsupported storage type")
+	}
+	boltStorage, err := NewBoltSchemaStorage(gotBolt)
+	if err != nil {
+		return nil, errors.Wrap(err, "could not instantiate Schema Bolt storage")
+	}
+	return boltStorage, err
+}
diff --git a/pkg/service/service.go b/pkg/service/service.go
index d2cbfa7..44e8efc 100644
--- a/pkg/service/service.go
+++ b/pkg/service/service.go
@@ -4,6 +4,7 @@ import (
 	"github.com/pkg/errors"
 	"github.com/tbd54566975/ssi-service/pkg/service/did"
 	"github.com/tbd54566975/ssi-service/pkg/service/framework"
+	"github.com/tbd54566975/ssi-service/pkg/service/schema"
 	"github.com/tbd54566975/ssi-service/pkg/storage"
 	"log"
 )
@@ -41,14 +42,18 @@ func (ssi *SSIService) GetServices() []framework.Service {
 }
 
 // instantiateServices begins all instantiates and their dependencies
-func instantiateServices(log *log.Logger) ([]framework.Service, error) {
-	bolt, err := storage.NewBoltDB()
+func instantiateServices(logger *log.Logger) ([]framework.Service, error) {
+	bolt, err := storage.NewBoltDB(logger)
 	if err != nil {
 		return nil, errors.Wrap(err, "could not instantiate BoltDB")
 	}
-	didService, err := did.NewDIDService(log, []did.Method{did.KeyMethod}, bolt)
+	didService, err := did.NewDIDService(logger, []did.Method{did.KeyMethod}, bolt)
 	if err != nil {
 		return nil, errors.Wrap(err, "could not instantiate the DID service")
 	}
-	return []framework.Service{didService}, nil
+	schemaService, err := schema.NewSchemaService(logger, bolt)
+	if err != nil {
+		return nil, errors.Wrap(err, "could not instantiate the schema service")
+	}
+	return []framework.Service{didService, schemaService}, nil
 }
diff --git a/pkg/storage/bolt.go b/pkg/storage/bolt.go
index c2543ec..8bad769 100644
--- a/pkg/storage/bolt.go
+++ b/pkg/storage/bolt.go
@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"github.com/boltdb/bolt"
 	"github.com/pkg/errors"
+	"log"
 	"strings"
 	"time"
 )
@@ -13,20 +14,21 @@ const (
 )
 
 type BoltDB struct {
-	db *bolt.DB
+	logger *log.Logger
+	db     *bolt.DB
 }
 
 // NewBoltDB instantiates a file-based storage instance for Bolt https://github.com/boltdb/bolt
-func NewBoltDB() (*BoltDB, error) {
-	return NewBoltDBWithFile(DBFile)
+func NewBoltDB(logger *log.Logger) (*BoltDB, error) {
+	return NewBoltDBWithFile(logger, DBFile)
 }
 
-func NewBoltDBWithFile(filePath string) (*BoltDB, error) {
-	db, err := bolt.Open(filePath, 0600, &bolt.Options{Timeout: 1 * time.Second})
+func NewBoltDBWithFile(logger *log.Logger, filePath string) (*BoltDB, error) {
+	db, err := bolt.Open(filePath, 0600, &bolt.Options{Timeout: 3 * time.Second})
 	if err != nil {
 		return nil, err
 	}
-	return &BoltDB{db: db}, nil
+	return &BoltDB{logger: logger, db: db}, nil
 }
 
 func (b *BoltDB) Close() error {
@@ -51,7 +53,8 @@ func (b *BoltDB) Read(namespace, key string) ([]byte, error) {
 	err := b.db.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(namespace))
 		if bucket == nil {
-			return fmt.Errorf("namespace<%s> does not exist", namespace)
+			b.logger.Printf("namespace<%s> does not exist", namespace)
+			return nil
 		}
 		result = bucket.Get([]byte(key))
 		return nil
@@ -64,7 +67,8 @@ func (b *BoltDB) ReadAll(namespace string) (map[string][]byte, error) {
 	err := b.db.View(func(tx *bolt.Tx) error {
 		bucket := tx.Bucket([]byte(namespace))
 		if bucket == nil {
-			return fmt.Errorf("namespace<%s> does not exist", namespace)
+			b.logger.Printf("namespace<%s> does not exist", namespace)
+			return nil
 		}
 		cursor := bucket.Cursor()
 		for k, v := cursor.First(); k != nil; k, v = cursor.Next() {


Test patch:
diff --git a/pkg/server/router/did_test.go b/pkg/server/router/did_test.go
index 0fa363c..d21e471 100644
--- a/pkg/server/router/did_test.go
+++ b/pkg/server/router/did_test.go
@@ -33,11 +33,12 @@ func TestDIDRouter(t *testing.T) {
 	})
 
 	t.Run("DID Service Test", func(tt *testing.T) {
-		bolt, err := storage.NewBoltDB()
+		logger := log.New(os.Stdout, "ssi-test", log.LstdFlags)
+
+		bolt, err := storage.NewBoltDB(logger)
 		assert.NoError(tt, err)
 		assert.NotEmpty(tt, bolt)
 
-		logger := log.New(os.Stdout, "ssi-test", log.LstdFlags)
 		didService, err := did.NewDIDService(logger, []did.Method{did.KeyMethod}, bolt)
 		assert.NoError(tt, err)
 		assert.NotEmpty(tt, didService)
@@ -47,27 +48,22 @@ func TestDIDRouter(t *testing.T) {
 		assert.Equal(tt, framework.StatusReady, didService.Status().Status)
 
 		// get unknown handler
-		_, err = didService.GetHandler("bad")
+		_, err = didService.GetDIDByMethod(did.GetDIDRequest{Method: "bad"})
 		assert.Error(tt, err)
-		assert.Contains(tt, err.Error(), "could not get handler for DID method: bad")
+		assert.Contains(tt, err.Error(), "could not get handler for method<bad>")
 
 		supported := didService.GetSupportedMethods()
 		assert.NotEmpty(tt, supported)
-		assert.Len(tt, supported, 1)
-		assert.Equal(tt, did.KeyMethod, supported[0])
-
-		// get known handler
-		keyHandler, err := didService.GetHandler(did.KeyMethod)
-		assert.NoError(tt, err)
-		assert.NotEmpty(tt, keyHandler)
+		assert.Len(tt, supported.Methods, 1)
+		assert.Equal(tt, did.KeyMethod, supported.Methods[0])
 
 		// bad key type
-		_, err = keyHandler.CreateDID(did.CreateDIDRequest{KeyType: "bad"})
+		_, err = didService.CreateDIDByMethod(did.CreateDIDRequest{Method: did.KeyMethod, KeyType: "bad"})
 		assert.Error(tt, err)
 		assert.Contains(tt, err.Error(), "could not create did:key")
 
 		// good key type
-		createDIDResponse, err := keyHandler.CreateDID(did.CreateDIDRequest{KeyType: crypto.Ed25519})
+		createDIDResponse, err := didService.CreateDIDByMethod(did.CreateDIDRequest{Method: did.KeyMethod, KeyType: crypto.Ed25519})
 		assert.NoError(tt, err)
 		assert.NotEmpty(tt, createDIDResponse)
 
@@ -75,7 +71,7 @@ func TestDIDRouter(t *testing.T) {
 		assert.Contains(tt, createDIDResponse.DID.ID, "did:key")
 
 		// get it back
-		getDIDResponse, err := keyHandler.GetDID(createDIDResponse.DID.ID)
+		getDIDResponse, err := didService.GetDIDByMethod(did.GetDIDRequest{Method: did.KeyMethod, ID: createDIDResponse.DID.ID})
 		assert.NoError(tt, err)
 		assert.NotEmpty(tt, getDIDResponse)
 
diff --git a/pkg/server/router/schema_test.go b/pkg/server/router/schema_test.go
new file mode 100644
index 0000000..d3aabd5
--- /dev/null
+++ b/pkg/server/router/schema_test.go
@@ -0,0 +1,107 @@
+package router
+
+import (
+	"github.com/stretchr/testify/assert"
+	"github.com/tbd54566975/ssi-service/pkg/service/framework"
+	"github.com/tbd54566975/ssi-service/pkg/service/schema"
+	"github.com/tbd54566975/ssi-service/pkg/storage"
+	"log"
+	"os"
+	"testing"
+)
+
+func TestSchemaRouter(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) {
+		schemaRouter, err := NewSchemaRouter(nil, nil)
+		assert.Error(tt, err)
+		assert.Empty(tt, schemaRouter)
+		assert.Contains(tt, err.Error(), "service cannot be nil")
+	})
+
+	t.Run("Bad Service", func(tt *testing.T) {
+		schemaRouter, err := NewSchemaRouter(&testService{}, nil)
+		assert.Error(tt, err)
+		assert.Empty(tt, schemaRouter)
+		assert.Contains(tt, err.Error(), "could not create schema router with service type: test")
+	})
+
+	t.Run("Schema Service Test", func(tt *testing.T) {
+		logger := log.New(os.Stdout, "ssi-test", log.LstdFlags)
+
+		bolt, err := storage.NewBoltDB(logger)
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, bolt)
+
+		schemaService, err := schema.NewSchemaService(logger, bolt)
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, schemaService)
+
+		// check type and status
+		assert.Equal(tt, framework.Schema, schemaService.Type())
+		assert.Equal(tt, framework.StatusReady, schemaService.Status().Status)
+
+		// get all schemas (none)
+		gotSchemas, err := schemaService.GetSchemas()
+		assert.NoError(tt, err)
+		assert.Empty(tt, gotSchemas)
+		assert.Equal(tt, 0, len(gotSchemas.Schemas))
+
+		// get schema that doesn't exist
+		_, err = schemaService.GetSchemaByID(schema.GetSchemaByIDRequest{ID: "bad"})
+		assert.Error(tt, err)
+		assert.Contains(tt, err.Error(), "error getting schema")
+
+		// create a schema
+		simpleSchema := map[string]interface{}{
+			"type": "object",
+			"properties": map[string]interface{}{
+				"foo": map[string]interface{}{
+					"type": "string",
+				},
+			},
+			"required":             []interface{}{"foo"},
+			"additionalProperties": false,
+		}
+		createdSchema, err := schemaService.CreateSchema(schema.CreateSchemaRequest{Author: "me", Name: "simple schema", Schema: simpleSchema})
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, createdSchema)
+		assert.NotEmpty(tt, createdSchema.ID)
+		assert.Equal(tt, "me", createdSchema.Schema.Author)
+		assert.Equal(tt, "simple schema", createdSchema.Schema.Name)
+
+		// get schema by ID
+		gotSchema, err := schemaService.GetSchemaByID(schema.GetSchemaByIDRequest{ID: createdSchema.ID})
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, gotSchema)
+		assert.EqualValues(tt, createdSchema.Schema, gotSchema.Schema)
+
+		// get all schemas, expect one
+		gotSchemas, err = schemaService.GetSchemas()
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, gotSchemas.Schemas)
+		assert.Len(tt, gotSchemas.Schemas, 1)
+
+		// store another
+		createdSchema, err = schemaService.CreateSchema(schema.CreateSchemaRequest{Author: "me", Name: "simple schema 2", Schema: simpleSchema})
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, createdSchema)
+		assert.NotEmpty(tt, createdSchema.ID)
+		assert.Equal(tt, "me", createdSchema.Schema.Author)
+		assert.Equal(tt, "simple schema 2", createdSchema.Schema.Name)
+
+		// get all schemas, expect two
+		gotSchemas, err = schemaService.GetSchemas()
+		assert.NoError(tt, err)
+		assert.NotEmpty(tt, gotSchemas.Schemas)
+		assert.Len(tt, gotSchemas.Schemas, 2)
+
+		// make sure their IDs are different
+		assert.True(tt, gotSchemas.Schemas[0].ID != gotSchemas.Schemas[1].ID)
+	})
+}
diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go
index af9e9fb..7c97f5b 100644
--- a/pkg/server/server_test.go
+++ b/pkg/server/server_test.go
@@ -15,6 +15,7 @@ import (
 	"github.com/tbd54566975/ssi-service/pkg/service"
 	"github.com/tbd54566975/ssi-service/pkg/service/did"
 	svcframework "github.com/tbd54566975/ssi-service/pkg/service/framework"
+	"github.com/tbd54566975/ssi-service/pkg/service/schema"
 	"github.com/tbd54566975/ssi-service/pkg/storage"
 	"io"
 	"log"
@@ -25,66 +26,68 @@ import (
 	"time"
 )
 
-func TestAPI(t *testing.T) {
-
+func TestHealthCheckAPI(t *testing.T) {
 	// remove the db file after the test
 	t.Cleanup(func() {
 		_ = os.Remove(storage.DBFile)
 	})
 
-	t.Run("Test Health Check", func(tt *testing.T) {
-		shutdown := make(chan os.Signal, 1)
-		logger := log.New(os.Stdout, "ssi-test", log.LstdFlags)
-		server, err := NewSSIServer(shutdown, service.Config{Logger: logger})
-		assert.NoError(tt, err)
-		assert.NotEmpty(tt, server)
+	shutdown := make(chan os.Signal, 1)
+	logger := log.New(os.Stdout, "ssi-test", log.LstdFlags)
+	server, err := NewSSIServer(shutdown, service.Config{Logger: logger})
+	assert.NoError(t, err)
+	assert.NotEmpty(t, server)
 
-		req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/health", nil)
-		w := httptest.NewRecorder()
+	req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/health", nil)
+	w := httptest.NewRecorder()
 
-		err = router.Health(context.TODO(), w, req)
-		assert.NoError(tt, err)
-		assert.Equal(tt, http.StatusOK, w.Result().StatusCode)
+	err = router.Health(context.TODO(), w, req)
+	assert.NoError(t, err)
+	assert.Equal(t, http.StatusOK, w.Result().StatusCode)
 
-		var resp router.GetHealthCheckResponse
-		err = json.NewDecoder(w.Body).Decode(&resp)
-		assert.NoError(tt, err)
+	var resp router.GetHealthCheckResponse
+	err = json.NewDecoder(w.Body).Decode(&resp)
+	assert.NoError(t, err)
+
+	assert.Equal(t, router.HealthOK, resp.Status)
 
-		assert.Equal(tt, router.HealthOK, resp.Status)
+}
+
+func TestReadinessAPI(t *testing.T) {
+	// remove the db file after the test
+	t.Cleanup(func() {
+		_ = os.Remove(storage.DBFile)
 	})
 
-	t.Run("Test Readiness Check", func(tt *testing.T) {
-		shutdown := make(chan os.Signal, 1)
-		logger := log.New(os.Stdout, "ssi-test", log.LstdFlags)
-		server, err := NewSSIServer(shutdown, service.Config{Logger: logger})
-		assert.NoError(tt, err)
-		assert.NotEmpty(tt, server)
+	shutdown := make(chan os.Signal, 1)
+	logger := log.New(os.Stdout, "ssi-test", log.LstdFlags)
+	server, err := NewSSIServer(shutdown, service.Config{Logger: logger})
+	assert.NoError(t, err)
+	assert.NotEmpty(t, server)
 
-		req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/readiness", nil)
-		w := httptest.NewRecorder()
+	req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/readiness", nil)
+	w := httptest.NewRecorder()
 
-		handler := router.Readiness(nil, logger)
-		err = handler(newRequestContext(), w, req)
-		assert.NoError(tt, err)
-		assert.Equal(tt, http.StatusOK, w.Result().StatusCode)
+	handler := router.Readiness(nil, logger)
+	err = handler(newRequestContext(), w, req)
+	assert.NoError(t, err)
+	assert.Equal(t, http.StatusOK, w.Result().StatusCode)
 
-		var resp router.GetReadinessResponse
-		err = json.NewDecoder(w.Body).Decode(&resp)
-		assert.NoError(tt, err)
+	var resp router.GetReadinessResponse
+	err = json.NewDecoder(w.Body).Decode(&resp)
+	assert.NoError(t, err)
 
-		assert.Equal(tt, svcframework.StatusReady, resp.Status.Status)
-		assert.Len(tt, resp.ServiceStatuses, 0)
-	})
+	assert.Equal(t, svcframework.StatusReady, resp.Status.Status)
+	assert.Len(t, resp.ServiceStatuses, 0)
 }
 
 func TestDIDAPI(t *testing.T) {
-
-	// remove the db file after the test
-	t.Cleanup(func() {
-		_ = os.Remove(storage.DBFile)
-	})
-
 	t.Run("Test Get DID Methods", func(tt *testing.T) {
+		// remove the db file after the test
+		tt.Cleanup(func() {
+			_ = os.Remove(storage.DBFile)
+		})
+
 		didRouter := newDIDService(tt)
 
 		// get DID methods
@@ -104,6 +107,11 @@ func TestDIDAPI(t *testing.T) {
 	})
 
 	t.Run("Test Create DID By Method: Key", func(tt *testing.T) {
+		// remove the db file after the test
+		tt.Cleanup(func() {
+			_ = os.Remove(storage.DBFile)
+		})
+
 		didRouter := newDIDService(tt)
 
 		// create DID by method - key - missing body
@@ -118,7 +126,7 @@ func TestDIDAPI(t *testing.T) {
 		assert.Contains(tt, err.Error(), "invalid create DID request")
 
 		// with body, bad key type
-		createDIDRequest := did.CreateDIDRequest{KeyType: "bad"}
+		createDIDRequest := router.CreateDIDByMethodRequest{KeyType: "bad"}
 		requestReader := newRequestValue(tt, createDIDRequest)
 		req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/dids/key", requestReader)
 		w = httptest.NewRecorder()
@@ -128,7 +136,7 @@ func TestDIDAPI(t *testing.T) {
 		assert.Contains(tt, err.Error(), "could not create DID for method<key> with key type: bad")
 
 		// with body, good key type
-		createDIDRequest = did.CreateDIDRequest{KeyType: crypto.Ed25519}
+		createDIDRequest = router.CreateDIDByMethodRequest{KeyType: crypto.Ed25519}
 		requestReader = newRequestValue(tt, createDIDRequest)
 		req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/dids/key", requestReader)
 		w = httptest.NewRecorder()
@@ -144,6 +152,11 @@ func TestDIDAPI(t *testing.T) {
 	})
 
 	t.Run("Test Get DID By Method", func(tt *testing.T) {
+		// remove the db file after the test
+		tt.Cleanup(func() {
+			_ = os.Remove(storage.DBFile)
+		})
+
 		didRouter := newDIDService(tt)
 
 		// get DID by method
@@ -157,7 +170,7 @@ func TestDIDAPI(t *testing.T) {
 		}
 		err := didRouter.GetDIDByMethod(newRequestContextWithParams(badParams), w, req)
 		assert.Error(tt, err)
-		assert.Contains(tt, err.Error(), "could not get handler for method<bad>")
+		assert.Contains(tt, err.Error(), "could not get DID for method<bad>")
 
 		// good method, bad id
 		badParams1 := map[string]string{
@@ -169,7 +182,7 @@ func TestDIDAPI(t *testing.T) {
 		assert.Contains(tt, err.Error(), "could not get DID for method<key> with id: worse")
 
 		// store a DID
-		createDIDRequest := did.CreateDIDRequest{KeyType: crypto.Ed25519}
+		createDIDRequest := router.CreateDIDByMethodRequest{KeyType: crypto.Ed25519}
 		requestReader := newRequestValue(tt, createDIDRequest)
 		params := map[string]string{"method": "key"}
 		req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/dids/key", requestReader)
@@ -205,11 +218,12 @@ func TestDIDAPI(t *testing.T) {
 
 func newDIDService(t *testing.T) *router.DIDRouter {
 	// set up DID service
-	bolt, err := storage.NewBoltDB()
+	logger := log.New(os.Stdout, "ssi-test", log.LstdFlags)
+
+	bolt, err := storage.NewBoltDB(logger)
 	require.NoError(t, err)
 	require.NotEmpty(t, bolt)
 
-	logger := log.New(os.Stdout, "ssi-test", log.LstdFlags)
 	didService, err := did.NewDIDService(logger, []did.Method{did.KeyMethod}, bolt)
 	require.NoError(t, err)
 	require.NotEmpty(t, didService)
@@ -222,6 +236,163 @@ func newDIDService(t *testing.T) *router.DIDRouter {
 	return didRouter
 }
 
+func TestSchemaAPI(t *testing.T) {
+	t.Run("Test Create Schema", func(tt *testing.T) {
+		// remove the db file after the test
+		tt.Cleanup(func() {
+			_ = os.Remove(storage.DBFile)
+		})
+
+		schemaRouter := newSchemaService(tt)
+
+		simpleSchema := map[string]interface{}{
+			"type": "object",
+			"properties": map[string]interface{}{
+				"foo": map[string]interface{}{
+					"type": "string",
+				},
+			},
+			"required":             []interface{}{"foo"},
+			"additionalProperties": false,
+		}
+		badSchemaRequest := router.CreateSchemaRequest{Schema: simpleSchema}
+		schemaRequestValue := newRequestValue(tt, badSchemaRequest)
+		req := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/schemas", schemaRequestValue)
+		w := httptest.NewRecorder()
+
+		err := schemaRouter.CreateSchema(newRequestContext(), w, req)
+		assert.Error(tt, err)
+		assert.Contains(tt, err.Error(), "invalid create schema request")
+
+		// reset the http recorder
+		w.Flush()
+
+		schemaRequest := router.CreateSchemaRequest{Author: "did:test", Name: "test schema", Schema: simpleSchema}
+		schemaRequestValue = newRequestValue(tt, schemaRequest)
+		req = httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/schemas", schemaRequestValue)
+		err = schemaRouter.CreateSchema(newRequestContext(), w, req)
+		assert.NoError(tt, err)
+
+		var resp router.CreateSchemaResponse
+		err = json.NewDecoder(w.Body).Decode(&resp)
+		assert.NoError(tt, err)
+
+		assert.NotEmpty(tt, resp.ID)
+		assert.EqualValues(tt, schemaRequest.Schema, resp.Schema.Schema)
+	})
+
+	t.Run("Test Get Schema/s", func(tt *testing.T) {
+		// remove the db file after the test
+		tt.Cleanup(func() {
+			_ = os.Remove(storage.DBFile)
+		})
+
+		schemaRouter := newSchemaService(tt)
+
+		// get schema that doesn't exist
+		w := httptest.NewRecorder()
+		req := httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/schemas/bad", nil)
+		err := schemaRouter.GetSchemaByID(newRequestContext(), w, req)
+		assert.Error(tt, err)
+		assert.Contains(tt, err.Error(), "cannot get schema without ID parameter")
+
+		// reset recorder between calls
+		w.Flush()
+
+		// get schema with invalid id
+		req = httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/schemas/bad", nil)
+		err = schemaRouter.GetSchemaByID(newRequestContextWithParams(map[string]string{"id": "bad"}), w, req)
+		assert.Error(tt, err)
+		assert.Contains(tt, err.Error(), "could not get schema with id: bad")
+
+		// reset recorder between calls
+		w.Flush()
+
+		// get all schemas - get none
+		req = httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/schemas", nil)
+		err = schemaRouter.GetSchemas(newRequestContext(), w, req)
+		assert.NoError(tt, err)
+		var getSchemasResp router.GetSchemasResponse
+		err = json.NewDecoder(w.Body).Decode(&getSchemasResp)
+		assert.NoError(tt, err)
+		assert.Len(tt, getSchemasResp.Schemas, 0)
+
+		// reset recorder between calls
+		w.Flush()
+
+		// create a schema
+		simpleSchema := map[string]interface{}{
+			"type": "object",
+			"properties": map[string]interface{}{
+				"foo": map[string]interface{}{
+					"type": "string",
+				},
+			},
+			"required":             []interface{}{"foo"},
+			"additionalProperties": false,
+		}
+		schemaRequest := router.CreateSchemaRequest{Author: "did:test", Name: "test schema", Schema: simpleSchema}
+		schemaRequestValue := newRequestValue(tt, schemaRequest)
+		createReq := httptest.NewRequest(http.MethodPut, "https://ssi-service.com/v1/schemas", schemaRequestValue)
+
+		err = schemaRouter.CreateSchema(newRequestContext(), w, createReq)
+		assert.NoError(tt, err)
+
+		var createResp router.CreateSchemaResponse
+		err = json.NewDecoder(w.Body).Decode(&createResp)
+		assert.NoError(tt, err)
+
+		assert.NotEmpty(tt, createResp.ID)
+		assert.EqualValues(tt, schemaRequest.Schema, createResp.Schema.Schema)
+
+		// reset recorder between calls
+		w.Flush()
+
+		// get it back
+		req = httptest.NewRequest(http.MethodGet, fmt.Sprintf("https://ssi-service.com/v1/schemas/%s", createResp.ID), nil)
+		err = schemaRouter.GetSchemaByID(newRequestContextWithParams(map[string]string{"id": createResp.ID}), w, req)
+		assert.NoError(tt, err)
+
+		var gotSchemaResp router.GetSchemaResponse
+		err = json.NewDecoder(w.Body).Decode(&gotSchemaResp)
+		assert.NoError(tt, err)
+
+		assert.Equal(tt, createResp.ID, gotSchemaResp.Schema.ID)
+		assert.Equal(tt, createResp.Schema.Schema, gotSchemaResp.Schema.Schema)
+
+		// reset recorder between calls
+		w.Flush()
+
+		// get all schemas - get none
+		req = httptest.NewRequest(http.MethodGet, "https://ssi-service.com/v1/schemas", nil)
+		err = schemaRouter.GetSchemas(newRequestContext(), w, req)
+		assert.NoError(tt, err)
+		err = json.NewDecoder(w.Body).Decode(&getSchemasResp)
+		assert.NoError(tt, err)
+		assert.Len(tt, getSchemasResp.Schemas, 1)
+	})
+}
+
+func newSchemaService(t *testing.T) *router.SchemaRouter {
+	// set up DID service
+	logger := log.New(os.Stdout, "ssi-test", log.LstdFlags)
+
+	bolt, err := storage.NewBoltDB(logger)
+	require.NoError(t, err)
+	require.NotEmpty(t, bolt)
+
+	schemaService, err := schema.NewSchemaService(logger, bolt)
+	require.NoError(t, err)
+	require.NotEmpty(t, schemaService)
+
+	// create router for service
+	schemaRouter, err := router.NewSchemaRouter(schemaService, logger)
+	require.NoError(t, err)
+	require.NotEmpty(t, schemaRouter)
+
+	return schemaRouter
+}
+
 func newRequestValue(t *testing.T, data interface{}) io.Reader {
 	dataBytes, err := json.Marshal(data)
 	require.NoError(t, err)
diff --git a/pkg/storage/bolt_test.go b/pkg/storage/bolt_test.go
index 500035c..12ef51b 100644
--- a/pkg/storage/bolt_test.go
+++ b/pkg/storage/bolt_test.go
@@ -3,12 +3,15 @@ package storage
 import (
 	"encoding/json"
 	"github.com/stretchr/testify/assert"
+	"log"
 	"os"
 	"testing"
 )
 
 func TestBoltDB(t *testing.T) {
-	db, err := NewBoltDBWithFile("test.db")
+	logger := log.New(os.Stdout, "ssi-test", log.LstdFlags)
+
+	db, err := NewBoltDBWithFile(logger, "test.db")
 	assert.NoError(t, err)
 	assert.NotEmpty(t, db)
 
@@ -38,9 +41,9 @@ func TestBoltDB(t *testing.T) {
 	assert.EqualValues(t, players1, players1Result)
 
 	// get a value from a namespace that doesn't exist
-	_, err = db.Read("bad", "worse")
-	assert.Error(t, err)
-	assert.Contains(t, err.Error(), "namespace<bad> does not exist")
+	res, err := db.Read("bad", "worse")
+	assert.NoError(t, err)
+	assert.Empty(t, res)
 
 	// get a value that doesn't exist in the namespace
 	noValue, err := db.Read(namespace, "Porsche")
@@ -88,7 +91,7 @@ func TestBoltDB(t *testing.T) {
 	err = db.DeleteNamespace(namespace)
 	assert.NoError(t, err)
 
-	_, err = db.Read(namespace, team1)
-	assert.Error(t, err)
-	assert.Contains(t, err.Error(), "namespace<F1> does not exist")
+	res, err = db.Read(namespace, team1)
+	assert.NoError(t, err)
+	assert.Empty(t, res)
 }