diff --git a/Makefile b/Makefile
index 97eb43b2fe74b67270dd6b1bc1b577427c979517..edffb61feca4e592fbd263981a3ebcf5ea682477 100644
--- a/Makefile
+++ b/Makefile
@@ -32,3 +32,23 @@ ifeq (,$(wildcard $(GOPATH)/bin/protoc-gen-go))
 	Run \"go get -u google.golang.org/protobuf/cmd/protoc-gen-go google.golang.org/grpc/cmd/protoc-gen-go-grpc\" \
 	or visit \"https://github.com/golang/protobuf/tree/v1.3.2#installation\" for more.\n")
 endif
+
+
+
+#MICROGENFILES?=$(shell find $(SERVICESDIR) -name "service.go" -exec grep -Ril "microgen" {} \;)
+#SERVICEDIRS?=$(shell find $(SERVICESDIR) -name "service" -type d -exec dirname {} \;)
+#SERVICEFILES?=$(shell find $(SERVICESDIR) -name "service.go" -exec grep -Ril "go:generate" {} \;)
+
+# Генерация моков для всех интерфейсов, найденных в директории. Выходные файлы с моками сохраняются в `./mocks`
+MOCKSDIRS?=$(shell find . -name "service.go" -exec dirname {} \;)
+MOCKS=$(MOCKSDIRS:=/mocks)
+
+mocks: $(MOCKS)
+	@echo "Generate mocks $<"
+
+%/mocks: %
+	@echo "$@ - $<"
+	@mockery --log-level="error" --all --dir "$<" --output="$@"
+
+mocks-clean:
+	@rm -rf $(MOCKS)
diff --git a/go.mod b/go.mod
index 2fc7ac75587f92d4a4a9b31e16c288997592a4f5..94c2cfbdd2c37f7bbedc4bcc6f91bd752bd0b492 100644
--- a/go.mod
+++ b/go.mod
@@ -3,14 +3,26 @@ module git.perx.ru/perxis/perxis-go
 go 1.18
 
 require (
+	github.com/go-kit/kit v0.12.0
+	github.com/golang/protobuf v1.5.2
+	github.com/hashicorp/go-multierror v1.1.1
+	github.com/pkg/errors v0.9.1
+	github.com/rs/xid v1.4.0
+	github.com/stretchr/testify v1.7.0
+	golang.org/x/net v0.0.0-20210917221730-978cfadd31cf
 	google.golang.org/grpc v1.45.0
 	google.golang.org/protobuf v1.28.0
 )
 
 require (
-	github.com/golang/protobuf v1.5.2 // indirect
-	golang.org/x/net v0.0.0-20200822124328-c89045814202 // indirect
-	golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect
-	golang.org/x/text v0.3.0 // indirect
-	google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect
+	github.com/davecgh/go-spew v1.1.0 // indirect
+	github.com/go-kit/log v0.2.0 // indirect
+	github.com/go-logfmt/logfmt v0.5.1 // indirect
+	github.com/hashicorp/errwrap v1.0.0 // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/stretchr/objx v0.1.0 // indirect
+	golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 // indirect
+	golang.org/x/text v0.3.7 // indirect
+	google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4 // indirect
+	gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
 )
diff --git a/go.sum b/go.sum
index e75a1fd6adeb9088de62b9d54e80c78ccbad0acd..2dc3ca61e747e97abd24d179acc4e32112c07a4e 100644
--- a/go.sum
+++ b/go.sum
@@ -1,24 +1,35 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
 github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4=
+github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs=
+github.com/go-kit/log v0.2.0 h1:7i2K3eKTos3Vc0enKCfnVcgHh2olr/MyfboYq7cAcFw=
+github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
+github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
+github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -44,44 +55,79 @@ github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
+github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+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/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210917221730-978cfadd31cf h1:R150MpwJIv1MpS0N/pc+NhTM8ajzvlmxlY5OYsrevXQ=
+golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
 golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 h1:J27LZFQBFoihqXoegpscI10HpjZ7B5WQLLKL2FZXQKw=
+golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -90,14 +136,16 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
 google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
-google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4 h1:ysnBoUyeL/H6RCvNRhWHjKoDEmguI+mPU+qHgK8qv/w=
+google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
 google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
 google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
@@ -111,11 +159,14 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
 google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/pkg/errors/code.go b/pkg/errors/code.go
new file mode 100644
index 0000000000000000000000000000000000000000..fdb94194b49508cc0f1e3610c41fc93d2c271bdf
--- /dev/null
+++ b/pkg/errors/code.go
@@ -0,0 +1,59 @@
+package errors
+
+import (
+	"fmt"
+	"io"
+	"sync"
+)
+
+var codeErrors sync.Map
+
+type CodeError interface {
+	Code() uint64
+}
+
+type codeError struct {
+	code uint64
+	err  error
+}
+
+func (e *codeError) Error() string { return fmt.Sprintf("(%d) %s", e.code, e.err.Error()) }
+func (e *codeError) Code() uint64  { return e.code }
+func (w *codeError) Unwrap() error { return w.err }
+
+func (e *codeError) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		if s.Flag('+') {
+			fmt.Fprintf(s, "\n(%d): %s", e.code, e.Error())
+			return
+		}
+		fallthrough
+	case 's':
+		io.WriteString(s, e.Error())
+	case 'q':
+		fmt.Fprintf(s, "%q", e.Error())
+	}
+}
+
+func (e *codeError) Is(err error) bool {
+	if ce, ok := err.(CodeError); ok {
+		return e.code == ce.Code()
+	}
+	return false
+}
+
+func WithCode(err error, code uint64) error {
+	err = &codeError{code: code, err: err}
+	v, _ := codeErrors.LoadOrStore(code, err)
+	return v.(error)
+}
+
+func GetCode(err error) uint64 {
+	for e := err; e != nil; e = Unwrap(e) {
+		if errCode, ok := e.(CodeError); ok {
+			return errCode.Code()
+		}
+	}
+	return 0
+}
diff --git a/pkg/errors/context.go b/pkg/errors/context.go
new file mode 100644
index 0000000000000000000000000000000000000000..287485e50f79c7d767be536fa7043e66fcb4ee5c
--- /dev/null
+++ b/pkg/errors/context.go
@@ -0,0 +1,55 @@
+package errors
+
+type ErrContext interface{ Context() []interface{} }
+
+type withContext struct {
+	err error
+	kv  []interface{}
+}
+
+func (w *withContext) Context() []interface{} { return w.kv }
+func (w *withContext) Error() string          { return w.err.Error() }
+func (w *withContext) Cause() error           { return w.err }
+func (w *withContext) Unwrap() error          { return w.err }
+
+// WithContext добавляет контекст к ошибке
+func WithContext(err error, kv ...interface{}) error {
+	for e := err; e != nil; e = Unwrap(e) {
+		if wc, ok := e.(*withContext); ok {
+			wc.kv = append(wc.kv, kv...)
+			return err
+		}
+	}
+	return &withContext{
+		err: err,
+		kv:  kv,
+	}
+}
+
+// Context возвращает контекст из ошибки
+func Context(err error) []interface{} {
+	for e := err; e != nil; e = Unwrap(e) {
+		if errContext, ok := e.(ErrContext); ok {
+			return errContext.Context()
+		}
+	}
+	return nil
+}
+
+// ContextKey возвращает значение ключа из контекста ошибки
+func ContextKey(err error, key interface{}) (interface{}, bool) {
+	if kv := Context(err); kv != nil {
+		var i int
+		for {
+			if i >= len(kv) {
+				return nil, false
+			}
+			v := kv[i]
+			if v == key {
+				return kv[i+1], true
+			}
+			i += 2
+		}
+	}
+	return nil, false
+}
diff --git a/pkg/errors/detail.go b/pkg/errors/detail.go
new file mode 100644
index 0000000000000000000000000000000000000000..a82ec3f8ad5fb7fd52f0ce0acdeeb833065e6ffa
--- /dev/null
+++ b/pkg/errors/detail.go
@@ -0,0 +1,57 @@
+package errors
+
+import (
+	"fmt"
+	"io"
+)
+
+type ErrDetail interface{ Detail() string }
+
+type withDetail struct {
+	err    error
+	detail string
+}
+
+func (w *withDetail) Detail() string { return w.detail }
+func (w *withDetail) Error() string  { return w.err.Error() }
+func (w *withDetail) Cause() error   { return w.err }
+func (w *withDetail) Unwrap() error  { return w.err }
+
+func (w *withDetail) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		if s.Flag('+') {
+			fmt.Fprintf(s, "%+v", w.Cause())
+			fmt.Fprintf(s, "\nerror detail: %s", w.detail)
+			return
+		}
+		fallthrough
+	case 's':
+		io.WriteString(s, w.Error())
+	case 'q':
+		fmt.Fprintf(s, "%q", w.Error())
+	}
+}
+
+func WithDetail(err error, detail string) error {
+	if err == nil {
+		return nil
+	}
+	return &withDetail{err: err, detail: detail}
+}
+
+func WithDetailf(err error, format string, args ...interface{}) error {
+	if err == nil {
+		return nil
+	}
+	return &withDetail{err: err, detail: fmt.Sprintf(format, args...)}
+}
+
+func GetDetail(err error) string {
+	for e := err; e != nil; e = Unwrap(e) {
+		if errDetail, ok := e.(ErrDetail); ok {
+			return errDetail.Detail()
+		}
+	}
+	return ""
+}
diff --git a/pkg/errors/domain.go b/pkg/errors/domain.go
new file mode 100644
index 0000000000000000000000000000000000000000..35642c34e9537d236a83092855089804164a90de
--- /dev/null
+++ b/pkg/errors/domain.go
@@ -0,0 +1,58 @@
+package errors
+
+import (
+	"fmt"
+	"io"
+)
+
+const (
+	NoDomain     = ""
+	GlobalDomain = "global"
+)
+
+type ErrDomain interface{ Domain() string }
+
+type withDomain struct {
+	err    error
+	domain string
+}
+
+func (w *withDomain) Domain() string { return w.domain }
+func (w *withDomain) Error() string  { return w.err.Error() }
+func (w *withDomain) Unwrap() error  { return w.err }
+
+func (w *withDomain) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		if s.Flag('+') {
+			fmt.Fprintf(s, "%+v", w.Unwrap())
+			fmt.Fprintf(s, "\nerror domain: %s", w.domain)
+			return
+		}
+		fallthrough
+	case 's':
+		io.WriteString(s, w.Error())
+	case 'q':
+		fmt.Fprintf(s, "%q", w.Error())
+	}
+}
+
+func WithDomain(err error, domain string) error {
+	if err == nil {
+		return nil
+	}
+	return &withDomain{err: err, domain: domain}
+}
+
+func Global(err error) error {
+	return WithDomain(err, GlobalDomain)
+}
+
+func GetDomain(err error) string {
+	for e := err; e != nil; e = Unwrap(e) {
+		if errDomain, ok := e.(ErrDomain); ok {
+			return errDomain.Domain()
+		}
+	}
+	return NoDomain
+}
diff --git a/pkg/errors/error.go b/pkg/errors/error.go
new file mode 100644
index 0000000000000000000000000000000000000000..cb8ec386db413ecc1a8f33f6dddd45c29359ea85
--- /dev/null
+++ b/pkg/errors/error.go
@@ -0,0 +1,51 @@
+package errors
+
+//import (
+//	"errors"
+//)
+
+//var (
+//	ErrSpaceRequired      = errors.New("space required")
+//	ErrSpaceHostRequired  = errors.New("space host required")
+//	ErrSpaceNotFound      = errors.New("space not found")
+//	ErrSpaceAlreadyExists = errors.New("space already exists")
+//)
+//
+//var errs = []error{
+//	ErrSpaceRequired,
+//	ErrSpaceHostRequired,
+//	ErrSpaceNotFound,
+//	ErrSpaceAlreadyExists,
+//}
+//
+//var errMap map[string]error
+//
+////func New(text string) error {
+////	if errMap == nil {
+////		errMap = make(map[string]error)
+////		for _, e := range errs {
+////			errMap[e.Error()] = e
+////		}
+////	}
+////
+////	if e, ok := errMap[text]; ok {
+////		return e
+////	}
+////
+////	return errors.New(text)
+////}
+//
+//func IsAny(err error, targets ...error) bool {
+//	for _, t := range targets {
+//		if errors.Is(err, t) {
+//			return true
+//		}
+//	}
+//	return false
+//}
+//
+//var (
+//	Is     = errors.Is
+//	As     = errors.As
+//	Unwrap = errors.Unwrap
+//)
diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go
new file mode 100644
index 0000000000000000000000000000000000000000..952cd8688db8dffe737f63ccba6e1abd4521f715
--- /dev/null
+++ b/pkg/errors/errors.go
@@ -0,0 +1,72 @@
+package errors
+
+import (
+	stderr "errors"
+
+	"github.com/pkg/errors"
+)
+
+var (
+	Is     = stderr.Is
+	As     = stderr.As
+	New    = stderr.New
+	Unwrap = stderr.Unwrap
+	Errorf = errors.Errorf
+	Wrap   = errors.Wrap
+	Wrapf  = errors.Wrapf
+
+//Wrap          = errors.Wrap
+//Wrapf         = errors.Wrapf
+//WithMessage   = errors.WithMessage
+//CombineErrors = errors.CombineErrors
+////Detail    = errors.Detail
+////WithDetailf   = errors.WithDetailf
+////WithHint = errors.WithHint
+
+//Mark   = errors.Mark
+//Tags   = errors.WithContextTags
+//
+//CoreDomain      = errors.Domain("core")
+//StorageDomain   = errors.Domain("storage")
+//ServiceDomain   = errors.Domain("service")
+//TransportDomain = errors.Domain("transport")
+//EventsDomain    = errors.Domain("events")
+)
+
+//func New(msg string) error {
+//	return &Error{Message: msg}
+//}
+
+//func Trace(err error) error {
+//	return WithStack(WithID(err))
+//}
+//
+//func Wrap(err error, msg string) error {
+//	return WithID(errors.Wrap(err, msg))
+//}
+//
+//func ErrorBuilder(domain string) func(string) error {
+//	return func(msg string) error {
+//		return WithDomain(WithID(New(msg)), domain)
+//	}
+//}
+//
+//func ErrorfBuilder(domain string) func(format string, args ...interface{}) error {
+//	return func(format string, args ...interface{}) error {
+//		return WithDomain(WithID(Errorf(format, args...)), domain)
+//	}
+//}
+//
+//type Error struct {
+//	Message string
+//}
+//
+//func (e *Error) Error() string { return e.Message }
+//func (e *Error) Is(err error) bool {
+//	if errr, ok := err.(*Error); ok {
+//		if e.Message == errr.Message {
+//			return true
+//		}
+//	}
+//	return false
+//}
diff --git a/pkg/errors/errors_test.go b/pkg/errors/errors_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..365e7120979a73209df1a5e68a10fa76882d8ad0
--- /dev/null
+++ b/pkg/errors/errors_test.go
@@ -0,0 +1,131 @@
+package errors
+
+//
+//import (
+//	"fmt"
+//	"testing"
+//
+//	jsoniter "github.com/json-iterator/go"
+//	"github.com/perxteam/hoop4/yaml"
+//	"github.com/stretchr/testify/assert"
+//	"github.com/stretchr/testify/require"
+//)
+//
+//func TestErrors_OmitemptyError(t *testing.T) {
+//	type Struct struct {
+//		Some  string `json:"some"`
+//		Error error  `json:"error,omitempty"`
+//	}
+//
+//	s := &Struct{Some: "Some"}
+//	b, err := jsoniter.Marshal(s)
+//	require.NoError(t, err)
+//
+//	var ss Struct
+//	err = jsoniter.Unmarshal(b, &ss)
+//	require.NoError(t, err)
+//}
+//
+//func TestErrors_EmptyError(t *testing.T) {
+//	type Struct struct {
+//		Some  string `json:"some"`
+//		Error error  `json:"error"`
+//	}
+//
+//	s := &Struct{Some: "Some"}
+//	b, err := jsoniter.Marshal(s)
+//	require.NoError(t, err)
+//
+//	var ss Struct
+//	err = jsoniter.Unmarshal(b, &ss)
+//	require.NoError(t, err)
+//}
+//
+//func TestErrors_MarshalYAML(t *testing.T) {
+//	type S struct {
+//		Error error
+//	}
+//	l1 := &S{New("test")}
+//
+//	_, err := yaml.Marshal(l1)
+//	require.NoError(t, err)
+//}
+//
+//func TestErrors_Encode(t *testing.T) {
+//	e := Global(WithID(New("test")))
+//	e = WithStack(e)
+//	e = NotFound(e)
+//	e = fmt.Errorf("Got error %w", e)
+//	e = WithDetail(WithHint(e, "Hint"), "Detail")
+//	e = WithErrors(e, New("add1"), New("add2"))
+//	enc := EncodeError(e)
+//	err := DecodeError(enc)
+//
+//	assert.True(t, IsNotFound(err))
+//	assert.Equal(t, "Got error test", err.Error())
+//	assert.True(t, Is(err, New("Got error test")))
+//	assert.Equal(t, "Hint", GetHint(err))
+//	assert.Equal(t, "Detail", GetDetail(err))
+//	assert.NotEmpty(t, GetID(err))
+//	assert.Equal(t, GetID(e), GetID(err))
+//	assert.Equal(t, GlobalDomain, GetDomain(err))
+//	errs := GetErrors(err)
+//	require.Len(t, errs, 2)
+//	assert.True(t, Is(errs[0], New("add1")))
+//	assert.True(t, Is(errs[1], New("add2")))
+//}
+//
+//func TestErrors_JSONEncode(t *testing.T) {
+//	type Struct struct {
+//		Some  string `json:"some"`
+//		Error error  `json:"error,omitempty"`
+//	}
+//
+//	s := &Struct{Some: "Some"}
+//	e := Global(WithID(New("test")))
+//	e = WithStack(e)
+//	e = NotFound(e)
+//	e = fmt.Errorf("Got error %w", e)
+//	e = WithDetail(WithHint(e, "Hint"), "Detail")
+//	e = WithErrors(e, New("add1"), New("add2"))
+//	s.Error = e
+//
+//	b, jerr := jsoniter.Marshal(s)
+//	require.NoError(t, jerr)
+//
+//	var st Struct
+//	jerr = jsoniter.Unmarshal(b, &st)
+//	require.NoError(t, jerr)
+//	err := st.Error
+//
+//	assert.True(t, IsNotFound(err))
+//	assert.Equal(t, "Got error test", err.Error())
+//	assert.True(t, Is(err, New("Got error test")))
+//	assert.Equal(t, "Hint", GetHint(err))
+//	assert.Equal(t, "Detail", GetDetail(err))
+//	assert.NotEmpty(t, GetID(err))
+//	assert.Equal(t, GetID(e), GetID(err))
+//	assert.Equal(t, GlobalDomain, GetDomain(err))
+//	errs := GetErrors(err)
+//	require.Len(t, errs, 2)
+//	assert.True(t, Is(errs[0], New("add1")))
+//	assert.True(t, Is(errs[1], New("add2")))
+//}
+//
+//func TestErrors_MultiErrors(t *testing.T) {
+//	err := WithErrors(New("main"), New("add1"), New("add2"))
+//	errs := GetErrors(err)
+//	require.Len(t, errs, 2)
+//	assert.True(t, Is(errs[0], New("add1")))
+//	assert.True(t, Is(errs[1], New("add2")))
+//	assert.Equal(t, "main", err.Error())
+//}
+//
+////func TestErrors_NotFound(t *testing.T) {
+////	e := NotFound(New("test"))
+////	fmt.Println(IsNotFound(e))
+////	enc := EncodeError(e)
+////	fmt.Printf("%#v", enc)
+////	//require.NoError(t, e)
+////}
+//
diff --git a/pkg/errors/field.go b/pkg/errors/field.go
new file mode 100644
index 0000000000000000000000000000000000000000..891863f0471ab0215229f1eb848f8fb3822e1334
--- /dev/null
+++ b/pkg/errors/field.go
@@ -0,0 +1,76 @@
+package errors
+
+import (
+	"fmt"
+	"io"
+
+	"github.com/hashicorp/go-multierror"
+)
+
+type FieldError interface {
+	Field() string
+	Error() string
+}
+
+type withField struct {
+	err   error
+	field string
+}
+
+func (w *withField) Field() string { return w.field }
+func (w *withField) Error() string { return fmt.Sprintf("field '%s': %s", w.field, w.err.Error()) }
+func (w *withField) Unwrap() error { return w.err }
+
+func (w *withField) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		if s.Flag('+') {
+			fmt.Fprintf(s, "%+v", w.Unwrap())
+			fmt.Fprintf(s, "\nfield: %s", w.field)
+			return
+		}
+		fallthrough
+	case 's':
+		io.WriteString(s, w.Error())
+	case 'q':
+		fmt.Fprintf(s, "%q", w.Error())
+	}
+}
+
+func WithField(err error, field string) error {
+	if err == nil {
+		return nil
+	}
+
+	var merr *multierror.Error
+	if As(err, &merr) {
+		for i, e := range merr.Errors {
+			merr.Errors[i] = WithField(e, field)
+		}
+		return err
+	}
+
+	var ferr *withField
+	if As(err, &ferr) {
+		if ferr.field != "" {
+			ferr.field = field + "." + ferr.field
+		} else {
+			ferr.field = field
+		}
+		return err
+	}
+
+	return &withField{
+		field: field,
+		err:   err,
+	}
+}
+
+func GetField(err error) string {
+	for e := err; e != nil; e = Unwrap(e) {
+		if errField, ok := e.(FieldError); ok {
+			return errField.Field()
+		}
+	}
+	return ""
+}
diff --git a/pkg/errors/grpc/middleware.go b/pkg/errors/grpc/middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..a9f51c475f3fc10371bb8a20ff53df2a78d86709
--- /dev/null
+++ b/pkg/errors/grpc/middleware.go
@@ -0,0 +1,32 @@
+package grpc
+
+import (
+	"context"
+
+	"github.com/go-kit/kit/endpoint"
+	"google.golang.org/grpc/status"
+)
+
+func ServerMiddleware(ep endpoint.Endpoint) endpoint.Endpoint {
+	return func(ctx context.Context, request interface{}) (response interface{}, err error) {
+		response, err = ep(ctx, request)
+		if err != nil {
+			if st := StatusFromError(err); st != nil {
+				err = st.Err()
+			}
+
+		}
+		return response, err
+	}
+}
+
+func ClientMiddleware(ep endpoint.Endpoint) endpoint.Endpoint {
+	return func(ctx context.Context, request interface{}) (response interface{}, err error) {
+		response, err = ep(ctx, request)
+		if err != nil {
+			st, _ := status.FromError(err)
+			err = ErrorFromStatus(st)
+		}
+		return response, err
+	}
+}
diff --git a/pkg/errors/grpc/middleware_test.go b/pkg/errors/grpc/middleware_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..6140aaed091b46c1e214acb77dc7f6d5d2221107
--- /dev/null
+++ b/pkg/errors/grpc/middleware_test.go
@@ -0,0 +1,47 @@
+package grpc
+
+import (
+	"context"
+	"testing"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"git.perx.ru/perxis/perxis-go/proto/common"
+	"github.com/go-kit/kit/endpoint"
+	"github.com/hashicorp/go-multierror"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/status"
+)
+
+func testEndopoint(e error) endpoint.Endpoint {
+	return func(ctx context.Context, request interface{}) (response interface{}, err error) {
+		return nil, e
+	}
+}
+
+func TestMiddleware(t *testing.T) {
+	merr := multierror.Append(errors.WithField(errors.New("err1"), "fld1"), errors.WithField(errors.New("err2"), "fld2"))
+	err := errors.WrapErr(errors.New("test"), merr)
+	err = errors.WithID(err)
+	err = errors.NotFound(err)
+	err = errors.Wrap(err, "wrapped")
+	errID := errors.GetID(err)
+
+	serverEp := ServerMiddleware(testEndopoint(err))
+	_, grpcErr := serverEp(context.Background(), nil)
+	st, _ := status.FromError(grpcErr)
+	assert.Equal(t, "wrapped: test", st.Message())
+	assert.Equal(t, codes.NotFound, st.Code())
+	dt := st.Details()
+	require.Len(t, dt, 1)
+	require.IsType(t, (*common.Error)(nil), dt[0])
+
+	clientEp := ClientMiddleware(testEndopoint(grpcErr))
+	_, cliErr := clientEp(context.Background(), nil)
+	assert.Equal(t, "wrapped: test", cliErr.Error(), "ожидается то же строковое значние, что и у изначальной ошибки")
+	assert.Equal(t, errID, errors.GetID(cliErr))
+	assert.Equal(t, codes.NotFound, errors.GetStatusCode(cliErr))
+	assert.ErrorAs(t, err, &merr)
+	assert.Len(t, merr.Errors, 2)
+}
diff --git a/pkg/errors/grpc/status.go b/pkg/errors/grpc/status.go
new file mode 100644
index 0000000000000000000000000000000000000000..e9cc9ee08d1e962e62da1b3d9a9407bc02b3fc2d
--- /dev/null
+++ b/pkg/errors/grpc/status.go
@@ -0,0 +1,138 @@
+package grpc
+
+import (
+	"fmt"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"git.perx.ru/perxis/perxis-go/proto/common"
+	"github.com/hashicorp/go-multierror"
+	"google.golang.org/grpc/status"
+)
+
+func getHelp(err error) *common.Error_Help {
+	if help := errors.GetHelp(err); help != nil {
+		pbhelp := &common.Error_Help{}
+		for _, hl := range help.Links {
+			pbhelp.Links = append(pbhelp.Links, &common.Error_Help_Link{Description: hl.Description, Url: hl.URL})
+		}
+		return pbhelp
+	}
+	return nil
+}
+
+func helpFromPb(err error, pberr *common.Error) error {
+	if pberr.Help == nil || len(pberr.Help.Links) == 0 {
+		return err
+	}
+	h := &errors.Help{}
+	for _, l := range pberr.Help.Links {
+		h.Links = append(h.Links, errors.HelpLink{Description: l.Description, URL: l.Url})
+	}
+	return errors.WithHelp(err, h)
+}
+
+func getBadRequest(err error) *common.Error_BadRequest {
+	br := &common.Error_BadRequest{}
+
+	var merr *multierror.Error
+	if errors.As(err, &merr) {
+		for _, e := range merr.Errors {
+			var errField errors.FieldError
+			if errors.As(e, &errField) {
+				br.Errors = append(br.Errors, &common.Error_BadRequest_FieldViolation{Field: errField.Field(), Description: errors.Unwrap(errField).Error()})
+			}
+		}
+	} else {
+		var errField errors.FieldError
+		if errors.As(err, &errField) {
+			br.Errors = append(br.Errors, &common.Error_BadRequest_FieldViolation{Field: errField.Field(), Description: errors.Unwrap(errField).Error()})
+		}
+	}
+
+	if len(br.Errors) > 0 {
+		return br
+	}
+
+	return nil
+}
+
+func getDebugInfo(err error) *common.Error_DebugInfo {
+	if trace, ok := errors.GetStackTrace(err); ok {
+		di := &common.Error_DebugInfo{}
+		for _, t := range trace {
+			di.StackTrace = append(di.StackTrace, fmt.Sprintf("%+v", t))
+		}
+		return di
+	}
+
+	return nil
+}
+
+func badRequestFromPb(err error, pberr *common.Error) error {
+	if pberr.BadRequest == nil || len(pberr.BadRequest.Errors) == 0 {
+		return err
+	}
+	var merr error
+	for _, e := range pberr.BadRequest.Errors {
+		merr = multierror.Append(merr, errors.WithField(errors.New(e.Description), e.Field))
+	}
+	return errors.WrapErr(err, merr)
+}
+
+func StatusFromError(err error) *status.Status {
+	if err == nil {
+		return nil
+	}
+	err = errors.WithID(err)
+	pberr := &common.Error{
+		ErrorCode:         errors.GetCode(err),
+		ErrorId:           errors.GetID(err),
+		Reason:            "", // TBD
+		Domain:            errors.GetDomain(err),
+		Help:              getHelp(err),
+		Metadata:          nil, // TBD
+		BadRequest:        getBadRequest(err),
+		DebugInfo:         getDebugInfo(err),
+		LocalizedMessages: nil, // TBD
+	}
+
+	st := status.New(errors.GetStatusCode(err), err.Error())
+	st, _ = st.WithDetails(pberr)
+	return st
+}
+
+func convertProtoError(err error, pberr *common.Error) error {
+	if pberr == nil {
+		return nil
+	}
+
+	// Должен быть первым в цепочке
+	err = badRequestFromPb(err, pberr)
+
+	if pberr.ErrorCode != 0 {
+		err = errors.WithCode(err, pberr.ErrorCode)
+	}
+
+	if pberr.ErrorId != "" {
+		err = errors.SetID(err, pberr.ErrorId)
+	}
+
+	if pberr.Domain != "" {
+		err = errors.WithDomain(err, pberr.Domain)
+	}
+
+	err = helpFromPb(err, pberr)
+	return err
+}
+
+func ErrorFromStatus(st *status.Status) error {
+	details := st.Details()
+	err := errors.New(st.Message())
+	if len(details) > 0 {
+		if pberr, ok := details[0].(*common.Error); ok {
+			err = convertProtoError(err, pberr)
+		}
+	}
+	err = errors.WithStatusCode(err, st.Code())
+	return err
+}
diff --git a/pkg/errors/grpc/status_test.go b/pkg/errors/grpc/status_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..4a36f34fdb5239d9358fb05e0b345d4288cae047
--- /dev/null
+++ b/pkg/errors/grpc/status_test.go
@@ -0,0 +1,62 @@
+package grpc
+
+import (
+	"reflect"
+	"testing"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"git.perx.ru/perxis/perxis-go/proto/common"
+	"github.com/hashicorp/go-multierror"
+)
+
+func Test_getBadRequest(t *testing.T) {
+	tests := []struct {
+		name string
+		err  error
+		want *common.Error_BadRequest
+	}{
+		{"Without field error", errors.New("some"), nil},
+		{"Field error", errors.WithField(errors.New("some"), "fld1"),
+			&common.Error_BadRequest{Errors: []*common.Error_BadRequest_FieldViolation{{Description: "some", Field: "fld1"}}}},
+		{"Deep field error", errors.Wrap(errors.WithField(errors.New("some"), "fld1"), "extra"),
+			&common.Error_BadRequest{Errors: []*common.Error_BadRequest_FieldViolation{{Description: "some", Field: "fld1"}}}},
+		{"Nested field errors", errors.WithField(errors.WithField(errors.New("some"), "fld1"), "fld2"),
+			&common.Error_BadRequest{Errors: []*common.Error_BadRequest_FieldViolation{{Description: "some", Field: "fld2.fld1"}}}},
+		{"Deep nested field errors", errors.WithField(errors.Wrap(errors.WithField(errors.New("some"), "fld1"), "extra"), "fld2"),
+			&common.Error_BadRequest{Errors: []*common.Error_BadRequest_FieldViolation{{Description: "some", Field: "fld2.fld1"}}}},
+		{"Multi Field error", multierror.Append(errors.WithField(errors.New("some"), "fld1"), errors.WithField(errors.New("some"), "fld2")),
+			&common.Error_BadRequest{Errors: []*common.Error_BadRequest_FieldViolation{{Description: "some", Field: "fld1"}, {Description: "some", Field: "fld2"}}}},
+		{"Nested Multi Field error", errors.WithField(multierror.Append(errors.WithField(errors.New("some"), "fld1"), errors.WithField(errors.New("some"), "fld2")), "fld3"),
+			&common.Error_BadRequest{Errors: []*common.Error_BadRequest_FieldViolation{{Description: "some", Field: "fld3.fld1"}, {Description: "some", Field: "fld3.fld2"}}}},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := getBadRequest(tt.err); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("getBadRequest() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_getHelp(t *testing.T) {
+	tests := []struct {
+		name string
+		err  error
+		want *common.Error_Help
+	}{
+		{"Without help", errors.New("some"), nil},
+		{"With help",
+			errors.WithHelp(errors.New("some"), &errors.Help{Links: []errors.HelpLink{{Description: "Help1", URL: "Url1"}}}),
+			&common.Error_Help{Links: []*common.Error_Help_Link{{Description: "Help1", Url: "Url1"}}}},
+		{"With nested help",
+			errors.WithHelp(errors.WithHelp(errors.New("some"), &errors.Help{Links: []errors.HelpLink{{Description: "Help2", URL: "Url2"}}}), &errors.Help{Links: []errors.HelpLink{{Description: "Help1", URL: "Url1"}}}),
+			&common.Error_Help{Links: []*common.Error_Help_Link{{Description: "Help1", Url: "Url1"}, {Description: "Help2", Url: "Url2"}}}},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := getHelp(tt.err); !reflect.DeepEqual(got, tt.want) {
+				t.Errorf("getHelp() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/pkg/errors/help.go b/pkg/errors/help.go
new file mode 100644
index 0000000000000000000000000000000000000000..caf84d8796b7d9bc8dbef424ceaec0410e062a88
--- /dev/null
+++ b/pkg/errors/help.go
@@ -0,0 +1,62 @@
+package errors
+
+type Help struct {
+	Links []HelpLink
+}
+
+type HelpLink struct {
+	Description string
+	URL         string
+}
+
+type HelpError interface{ Help() *Help }
+
+type withHelp struct {
+	err  error
+	help *Help
+}
+
+func (w *withHelp) Help() *Help   { return w.help }
+func (w *withHelp) Error() string { return w.err.Error() }
+func (w *withHelp) Unwrap() error { return w.err }
+
+//func (w *withHelp) Format(s fmt.State, verb rune) {
+//	switch verb {
+//	case 'v':
+//		if s.Flag('+') {
+//			fmt.Fprintf(s, "%+v", w.Unwrap())
+//			fmt.Fprintf(s, "\nerror hint: %s", w.hint)
+//			return
+//		}
+//		fallthrough
+//	case 's':
+//		io.WriteString(s, w.Error())
+//	case 'q':
+//		fmt.Fprintf(s, "%q", w.Error())
+//	}
+//}
+
+func WithHelp(err error, help *Help) error {
+	if err == nil {
+		return nil
+	}
+	return &withHelp{err: err, help: help}
+}
+
+func GetHelp(err error) *Help {
+	var help Help
+	for e := err; e != nil; e = Unwrap(e) {
+		if errHelp, ok := e.(HelpError); ok {
+			h := errHelp.Help()
+			if h != nil && len(h.Links) > 0 {
+				help.Links = append(help.Links, h.Links...)
+			}
+		}
+	}
+
+	if len(help.Links) > 0 {
+		return &help
+	}
+
+	return nil
+}
diff --git a/pkg/errors/hint.go b/pkg/errors/hint.go
new file mode 100644
index 0000000000000000000000000000000000000000..f654d4024936cdf19e65b89aaca2fef3070fb5bb
--- /dev/null
+++ b/pkg/errors/hint.go
@@ -0,0 +1,57 @@
+package errors
+
+import (
+	"fmt"
+	"io"
+)
+
+type ErrHint interface{ Hint() string }
+
+type withHint struct {
+	err  error
+	hint string
+}
+
+func (w *withHint) Hint() string  { return w.hint }
+func (w *withHint) Error() string { return w.err.Error() }
+func (w *withHint) Cause() error  { return w.err }
+func (w *withHint) Unwrap() error { return w.err }
+
+func (w *withHint) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		if s.Flag('+') {
+			fmt.Fprintf(s, "%+v", w.Cause())
+			fmt.Fprintf(s, "\nerror hint: %s", w.hint)
+			return
+		}
+		fallthrough
+	case 's':
+		io.WriteString(s, w.Error())
+	case 'q':
+		fmt.Fprintf(s, "%q", w.Error())
+	}
+}
+
+func WithHint(err error, hint string) error {
+	if err == nil {
+		return nil
+	}
+	return &withHint{err: err, hint: hint}
+}
+
+func WithHintf(err error, format string, args ...interface{}) error {
+	if err == nil {
+		return nil
+	}
+	return &withHint{err: err, hint: fmt.Sprintf(format, args...)}
+}
+
+func GetHint(err error) string {
+	for e := err; e != nil; e = Unwrap(e) {
+		if errDetail, ok := e.(ErrHint); ok {
+			return errDetail.Hint()
+		}
+	}
+	return ""
+}
diff --git a/pkg/errors/id.go b/pkg/errors/id.go
new file mode 100644
index 0000000000000000000000000000000000000000..9d9804882e0892c92998560b9be4ae7ec0fe1558
--- /dev/null
+++ b/pkg/errors/id.go
@@ -0,0 +1,64 @@
+package errors
+
+import (
+	"fmt"
+	"io"
+
+	"github.com/rs/xid"
+)
+
+type ErrID interface{ ID() string }
+
+type withId struct {
+	err error
+	id  string
+}
+
+func (w *withId) ID() string    { return w.id }
+func (w *withId) Error() string { return w.err.Error() }
+func (w *withId) Cause() error  { return w.err }
+func (w *withId) Unwrap() error { return w.err }
+
+func (w *withId) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		if s.Flag('+') {
+			fmt.Fprintf(s, "%+v", w.Cause())
+			fmt.Fprintf(s, "\nerror id: %s", w.id)
+			return
+		}
+		fallthrough
+	case 's':
+		io.WriteString(s, w.Error())
+	case 'q':
+		fmt.Fprintf(s, "%q", w.Error())
+	}
+}
+
+func SetID(err error, id string) error {
+	if GetID(err) != "" {
+		return err
+	}
+	return &withId{err: err, id: id}
+}
+
+func GetID(err error) string {
+	for e := err; e != nil; e = Unwrap(e) {
+		if errID, ok := e.(ErrID); ok {
+			return errID.ID()
+		}
+	}
+	return ""
+}
+
+func WithID(err error) error {
+	if err == nil {
+		return nil
+	}
+	id := GetID(err)
+	if id == "" {
+		id = xid.New().String()
+		err = &withId{err: err, id: id}
+	}
+	return err
+}
diff --git a/pkg/errors/multi.go b/pkg/errors/multi.go
new file mode 100644
index 0000000000000000000000000000000000000000..d27392c7a96ec2a8b653699052d9cbf7ee679ba8
--- /dev/null
+++ b/pkg/errors/multi.go
@@ -0,0 +1,58 @@
+package errors
+
+import (
+	"fmt"
+	"io"
+)
+
+type MultiError interface {
+	Errors() []error
+	Error() string
+}
+
+type withMultiError struct {
+	err    error
+	errors []error
+}
+
+func (w *withMultiError) Errors() []error { return w.errors }
+func (w *withMultiError) Error() string   { return w.err.Error() }
+func (w *withMultiError) Unwrap() error   { return w.err }
+
+func (w *withMultiError) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		if s.Flag('+') {
+			fmt.Fprintf(s, "%+v", w.Unwrap())
+			if len(w.errors) > 0 {
+				fmt.Fprint(s, "\ninclude errors:\n")
+				for _, e := range w.errors {
+					fmt.Fprintf(s, "  %s\n", e)
+				}
+			}
+
+			return
+		}
+		fallthrough
+	case 's':
+		io.WriteString(s, w.Error())
+	case 'q':
+		fmt.Fprintf(s, "%q", w.Error())
+	}
+}
+
+func WithErrors(err error, errs ...error) error {
+	if err == nil || len(errs) == 0 {
+		return err
+	}
+	return &withMultiError{err: err, errors: errs}
+}
+
+func GetErrors(err error) []error {
+	for e := err; e != nil; e = Unwrap(e) {
+		if errMulti, ok := e.(MultiError); ok {
+			return errMulti.Errors()
+		}
+	}
+	return nil
+}
diff --git a/pkg/errors/source.go b/pkg/errors/source.go
new file mode 100644
index 0000000000000000000000000000000000000000..c43df751c063b7c2e419071e22812aa890074b42
--- /dev/null
+++ b/pkg/errors/source.go
@@ -0,0 +1,70 @@
+package errors
+
+import (
+	"fmt"
+	"io"
+)
+
+type ErrorSource struct {
+	Pointer   string `json:"pointer,omitempty"`
+	Parameter string `json:"parameter,omitempty"`
+	Field     string `json:"field,omitempty"`
+}
+
+func NewSource(args ...string) *ErrorSource {
+	s := &ErrorSource{}
+	if len(args) > 0 {
+		s.Pointer = args[0]
+	}
+	if len(args) > 1 {
+		s.Parameter = args[1]
+	}
+	if len(args) > 2 {
+		s.Field = args[2]
+	}
+	return s
+}
+
+type ErrSource interface{ Source() *ErrorSource }
+
+type withSource struct {
+	err    error
+	source *ErrorSource
+}
+
+func (w *withSource) Source() *ErrorSource { return w.source }
+func (w *withSource) Error() string        { return w.err.Error() }
+func (w *withSource) Cause() error         { return w.err }
+func (w *withSource) Unwrap() error        { return w.err }
+
+func (w *withSource) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		if s.Flag('+') {
+			fmt.Fprintf(s, "%+v", w.Cause())
+			fmt.Fprintf(s, "\nerror source: %+v", w.source)
+			return
+		}
+		fallthrough
+	case 's':
+		io.WriteString(s, w.Error())
+	case 'q':
+		fmt.Fprintf(s, "%q", w.Error())
+	}
+}
+
+func WithSource(err error, source *ErrorSource) error {
+	if err == nil {
+		return nil
+	}
+	return &withSource{err: err, source: source}
+}
+
+func GetSource(err error) *ErrorSource {
+	for e := err; e != nil; e = Unwrap(e) {
+		if errSource, ok := e.(ErrSource); ok {
+			return errSource.Source()
+		}
+	}
+	return nil
+}
diff --git a/pkg/errors/stack.go b/pkg/errors/stack.go
new file mode 100644
index 0000000000000000000000000000000000000000..0b9efc3360a4ae05f8d50ecf119464262974659a
--- /dev/null
+++ b/pkg/errors/stack.go
@@ -0,0 +1,30 @@
+package errors
+
+import (
+	"github.com/pkg/errors"
+)
+
+var WithStack = errors.WithStack
+
+type StackTrace = errors.StackTrace
+
+type StackTracer interface {
+	StackTrace() errors.StackTrace
+}
+
+func GetStackTrace(err error) (StackTrace, bool) {
+	if s := getStackTracer(err); s != nil {
+		return s.StackTrace(), true
+	}
+
+	return nil, false
+}
+
+func getStackTracer(err error) StackTracer {
+	for e := err; e != nil; e = Unwrap(e) {
+		if s, ok := err.(StackTracer); ok {
+			return s
+		}
+	}
+	return nil
+}
diff --git a/pkg/errors/status.go b/pkg/errors/status.go
new file mode 100644
index 0000000000000000000000000000000000000000..04b8261296574fb72e5750edc100d8c2e805f9ff
--- /dev/null
+++ b/pkg/errors/status.go
@@ -0,0 +1,76 @@
+package errors
+
+import (
+	"errors"
+	"fmt"
+	"io"
+
+	"google.golang.org/grpc/codes"
+)
+
+type ErrStatus interface {
+	StatusCode() codes.Code
+	Error() string
+}
+
+type withStatusError struct {
+	err  error
+	code codes.Code
+}
+
+func (w *withStatusError) StatusCode() codes.Code { return w.code }
+func (w *withStatusError) Error() string          { return w.err.Error() }
+func (w *withStatusError) Unwrap() error          { return w.err }
+
+func (w *withStatusError) Format(s fmt.State, verb rune) {
+	switch verb {
+	case 'v':
+		if s.Flag('+') {
+			fmt.Fprintf(s, "%+v", w.Unwrap())
+			fmt.Fprintf(s, "\nerror status: %s", w.code)
+			return
+		}
+		fallthrough
+	case 's':
+		io.WriteString(s, w.Error())
+	case 'q':
+		fmt.Fprintf(s, "%q", w.Error())
+	}
+}
+
+func WithStatusCode(err error, code codes.Code) error {
+	if err == nil || code == codes.Unknown {
+		return err
+	}
+
+	var se *withStatusError
+	if errors.As(err, &se) {
+		if se.StatusCode() == code {
+			return err
+		}
+	}
+
+	return &withStatusError{
+		err:  err,
+		code: code,
+	}
+}
+
+func GetStatusCode(err error) codes.Code {
+	for e := err; e != nil; e = Unwrap(e) {
+		if errStatus, ok := e.(ErrStatus); ok {
+			return errStatus.StatusCode()
+		}
+	}
+	return codes.Unknown
+}
+
+func InvalidArgument(err error) error    { return WithStatusCode(err, codes.InvalidArgument) }
+func DeadlineExceeded(err error) error   { return WithStatusCode(err, codes.DeadlineExceeded) }
+func NotFound(err error) error           { return WithStatusCode(err, codes.NotFound) }
+func AlreadyExists(err error) error      { return WithStatusCode(err, codes.AlreadyExists) }
+func PermissionDenied(err error) error   { return WithStatusCode(err, codes.PermissionDenied) }
+func FailedPrecondition(err error) error { return WithStatusCode(err, codes.FailedPrecondition) }
+func Internal(err error) error           { return WithStatusCode(err, codes.Internal) }
+func Unavailable(err error) error        { return WithStatusCode(err, codes.Unavailable) }
+func Unauthenticated(err error) error    { return WithStatusCode(err, codes.Unauthenticated) }
diff --git a/pkg/errors/wrap.go b/pkg/errors/wrap.go
new file mode 100644
index 0000000000000000000000000000000000000000..7403b3c1fae4f5a57f91f5bae992a6be1004f5ea
--- /dev/null
+++ b/pkg/errors/wrap.go
@@ -0,0 +1,38 @@
+package errors
+
+import "errors"
+
+type wrappedErr struct {
+	Err     error
+	Wrapped error
+}
+
+func WrapErr(err, wrapped error) error {
+	if wrapped == nil {
+		return nil
+	}
+	return &wrappedErr{
+		Err:     err,
+		Wrapped: wrapped,
+	}
+}
+
+func (w *wrappedErr) Error() string {
+	return w.Err.Error()
+}
+
+func (w *wrappedErr) WrappedErrors() []error {
+	return []error{w.Err, w.Wrapped}
+}
+
+func (w *wrappedErr) Unwrap() error {
+	return w.Wrapped
+}
+
+func (w *wrappedErr) Is(target error) bool {
+	return errors.Is(w.Err, target)
+}
+
+func (w *wrappedErr) As(target interface{}) bool {
+	return errors.As(w.Err, target)
+}
diff --git a/pkg/space/mocks/Spaces.go b/pkg/space/mocks/Spaces.go
new file mode 100644
index 0000000000000000000000000000000000000000..6fd9ef4aaf8012c82973ed3d94815babed9acd65
--- /dev/null
+++ b/pkg/space/mocks/Spaces.go
@@ -0,0 +1,150 @@
+// Code generated by mockery v2.22.1. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	space "git.perx.ru/perxis/perxis-go/pkg/space"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Spaces is an autogenerated mock type for the Spaces type
+type Spaces struct {
+	mock.Mock
+}
+
+// Create provides a mock function with given fields: ctx, _a1
+func (_m *Spaces) Create(ctx context.Context, _a1 *space.Space) (*space.Space, error) {
+	ret := _m.Called(ctx, _a1)
+
+	var r0 *space.Space
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, *space.Space) (*space.Space, error)); ok {
+		return rf(ctx, _a1)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, *space.Space) *space.Space); ok {
+		r0 = rf(ctx, _a1)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*space.Space)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, *space.Space) error); ok {
+		r1 = rf(ctx, _a1)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Delete provides a mock function with given fields: ctx, spaceId
+func (_m *Spaces) Delete(ctx context.Context, spaceId string) error {
+	ret := _m.Called(ctx, spaceId)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
+		r0 = rf(ctx, spaceId)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Get provides a mock function with given fields: ctx, spaceId
+func (_m *Spaces) Get(ctx context.Context, spaceId string) (*space.Space, error) {
+	ret := _m.Called(ctx, spaceId)
+
+	var r0 *space.Space
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, string) (*space.Space, error)); ok {
+		return rf(ctx, spaceId)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, string) *space.Space); ok {
+		r0 = rf(ctx, spaceId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*space.Space)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, spaceId)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// List provides a mock function with given fields: ctx, orgId
+func (_m *Spaces) List(ctx context.Context, orgId string) ([]*space.Space, error) {
+	ret := _m.Called(ctx, orgId)
+
+	var r0 []*space.Space
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, string) ([]*space.Space, error)); ok {
+		return rf(ctx, orgId)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, string) []*space.Space); ok {
+		r0 = rf(ctx, orgId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*space.Space)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, orgId)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Update provides a mock function with given fields: ctx, _a1
+func (_m *Spaces) Update(ctx context.Context, _a1 *space.Space) error {
+	ret := _m.Called(ctx, _a1)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *space.Space) error); ok {
+		r0 = rf(ctx, _a1)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// UpdateConfig provides a mock function with given fields: ctx, spaceId, config
+func (_m *Spaces) UpdateConfig(ctx context.Context, spaceId string, config *space.Config) error {
+	ret := _m.Called(ctx, spaceId, config)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, *space.Config) error); ok {
+		r0 = rf(ctx, spaceId, config)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+type mockConstructorTestingTNewSpaces interface {
+	mock.TestingT
+	Cleanup(func())
+}
+
+// NewSpaces creates a new instance of Spaces. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewSpaces(t mockConstructorTestingTNewSpaces) *Spaces {
+	mock := &Spaces{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/pkg/space/service.go b/pkg/space/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..576f5c2f8a7fae3a2b15d658eb1f42b5df0cab51
--- /dev/null
+++ b/pkg/space/service.go
@@ -0,0 +1,44 @@
+package space
+
+import (
+	"context"
+
+	"github.com/pkg/errors"
+)
+
+// @microgen grpc
+// @protobuf git.perx.ru/perxis/perxis-go/proto/spaces
+// @grpc-addr content.spaces.Spaces
+type Spaces interface {
+	// Create - создает пространство для размещения контента
+	// В процессе создание пространства выполняются следующие шаги:
+	// 1. Создается запись о пространстве
+	// 2. Создается БД пространства
+	// 3. Создается master окружение
+	// 4. Создаются необходимые схема согласно указанной конфигурации
+	// 5. Выполняется копирование данных согласно указанной конфигурации
+	// Шаги могут выполняться в отложенном режиме, при этом пространство может быть недоступно для
+	// выполнения любых операций. Попытка обращения к пространству будет сопровождаться ошибкой
+	// ErrUnavailable
+
+	Create(ctx context.Context, space *Space) (created *Space, err error)
+	Get(ctx context.Context, spaceId string) (space *Space, err error)
+	List(ctx context.Context, orgId string) (spaces []*Space, err error)
+	Update(ctx context.Context, space *Space) (err error)
+	UpdateConfig(ctx context.Context, spaceId string, config *Config) (err error)
+	Delete(ctx context.Context, spaceId string) (err error)
+}
+
+func IsSpaceAvailable(ctx context.Context, spcs Spaces, spaceId string) error {
+	spc, err := spcs.Get(ctx, spaceId)
+
+	if err != nil {
+		return errors.Wrap(err, "space not available")
+	}
+
+	if spc.State != StateReady {
+		return errors.New("space not available")
+	}
+
+	return nil
+}
diff --git a/pkg/space/space.go b/pkg/space/space.go
new file mode 100644
index 0000000000000000000000000000000000000000..dad51255a6fa9e8e648eec346a92f45c1749c876
--- /dev/null
+++ b/pkg/space/space.go
@@ -0,0 +1,54 @@
+package space
+
+type State int
+
+const (
+	StateUnknown     State = iota
+	StateNew               // Новое пространство, БД еще не инициализирована
+	StateReady             // Пространство готово к использованию
+	StatePreparing         // Пространство готовится в использованию
+	StateMaintenance       // Пространство находится на обслуживании
+	StateMigration         // Пространство в состоянии выполнения миграций
+	StateDeleting          // Пространство удаляется, данные будут удалены
+	StateError             // Произошла ошибка при конфигурации пространства (см. Space.Error)
+)
+
+type Config struct {
+	Features []string //Deprecated Возможности используемые пространством
+}
+
+const StateInfoEmpty = "EMPTY"
+
+type Space struct {
+	ID          string  `json:"id,omitempty" bson:"_id"`
+	OrgID       string  `json:"org_id,omitempty" bson:"org_id"`
+	Name        string  `json:"name,omitempty" bson:"name"`
+	Description string  `json:"desc,omitempty" bson:"desc"`
+	State       State   `json:"state" bson:"state"`
+	StateInfo   string  `json:"stateInfo,omitempty" bson:"stateInfo,omitempty"`
+	Config      *Config `json:"config" bson:"config"`
+}
+
+func (s Space) Clone() *Space {
+	return &s
+}
+
+func (s Space) Fetch(i interface{}) interface{} {
+	p, _ := i.(string)
+	switch p {
+	case "ID":
+		return s.ID
+	case "OrgID":
+		return s.OrgID
+	case "Name":
+		return s.Name
+	case "Description":
+		return s.Description
+	case "State":
+		return s.State
+	case "Config":
+		return s.Config
+	default:
+		panic("unknown parameter")
+	}
+}
diff --git a/pkg/space/transport/client.go b/pkg/space/transport/client.go
new file mode 100644
index 0000000000000000000000000000000000000000..44b72b6f5c8f0786f7ad7a255b9e46b69f672647
--- /dev/null
+++ b/pkg/space/transport/client.go
@@ -0,0 +1,66 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+
+	spaces "git.perx.ru/perxis/perxis-go/pkg/space"
+)
+
+func (set EndpointsSet) Create(arg0 context.Context, arg1 *spaces.Space) (res0 *spaces.Space, res1 error) {
+	request := CreateRequest{Space: arg1}
+	response, res1 := set.CreateEndpoint(arg0, &request)
+	if res1 != nil {
+		return
+	}
+	return response.(*CreateResponse).Created, res1
+}
+
+func (set EndpointsSet) Get(arg0 context.Context, arg1 string) (res0 *spaces.Space, res1 error) {
+	request := GetRequest{SpaceId: arg1}
+	response, res1 := set.GetEndpoint(arg0, &request)
+	if res1 != nil {
+		return
+	}
+	return response.(*GetResponse).Space, res1
+}
+
+func (set EndpointsSet) List(arg0 context.Context, arg1 string) (res0 []*spaces.Space, res1 error) {
+	request := ListRequest{OrgId: arg1}
+	response, res1 := set.ListEndpoint(arg0, &request)
+	if res1 != nil {
+		return
+	}
+	return response.(*ListResponse).Spaces, res1
+}
+
+func (set EndpointsSet) Update(arg0 context.Context, arg1 *spaces.Space) (res0 error) {
+	request := UpdateRequest{Space: arg1}
+	_, res0 = set.UpdateEndpoint(arg0, &request)
+	if res0 != nil {
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) UpdateConfig(arg0 context.Context, arg1 string, arg2 *spaces.Config) (res0 error) {
+	request := UpdateConfigRequest{
+		Config:  arg2,
+		SpaceId: arg1,
+	}
+	_, res0 = set.UpdateConfigEndpoint(arg0, &request)
+	if res0 != nil {
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) Delete(arg0 context.Context, arg1 string) (res0 error) {
+	request := DeleteRequest{SpaceId: arg1}
+	_, res0 = set.DeleteEndpoint(arg0, &request)
+	if res0 != nil {
+		return
+	}
+	return res0
+}
diff --git a/pkg/space/transport/endpoints.microgen.go b/pkg/space/transport/endpoints.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..fae8a92d5cdb23e198955db8f0c56c620db51ee3
--- /dev/null
+++ b/pkg/space/transport/endpoints.microgen.go
@@ -0,0 +1,15 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import endpoint "github.com/go-kit/kit/endpoint"
+
+// EndpointsSet implements Spaces API and used for transport purposes.
+type EndpointsSet struct {
+	CreateEndpoint       endpoint.Endpoint
+	GetEndpoint          endpoint.Endpoint
+	ListEndpoint         endpoint.Endpoint
+	UpdateEndpoint       endpoint.Endpoint
+	UpdateConfigEndpoint endpoint.Endpoint
+	DeleteEndpoint       endpoint.Endpoint
+}
diff --git a/pkg/space/transport/exchanges.microgen.go b/pkg/space/transport/exchanges.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..05ad11827f34070ebd644b44a217bcceedfecbc7
--- /dev/null
+++ b/pkg/space/transport/exchanges.microgen.go
@@ -0,0 +1,47 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import spaces "git.perx.ru/perxis/perxis-go/pkg/space"
+
+type (
+	CreateRequest struct {
+		Space *spaces.Space `json:"space"`
+	}
+	CreateResponse struct {
+		Created *spaces.Space `json:"created"`
+	}
+
+	GetRequest struct {
+		SpaceId string `json:"space_id"`
+	}
+	GetResponse struct {
+		Space *spaces.Space `json:"space"`
+	}
+
+	ListRequest struct {
+		OrgId string `json:"org_id"`
+	}
+	ListResponse struct {
+		Spaces []*spaces.Space `json:"spaces"`
+	}
+
+	UpdateRequest struct {
+		Space *spaces.Space `json:"space"`
+	}
+	// Formal exchange type, please do not delete.
+	UpdateResponse struct{}
+
+	UpdateConfigRequest struct {
+		SpaceId string         `json:"space_id"`
+		Config  *spaces.Config `json:"config"`
+	}
+	// Formal exchange type, please do not delete.
+	UpdateConfigResponse struct{}
+
+	DeleteRequest struct {
+		SpaceId string `json:"space_id"`
+	}
+	// Formal exchange type, please do not delete.
+	DeleteResponse struct{}
+)
diff --git a/pkg/space/transport/grpc/client.go b/pkg/space/transport/grpc/client.go
new file mode 100644
index 0000000000000000000000000000000000000000..bfa48ae9df7c4bff443f5bbaa3d88c37f1fa26bc
--- /dev/null
+++ b/pkg/space/transport/grpc/client.go
@@ -0,0 +1,21 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transportgrpc
+
+import (
+	grpcerr "git.perx.ru/perxis/perxis-go/pkg/errors/grpc"
+	transport "git.perx.ru/perxis/perxis-go/pkg/space/transport"
+	grpckit "github.com/go-kit/kit/transport/grpc"
+	grpc "google.golang.org/grpc"
+)
+
+func NewClient(conn *grpc.ClientConn, opts ...grpckit.ClientOption) transport.EndpointsSet {
+	c := NewGRPCClient(conn, "", opts...)
+	return transport.EndpointsSet{
+		CreateEndpoint:       grpcerr.ClientMiddleware(c.CreateEndpoint),
+		GetEndpoint:          grpcerr.ClientMiddleware(c.GetEndpoint),
+		ListEndpoint:         grpcerr.ClientMiddleware(c.ListEndpoint),
+		UpdateEndpoint:       grpcerr.ClientMiddleware(c.UpdateEndpoint),
+		UpdateConfigEndpoint: grpcerr.ClientMiddleware(c.UpdateConfigEndpoint),
+		DeleteEndpoint:       grpcerr.ClientMiddleware(c.DeleteEndpoint)}
+}
diff --git a/pkg/space/transport/grpc/client.microgen.go b/pkg/space/transport/grpc/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..b0d7d35e8e14c5ffa1d852d16e899160af5bb55e
--- /dev/null
+++ b/pkg/space/transport/grpc/client.microgen.go
@@ -0,0 +1,61 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/space/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/spaces"
+	grpckit "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	grpc "google.golang.org/grpc"
+)
+
+func NewGRPCClient(conn *grpc.ClientConn, addr string, opts ...grpckit.ClientOption) transport.EndpointsSet {
+	if addr == "" {
+		addr = "content.spaces.Spaces"
+	}
+	return transport.EndpointsSet{
+		CreateEndpoint: grpckit.NewClient(
+			conn, addr, "Create",
+			_Encode_Create_Request,
+			_Decode_Create_Response,
+			pb.CreateResponse{},
+			opts...,
+		).Endpoint(),
+		DeleteEndpoint: grpckit.NewClient(
+			conn, addr, "Delete",
+			_Encode_Delete_Request,
+			_Decode_Delete_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+		GetEndpoint: grpckit.NewClient(
+			conn, addr, "Get",
+			_Encode_Get_Request,
+			_Decode_Get_Response,
+			pb.GetResponse{},
+			opts...,
+		).Endpoint(),
+		ListEndpoint: grpckit.NewClient(
+			conn, addr, "List",
+			_Encode_List_Request,
+			_Decode_List_Response,
+			pb.ListResponse{},
+			opts...,
+		).Endpoint(),
+		UpdateConfigEndpoint: grpckit.NewClient(
+			conn, addr, "UpdateConfig",
+			_Encode_UpdateConfig_Request,
+			_Decode_UpdateConfig_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+		UpdateEndpoint: grpckit.NewClient(
+			conn, addr, "Update",
+			_Encode_Update_Request,
+			_Decode_Update_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+	}
+}
diff --git a/pkg/space/transport/grpc/protobuf_endpoint_converters.microgen.go b/pkg/space/transport/grpc/protobuf_endpoint_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..606d41cfa3335674b487cd861fd5cfb86152b90b
--- /dev/null
+++ b/pkg/space/transport/grpc/protobuf_endpoint_converters.microgen.go
@@ -0,0 +1,235 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// Please, do not change functions names!
+package transportgrpc
+
+import (
+	"context"
+	"errors"
+
+	transport "git.perx.ru/perxis/perxis-go/pkg/space/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/spaces"
+	empty "github.com/golang/protobuf/ptypes/empty"
+)
+
+func _Encode_Create_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil CreateRequest")
+	}
+	req := request.(*transport.CreateRequest)
+	reqSpace, err := PtrSpaceToProto(req.Space)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateRequest{Space: reqSpace}, nil
+}
+
+func _Encode_Get_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetRequest")
+	}
+	req := request.(*transport.GetRequest)
+	return &pb.GetRequest{SpaceId: req.SpaceId}, nil
+}
+
+func _Encode_List_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil ListRequest")
+	}
+	req := request.(*transport.ListRequest)
+	return &pb.ListRequest{OrgId: req.OrgId}, nil
+}
+
+func _Encode_Update_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil UpdateRequest")
+	}
+	req := request.(*transport.UpdateRequest)
+	reqSpace, err := PtrSpaceToProto(req.Space)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.UpdateRequest{Space: reqSpace}, nil
+}
+
+func _Encode_UpdateConfig_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil UpdateConfigRequest")
+	}
+	req := request.(*transport.UpdateConfigRequest)
+	reqConfig, err := PtrConfigToProto(req.Config)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.UpdateConfigRequest{
+		Config:  reqConfig,
+		SpaceId: req.SpaceId,
+	}, nil
+}
+
+func _Encode_Delete_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil DeleteRequest")
+	}
+	req := request.(*transport.DeleteRequest)
+	return &pb.DeleteRequest{SpaceId: req.SpaceId}, nil
+}
+
+func _Encode_Create_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil CreateResponse")
+	}
+	resp := response.(*transport.CreateResponse)
+	respCreated, err := PtrSpaceToProto(resp.Created)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateResponse{Created: respCreated}, nil
+}
+
+func _Encode_Get_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetResponse")
+	}
+	resp := response.(*transport.GetResponse)
+	respSpace, err := PtrSpaceToProto(resp.Space)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.GetResponse{Space: respSpace}, nil
+}
+
+func _Encode_List_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil ListResponse")
+	}
+	resp := response.(*transport.ListResponse)
+	respSpaces, err := ListPtrSpaceToProto(resp.Spaces)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.ListResponse{Spaces: respSpaces}, nil
+}
+
+func _Encode_Update_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_UpdateConfig_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_Delete_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_Create_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil CreateRequest")
+	}
+	req := request.(*pb.CreateRequest)
+	reqSpace, err := ProtoToPtrSpace(req.Space)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateRequest{Space: reqSpace}, nil
+}
+
+func _Decode_Get_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetRequest")
+	}
+	req := request.(*pb.GetRequest)
+	return &transport.GetRequest{SpaceId: string(req.SpaceId)}, nil
+}
+
+func _Decode_List_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil ListRequest")
+	}
+	req := request.(*pb.ListRequest)
+	return &transport.ListRequest{OrgId: string(req.OrgId)}, nil
+}
+
+func _Decode_Update_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil UpdateRequest")
+	}
+	req := request.(*pb.UpdateRequest)
+	reqSpace, err := ProtoToPtrSpace(req.Space)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.UpdateRequest{Space: reqSpace}, nil
+}
+
+func _Decode_UpdateConfig_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil UpdateConfigRequest")
+	}
+	req := request.(*pb.UpdateConfigRequest)
+	reqConfig, err := ProtoToPtrConfig(req.Config)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.UpdateConfigRequest{
+		Config:  reqConfig,
+		SpaceId: string(req.SpaceId),
+	}, nil
+}
+
+func _Decode_Delete_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil DeleteRequest")
+	}
+	req := request.(*pb.DeleteRequest)
+	return &transport.DeleteRequest{SpaceId: string(req.SpaceId)}, nil
+}
+
+func _Decode_Create_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil CreateResponse")
+	}
+	resp := response.(*pb.CreateResponse)
+	respCreated, err := ProtoToPtrSpace(resp.Created)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateResponse{Created: respCreated}, nil
+}
+
+func _Decode_Get_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetResponse")
+	}
+	resp := response.(*pb.GetResponse)
+	respSpace, err := ProtoToPtrSpace(resp.Space)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.GetResponse{Space: respSpace}, nil
+}
+
+func _Decode_List_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil ListResponse")
+	}
+	resp := response.(*pb.ListResponse)
+	respSpaces, err := ProtoToListPtrSpace(resp.Spaces)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.ListResponse{Spaces: respSpaces}, nil
+}
+
+func _Decode_Update_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_UpdateConfig_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_Delete_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
diff --git a/pkg/space/transport/grpc/protobuf_type_converters.microgen.go b/pkg/space/transport/grpc/protobuf_type_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..56e2dc38265de3a9fa239b3d4d8cb37873558796
--- /dev/null
+++ b/pkg/space/transport/grpc/protobuf_type_converters.microgen.go
@@ -0,0 +1,76 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// It is better for you if you do not change functions names!
+// This file will never be overwritten.
+package transportgrpc
+
+import (
+	service "git.perx.ru/perxis/perxis-go/pkg/space"
+	pb "git.perx.ru/perxis/perxis-go/proto/spaces"
+)
+
+func PtrConfigToProto(config *service.Config) (*pb.Config, error) {
+	if config == nil {
+		return nil, nil
+	}
+	return &pb.Config{
+		Features: config.Features,
+	}, nil
+}
+
+func ProtoToPtrConfig(protoConfig *pb.Config) (*service.Config, error) {
+	if protoConfig == nil {
+		return nil, nil
+	}
+	return &service.Config{
+		Features: protoConfig.Features,
+	}, nil
+}
+
+func PtrSpaceToProto(space *service.Space) (*pb.Space, error) {
+	if space == nil {
+		return nil, nil
+	}
+	cfg, _ := PtrConfigToProto(space.Config)
+	return &pb.Space{
+		Id:          space.ID,
+		OrgId:       space.OrgID,
+		Name:        space.Name,
+		Description: space.Description,
+		State:       pb.State(space.State),
+		Config:      cfg,
+	}, nil
+}
+
+func ProtoToPtrSpace(protoSpace *pb.Space) (*service.Space, error) {
+	if protoSpace == nil {
+		return nil, nil
+	}
+	cfg, _ := ProtoToPtrConfig(protoSpace.Config)
+	return &service.Space{
+		ID:          protoSpace.Id,
+		OrgID:       protoSpace.OrgId,
+		Name:        protoSpace.Name,
+		Description: protoSpace.Description,
+		State:       service.State(protoSpace.State),
+		Config:      cfg,
+	}, nil
+}
+
+func ListPtrSpaceToProto(spaces []*service.Space) ([]*pb.Space, error) {
+	protoSpaces := make([]*pb.Space, 0, len(spaces))
+	for _, sp := range spaces {
+		protoSpace, _ := PtrSpaceToProto(sp)
+		protoSpaces = append(protoSpaces, protoSpace)
+	}
+	return protoSpaces, nil
+}
+
+func ProtoToListPtrSpace(protoSpaces []*pb.Space) ([]*service.Space, error) {
+	spaces := make([]*service.Space, 0, len(protoSpaces))
+	for _, sp := range protoSpaces {
+		space, _ := ProtoToPtrSpace(sp)
+		spaces = append(spaces, space)
+	}
+	return spaces, nil
+}
diff --git a/pkg/space/transport/grpc/server.go b/pkg/space/transport/grpc/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..f8220070b19fe5cc9a51ed7bdd8aa6fe5579d428
--- /dev/null
+++ b/pkg/space/transport/grpc/server.go
@@ -0,0 +1,22 @@
+package transportgrpc
+
+import (
+	grpcerr "git.perx.ru/perxis/perxis-go/pkg/errors/grpc"
+	"git.perx.ru/perxis/perxis-go/pkg/space"
+	"git.perx.ru/perxis/perxis-go/pkg/space/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/spaces"
+	grpckit "github.com/go-kit/kit/transport/grpc"
+)
+
+func NewServer(svc space.Spaces, opts ...grpckit.ServerOption) pb.SpacesServer {
+	eps := transport.Endpoints(svc)
+	eps = transport.EndpointsSet{
+		CreateEndpoint:       grpcerr.ServerMiddleware(eps.CreateEndpoint),
+		GetEndpoint:          grpcerr.ServerMiddleware(eps.GetEndpoint),
+		ListEndpoint:         grpcerr.ServerMiddleware(eps.ListEndpoint),
+		UpdateEndpoint:       grpcerr.ServerMiddleware(eps.UpdateEndpoint),
+		UpdateConfigEndpoint: grpcerr.ServerMiddleware(eps.UpdateConfigEndpoint),
+		DeleteEndpoint:       grpcerr.ServerMiddleware(eps.DeleteEndpoint),
+	}
+	return NewGRPCServer(&eps, opts...)
+}
diff --git a/pkg/space/transport/grpc/server.microgen.go b/pkg/space/transport/grpc/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..0d00433457f573073eeeabe5e41c4a3803d69c20
--- /dev/null
+++ b/pkg/space/transport/grpc/server.microgen.go
@@ -0,0 +1,112 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// DO NOT EDIT.
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/space/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/spaces"
+	grpc "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	context "golang.org/x/net/context"
+)
+
+type spacesServer struct {
+	create       grpc.Handler
+	get          grpc.Handler
+	list         grpc.Handler
+	update       grpc.Handler
+	updateConfig grpc.Handler
+	delete       grpc.Handler
+
+	pb.UnimplementedSpacesServer
+}
+
+func NewGRPCServer(endpoints *transport.EndpointsSet, opts ...grpc.ServerOption) pb.SpacesServer {
+	return &spacesServer{
+		create: grpc.NewServer(
+			endpoints.CreateEndpoint,
+			_Decode_Create_Request,
+			_Encode_Create_Response,
+			opts...,
+		),
+		delete: grpc.NewServer(
+			endpoints.DeleteEndpoint,
+			_Decode_Delete_Request,
+			_Encode_Delete_Response,
+			opts...,
+		),
+		get: grpc.NewServer(
+			endpoints.GetEndpoint,
+			_Decode_Get_Request,
+			_Encode_Get_Response,
+			opts...,
+		),
+		list: grpc.NewServer(
+			endpoints.ListEndpoint,
+			_Decode_List_Request,
+			_Encode_List_Response,
+			opts...,
+		),
+		update: grpc.NewServer(
+			endpoints.UpdateEndpoint,
+			_Decode_Update_Request,
+			_Encode_Update_Response,
+			opts...,
+		),
+		updateConfig: grpc.NewServer(
+			endpoints.UpdateConfigEndpoint,
+			_Decode_UpdateConfig_Request,
+			_Encode_UpdateConfig_Response,
+			opts...,
+		),
+	}
+}
+
+func (S *spacesServer) Create(ctx context.Context, req *pb.CreateRequest) (*pb.CreateResponse, error) {
+	_, resp, err := S.create.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.CreateResponse), nil
+}
+
+func (S *spacesServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.GetResponse, error) {
+	_, resp, err := S.get.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.GetResponse), nil
+}
+
+func (S *spacesServer) List(ctx context.Context, req *pb.ListRequest) (*pb.ListResponse, error) {
+	_, resp, err := S.list.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.ListResponse), nil
+}
+
+func (S *spacesServer) Update(ctx context.Context, req *pb.UpdateRequest) (*empty.Empty, error) {
+	_, resp, err := S.update.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
+
+func (S *spacesServer) UpdateConfig(ctx context.Context, req *pb.UpdateConfigRequest) (*empty.Empty, error) {
+	_, resp, err := S.updateConfig.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
+
+func (S *spacesServer) Delete(ctx context.Context, req *pb.DeleteRequest) (*empty.Empty, error) {
+	_, resp, err := S.delete.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
diff --git a/pkg/space/transport/server.microgen.go b/pkg/space/transport/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..b4546383c62dae4fdc994a79f17ba3ee4345ceb1
--- /dev/null
+++ b/pkg/space/transport/server.microgen.go
@@ -0,0 +1,69 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+
+	spaces "git.perx.ru/perxis/perxis-go/pkg/space"
+	endpoint "github.com/go-kit/kit/endpoint"
+)
+
+func Endpoints(svc spaces.Spaces) EndpointsSet {
+	return EndpointsSet{
+		CreateEndpoint:       CreateEndpoint(svc),
+		DeleteEndpoint:       DeleteEndpoint(svc),
+		GetEndpoint:          GetEndpoint(svc),
+		ListEndpoint:         ListEndpoint(svc),
+		UpdateConfigEndpoint: UpdateConfigEndpoint(svc),
+		UpdateEndpoint:       UpdateEndpoint(svc),
+	}
+}
+
+func CreateEndpoint(svc spaces.Spaces) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*CreateRequest)
+		res0, res1 := svc.Create(arg0, req.Space)
+		return &CreateResponse{Created: res0}, res1
+	}
+}
+
+func GetEndpoint(svc spaces.Spaces) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*GetRequest)
+		res0, res1 := svc.Get(arg0, req.SpaceId)
+		return &GetResponse{Space: res0}, res1
+	}
+}
+
+func ListEndpoint(svc spaces.Spaces) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*ListRequest)
+		res0, res1 := svc.List(arg0, req.OrgId)
+		return &ListResponse{Spaces: res0}, res1
+	}
+}
+
+func UpdateEndpoint(svc spaces.Spaces) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*UpdateRequest)
+		res0 := svc.Update(arg0, req.Space)
+		return &UpdateResponse{}, res0
+	}
+}
+
+func UpdateConfigEndpoint(svc spaces.Spaces) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*UpdateConfigRequest)
+		res0 := svc.UpdateConfig(arg0, req.SpaceId, req.Config)
+		return &UpdateConfigResponse{}, res0
+	}
+}
+
+func DeleteEndpoint(svc spaces.Spaces) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*DeleteRequest)
+		res0 := svc.Delete(arg0, req.SpaceId)
+		return &DeleteResponse{}, res0
+	}
+}