diff --git a/pkg/environments/environment.go b/pkg/environments/environment.go
new file mode 100644
index 0000000000000000000000000000000000000000..cbd468dc361951e048a0840f4158a42c962e7af1
--- /dev/null
+++ b/pkg/environments/environment.go
@@ -0,0 +1,114 @@
+package environments
+
+import "time"
+
+const (
+	DefaultEnvironment = "master"
+)
+
+type State int
+
+const (
+	StateUnknown State = iota
+	StateNew
+	StatePreparing
+	StateReady
+	StateError
+
+	StateInfoEmpty = "EMPTY"
+)
+
+func (s State) String() string {
+	switch s {
+	case StateNew:
+		return "new"
+	case StatePreparing:
+		return "preparing"
+	case StateReady:
+		return "ready"
+	case StateError:
+		return "error"
+	default:
+		return "unknown"
+	}
+}
+
+type StateInfo struct {
+	State     State     `json:"state" bson:"state"`
+	StartedAt time.Time `json:"started_at,omitempty" bson:"started_at,omitempty"`
+	Info      string    `json:"info,omitempty" bson:"info,omitempty"`
+}
+
+type Config struct {
+	SourceID string
+
+	// Deprecated
+	Features []string
+}
+
+// Environment - представляет рабочее окружения для пространства
+// Каждое окружение может иметь собственный набор коллекций и данных и
+// использоваться независимо друг от друга
+type Environment struct {
+	ID          string `json:"id" bson:"_id"` // Идентификатор окружения, задается пользователем при создании. Уникален в рамках пространства `SpaceID`
+	SpaceID     string `json:"spaceID" bson:"-"`
+	Description string `json:"description" bson:"desc,omitempty"` // Описание для окружения
+	//State       State  `json:"state" bson:"state"`                // Состояние окружения (Preparing/Ready/Failed)
+	//StateInfo   string   `json:"state_info,omitempty" bson:"state_info,omitempty"`
+
+	// StateInfo отображает состояние коллекции:
+	// - State: идентификатор состояния окружения (unknown/new/preparing/ready/error)
+	// - Info: дополнительная информация о состоянии коллекции (например, если при
+	//   применении схемы к коллекции произошла ошибка)
+	// - StartedAt: время, в которое коллекция перешла в состояние `Preparing`
+	StateInfo *StateInfo `json:"state_info" bson:"state_info,omitempty"`
+
+	Aliases []string `json:"aliases" bson:"aliases,omitempty"` // Синонимы окружения (только чтение)
+	Config  *Config  `json:"config,omitempty" bson:"config,omitempty"`
+}
+
+func (e Environment) Clone() *Environment {
+	clone := &Environment{
+		ID:          e.ID,
+		SpaceID:     e.SpaceID,
+		Description: e.Description,
+		Aliases:     append([]string(nil), e.Aliases...),
+		Config:      nil,
+	}
+
+	if e.StateInfo != nil {
+		clone.StateInfo = &StateInfo{
+			State:     e.StateInfo.State,
+			Info:      e.StateInfo.Info,
+			StartedAt: e.StateInfo.StartedAt,
+		}
+	}
+
+	if e.Config != nil {
+		clone.Config = &Config{
+			SourceID: e.Config.SourceID,
+		}
+	}
+
+	return clone
+}
+
+func (e Environment) Fetch(i interface{}) interface{} {
+	p, _ := i.(string)
+	switch p {
+	case "ID":
+		return e.ID
+	case "SpaceID":
+		return e.SpaceID
+	case "Description":
+		return e.Description
+	case "StateInfo":
+		return e.StateInfo
+	case "Aliases":
+		return e.Aliases
+	case "Config":
+		return e.Config
+	default:
+		panic("unknown parameter")
+	}
+}
diff --git a/pkg/environments/mocks/Environments.go b/pkg/environments/mocks/Environments.go
new file mode 100644
index 0000000000000000000000000000000000000000..8c8d099f32cbcbd7cd1edf377b3d46e6756a1d72
--- /dev/null
+++ b/pkg/environments/mocks/Environments.go
@@ -0,0 +1,176 @@
+// Code generated by mockery v2.14.0. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	environments "git.perx.ru/perxis/perxis-go/pkg/environments"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Environments is an autogenerated mock type for the Environments type
+type Environments struct {
+	mock.Mock
+}
+
+// Create provides a mock function with given fields: ctx, env
+func (_m *Environments) Create(ctx context.Context, env *environments.Environment) (*environments.Environment, error) {
+	ret := _m.Called(ctx, env)
+
+	var r0 *environments.Environment
+	if rf, ok := ret.Get(0).(func(context.Context, *environments.Environment) *environments.Environment); ok {
+		r0 = rf(ctx, env)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*environments.Environment)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *environments.Environment) error); ok {
+		r1 = rf(ctx, env)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Delete provides a mock function with given fields: ctx, spaceId, envId
+func (_m *Environments) Delete(ctx context.Context, spaceId string, envId string) error {
+	ret := _m.Called(ctx, spaceId, envId)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
+		r0 = rf(ctx, spaceId, envId)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Get provides a mock function with given fields: ctx, spaceId, envId
+func (_m *Environments) Get(ctx context.Context, spaceId string, envId string) (*environments.Environment, error) {
+	ret := _m.Called(ctx, spaceId, envId)
+
+	var r0 *environments.Environment
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) *environments.Environment); ok {
+		r0 = rf(ctx, spaceId, envId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*environments.Environment)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
+		r1 = rf(ctx, spaceId, envId)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// List provides a mock function with given fields: ctx, spaceId
+func (_m *Environments) List(ctx context.Context, spaceId string) ([]*environments.Environment, error) {
+	ret := _m.Called(ctx, spaceId)
+
+	var r0 []*environments.Environment
+	if rf, ok := ret.Get(0).(func(context.Context, string) []*environments.Environment); ok {
+		r0 = rf(ctx, spaceId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*environments.Environment)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, spaceId)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Migrate provides a mock function with given fields: ctx, spaceId, envId, options
+func (_m *Environments) Migrate(ctx context.Context, spaceId string, envId string, options ...*environments.MigrateOptions) error {
+	_va := make([]interface{}, len(options))
+	for _i := range options {
+		_va[_i] = options[_i]
+	}
+	var _ca []interface{}
+	_ca = append(_ca, ctx, spaceId, envId)
+	_ca = append(_ca, _va...)
+	ret := _m.Called(_ca...)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string, ...*environments.MigrateOptions) error); ok {
+		r0 = rf(ctx, spaceId, envId, options...)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// RemoveAlias provides a mock function with given fields: ctx, spaceId, envId, alias
+func (_m *Environments) RemoveAlias(ctx context.Context, spaceId string, envId string, alias string) error {
+	ret := _m.Called(ctx, spaceId, envId, alias)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok {
+		r0 = rf(ctx, spaceId, envId, alias)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// SetAlias provides a mock function with given fields: ctx, spaceId, envId, alias
+func (_m *Environments) SetAlias(ctx context.Context, spaceId string, envId string, alias string) error {
+	ret := _m.Called(ctx, spaceId, envId, alias)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string, string) error); ok {
+		r0 = rf(ctx, spaceId, envId, alias)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Update provides a mock function with given fields: ctx, env
+func (_m *Environments) Update(ctx context.Context, env *environments.Environment) error {
+	ret := _m.Called(ctx, env)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *environments.Environment) error); ok {
+		r0 = rf(ctx, env)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+type mockConstructorTestingTNewEnvironments interface {
+	mock.TestingT
+	Cleanup(func())
+}
+
+// NewEnvironments creates a new instance of Environments. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewEnvironments(t mockConstructorTestingTNewEnvironments) *Environments {
+	mock := &Environments{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}
diff --git a/pkg/environments/options.go b/pkg/environments/options.go
new file mode 100644
index 0000000000000000000000000000000000000000..02f1a1fea3229ccfe702401c2dfa06e5094d9671
--- /dev/null
+++ b/pkg/environments/options.go
@@ -0,0 +1,37 @@
+package environments
+
+type MigrateOptions struct {
+
+	// Ожидать завершения миграции в синхронном режиме
+	Wait bool
+}
+
+func MergeMigrateOptions(opts ...*MigrateOptions) *MigrateOptions {
+	o := &MigrateOptions{}
+	for _, opt := range opts {
+		if opt.Wait {
+			o.Wait = true
+		}
+	}
+	return o
+}
+
+type UpdateOptions struct {
+
+	// Состояние будет обновлено только в том случае, если выполняется указанное условие
+	// Cond указывается с использованием синтаксиса `expr`
+	Cond string
+}
+
+func MergeUpdateOptions(opts ...*UpdateOptions) *UpdateOptions {
+	o := &UpdateOptions{}
+	for _, opt := range opts {
+		if opt.Cond != "" {
+			if o.Cond != "" {
+				o.Cond += " && "
+			}
+			o.Cond += opt.Cond
+		}
+	}
+	return o
+}
diff --git a/pkg/environments/service.go b/pkg/environments/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..cd0a37f449819a4b92239f8d8b38a3a0036ec8d8
--- /dev/null
+++ b/pkg/environments/service.go
@@ -0,0 +1,20 @@
+package environments
+
+import (
+	"context"
+)
+
+// Environments
+// @microgen grpc
+// @protobuf git.perx.ru/perxis/perxis-go/proto/environments
+// @grpc-addr content.environments.Environments
+type Environments interface {
+	Create(ctx context.Context, env *Environment) (created *Environment, err error)
+	Get(ctx context.Context, spaceId, envId string) (env *Environment, err error)
+	List(ctx context.Context, spaceId string) (envs []*Environment, err error)
+	Update(ctx context.Context, env *Environment) (err error)
+	Delete(ctx context.Context, spaceId, envId string) (err error)
+	SetAlias(ctx context.Context, spaceId, envId, alias string) (err error)
+	RemoveAlias(ctx context.Context, spaceId, envId, alias string) (err error)
+	Migrate(ctx context.Context, spaceId, envId string, options ...*MigrateOptions) (err error)
+}
diff --git a/pkg/environments/transport/client.microgen.go b/pkg/environments/transport/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..96094fda943e407d0264a8abc738fd5c3afe440e
--- /dev/null
+++ b/pkg/environments/transport/client.microgen.go
@@ -0,0 +1,126 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+	"errors"
+
+	environments "git.perx.ru/perxis/perxis-go/pkg/environments"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+func (set EndpointsSet) Create(arg0 context.Context, arg1 *environments.Environment) (res0 *environments.Environment, res1 error) {
+	request := CreateRequest{Env: arg1}
+	response, res1 := set.CreateEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*CreateResponse).Created, res1
+}
+
+func (set EndpointsSet) Get(arg0 context.Context, arg1 string, arg2 string) (res0 *environments.Environment, res1 error) {
+	request := GetRequest{
+		EnvId:   arg2,
+		SpaceId: arg1,
+	}
+	response, res1 := set.GetEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*GetResponse).Env, res1
+}
+
+func (set EndpointsSet) List(arg0 context.Context, arg1 string) (res0 []*environments.Environment, res1 error) {
+	request := ListRequest{SpaceId: arg1}
+	response, res1 := set.ListEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*ListResponse).Envs, res1
+}
+
+func (set EndpointsSet) Update(arg0 context.Context, arg1 *environments.Environment) (res0 error) {
+	request := UpdateRequest{Env: arg1}
+	_, res0 = set.UpdateEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) Delete(arg0 context.Context, arg1 string, arg2 string) (res0 error) {
+	request := DeleteRequest{
+		EnvId:   arg2,
+		SpaceId: arg1,
+	}
+	_, res0 = set.DeleteEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) SetAlias(arg0 context.Context, arg1 string, arg2 string, arg3 string) (res0 error) {
+	request := SetAliasRequest{
+		Alias:   arg3,
+		EnvId:   arg2,
+		SpaceId: arg1,
+	}
+	_, res0 = set.SetAliasEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) RemoveAlias(arg0 context.Context, arg1 string, arg2 string, arg3 string) (res0 error) {
+	request := RemoveAliasRequest{
+		Alias:   arg3,
+		EnvId:   arg2,
+		SpaceId: arg1,
+	}
+	_, res0 = set.RemoveAliasEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) Migrate(arg0 context.Context, arg1 string, arg2 string, arg3 ...*environments.MigrateOptions) (res0 error) {
+	request := MigrateRequest{
+		EnvId:   arg2,
+		Options: arg3,
+		SpaceId: arg1,
+	}
+	_, res0 = set.MigrateEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
diff --git a/pkg/environments/transport/endpoints.microgen.go b/pkg/environments/transport/endpoints.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..5639637d2b479721e21d5c3fe4ea097d2f2a6cde
--- /dev/null
+++ b/pkg/environments/transport/endpoints.microgen.go
@@ -0,0 +1,17 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import endpoint "github.com/go-kit/kit/endpoint"
+
+// EndpointsSet implements Environments API and used for transport purposes.
+type EndpointsSet struct {
+	CreateEndpoint      endpoint.Endpoint
+	GetEndpoint         endpoint.Endpoint
+	ListEndpoint        endpoint.Endpoint
+	UpdateEndpoint      endpoint.Endpoint
+	DeleteEndpoint      endpoint.Endpoint
+	SetAliasEndpoint    endpoint.Endpoint
+	RemoveAliasEndpoint endpoint.Endpoint
+	MigrateEndpoint     endpoint.Endpoint
+}
diff --git a/pkg/environments/transport/exchanges.microgen.go b/pkg/environments/transport/exchanges.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..a1a0ab068d575767c543d8db652c5d8c9a7e02f4
--- /dev/null
+++ b/pkg/environments/transport/exchanges.microgen.go
@@ -0,0 +1,66 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import environments "git.perx.ru/perxis/perxis-go/pkg/environments"
+
+type (
+	CreateRequest struct {
+		Env *environments.Environment `json:"env"`
+	}
+	CreateResponse struct {
+		Created *environments.Environment `json:"created"`
+	}
+
+	GetRequest struct {
+		SpaceId string `json:"space_id"`
+		EnvId   string `json:"env_id"`
+	}
+	GetResponse struct {
+		Env *environments.Environment `json:"env"`
+	}
+
+	ListRequest struct {
+		SpaceId string `json:"space_id"`
+	}
+	ListResponse struct {
+		Envs []*environments.Environment `json:"envs"`
+	}
+
+	UpdateRequest struct {
+		Env *environments.Environment `json:"env"`
+	}
+	// Formal exchange type, please do not delete.
+	UpdateResponse struct{}
+
+	DeleteRequest struct {
+		SpaceId string `json:"space_id"`
+		EnvId   string `json:"env_id"`
+	}
+	// Formal exchange type, please do not delete.
+	DeleteResponse struct{}
+
+	SetAliasRequest struct {
+		SpaceId string `json:"space_id"`
+		EnvId   string `json:"env_id"`
+		Alias   string `json:"alias"`
+	}
+	// Formal exchange type, please do not delete.
+	SetAliasResponse struct{}
+
+	RemoveAliasRequest struct {
+		SpaceId string `json:"space_id"`
+		EnvId   string `json:"env_id"`
+		Alias   string `json:"alias"`
+	}
+	// Formal exchange type, please do not delete.
+	RemoveAliasResponse struct{}
+
+	MigrateRequest struct {
+		SpaceId string                         `json:"space_id"`
+		EnvId   string                         `json:"env_id"`
+		Options []*environments.MigrateOptions `json:"options"` // This field was defined with ellipsis (...).
+	}
+	// Formal exchange type, please do not delete.
+	MigrateResponse struct{}
+)
diff --git a/pkg/environments/transport/grpc/client.microgen.go b/pkg/environments/transport/grpc/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..34e0fa4db1cf8149475dde562b3179091089f70f
--- /dev/null
+++ b/pkg/environments/transport/grpc/client.microgen.go
@@ -0,0 +1,75 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/environments/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/environments"
+	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.environments.Environments"
+	}
+	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(),
+		MigrateEndpoint: grpckit.NewClient(
+			conn, addr, "Migrate",
+			_Encode_Migrate_Request,
+			_Decode_Migrate_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+		RemoveAliasEndpoint: grpckit.NewClient(
+			conn, addr, "RemoveAlias",
+			_Encode_RemoveAlias_Request,
+			_Decode_RemoveAlias_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+		SetAliasEndpoint: grpckit.NewClient(
+			conn, addr, "SetAlias",
+			_Encode_SetAlias_Request,
+			_Decode_SetAlias_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/environments/transport/grpc/protobuf_endpoint_converters.microgen.go b/pkg/environments/transport/grpc/protobuf_endpoint_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..6411216cd4f54cedd9bf2a3c61ff77b98d70cc35
--- /dev/null
+++ b/pkg/environments/transport/grpc/protobuf_endpoint_converters.microgen.go
@@ -0,0 +1,307 @@
+// 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/environments/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/environments"
+	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)
+	reqEnv, err := PtrEnvironmentToProto(req.Env)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateRequest{Env: reqEnv}, 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{
+		EnvId:   req.EnvId,
+		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{SpaceId: req.SpaceId}, 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)
+	reqEnv, err := PtrEnvironmentToProto(req.Env)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.UpdateRequest{Env: reqEnv}, 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{
+		EnvId:   req.EnvId,
+		SpaceId: req.SpaceId,
+	}, nil
+}
+
+func _Encode_SetAlias_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil SetAliasRequest")
+	}
+	req := request.(*transport.SetAliasRequest)
+	return &pb.SetAliasRequest{
+		Alias:   req.Alias,
+		EnvId:   req.EnvId,
+		SpaceId: req.SpaceId,
+	}, nil
+}
+
+func _Encode_RemoveAlias_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil RemoveAliasRequest")
+	}
+	req := request.(*transport.RemoveAliasRequest)
+	return &pb.RemoveAliasRequest{
+		Alias:   req.Alias,
+		EnvId:   req.EnvId,
+		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 := PtrEnvironmentToProto(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)
+	respEnv, err := PtrEnvironmentToProto(resp.Env)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.GetResponse{Env: respEnv}, 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)
+	respEnvs, err := ListPtrEnvironmentToProto(resp.Envs)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.ListResponse{Envs: respEnvs}, nil
+}
+
+func _Encode_Update_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 _Encode_SetAlias_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_RemoveAlias_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)
+	reqEnv, err := ProtoToPtrEnvironment(req.Env)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateRequest{Env: reqEnv}, 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{
+		EnvId:   string(req.EnvId),
+		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{SpaceId: string(req.SpaceId)}, 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)
+	reqEnv, err := ProtoToPtrEnvironment(req.Env)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.UpdateRequest{Env: reqEnv}, 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{
+		EnvId:   string(req.EnvId),
+		SpaceId: string(req.SpaceId),
+	}, nil
+}
+
+func _Decode_SetAlias_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil SetAliasRequest")
+	}
+	req := request.(*pb.SetAliasRequest)
+	return &transport.SetAliasRequest{
+		Alias:   string(req.Alias),
+		EnvId:   string(req.EnvId),
+		SpaceId: string(req.SpaceId),
+	}, nil
+}
+
+func _Decode_RemoveAlias_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil RemoveAliasRequest")
+	}
+	req := request.(*pb.RemoveAliasRequest)
+	return &transport.RemoveAliasRequest{
+		Alias:   string(req.Alias),
+		EnvId:   string(req.EnvId),
+		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 := ProtoToPtrEnvironment(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)
+	respEnv, err := ProtoToPtrEnvironment(resp.Env)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.GetResponse{Env: respEnv}, 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)
+	respEnvs, err := ProtoToListPtrEnvironment(resp.Envs)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.ListResponse{Envs: respEnvs}, nil
+}
+
+func _Decode_Update_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
+}
+
+func _Decode_SetAlias_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_RemoveAlias_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_Migrate_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil MigrateRequest")
+	}
+	req := request.(*transport.MigrateRequest)
+	opts, _ := ElPtrMigrateOptionsToProto(req.Options)
+	return &pb.MigrateRequest{
+		EnvId:   req.EnvId,
+		SpaceId: req.SpaceId,
+		Options: opts,
+	}, nil
+}
+
+func _Encode_Migrate_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_Migrate_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil MigrateRequest")
+	}
+	req := request.(*pb.MigrateRequest)
+	opts, _ := ProtoToElPtrMigrateOptions(req.Options)
+	return &transport.MigrateRequest{
+		EnvId:   string(req.EnvId),
+		SpaceId: string(req.SpaceId),
+		Options: opts,
+	}, nil
+}
+
+func _Decode_Migrate_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
diff --git a/pkg/environments/transport/grpc/protobuf_type_converters.microgen.go b/pkg/environments/transport/grpc/protobuf_type_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..5402c513bc7ecbf3a7dc3b4a291282eed03f554e
--- /dev/null
+++ b/pkg/environments/transport/grpc/protobuf_type_converters.microgen.go
@@ -0,0 +1,99 @@
+// 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 (
+	pb "git.perx.ru/perxis/perxis-go/proto/environments"
+	service "git.perx.ru/perxis/perxis-go/pkg/environments"
+	"github.com/golang/protobuf/ptypes"
+)
+
+func PtrEnvironmentToProto(env *service.Environment) (*pb.Environment, error) {
+	if env == nil {
+		return nil, nil
+	}
+	protoEnvironment := &pb.Environment{
+		Id:          env.ID,
+		SpaceId:     env.SpaceID,
+		Description: env.Description,
+		Aliases:     env.Aliases,
+	}
+	if env.StateInfo != nil {
+		protoEnvironment.StateInfo = &pb.StateInfo{
+			State: pb.StateInfo_State(env.StateInfo.State),
+			Info:  env.StateInfo.Info,
+		}
+		protoEnvironment.StateInfo.StartedAt, _ = ptypes.TimestampProto(env.StateInfo.StartedAt)
+	}
+	if env.Config != nil {
+		protoEnvironment.Config = &pb.Config{
+			SourceId: env.Config.SourceID,
+			Features: env.Config.Features,
+		}
+	}
+	return protoEnvironment, nil
+}
+
+func ProtoToPtrEnvironment(protoEnv *pb.Environment) (*service.Environment, error) {
+	if protoEnv == nil {
+		return nil, nil
+	}
+	env := &service.Environment{
+		ID:          protoEnv.Id,
+		SpaceID:     protoEnv.SpaceId,
+		Description: protoEnv.Description,
+		Aliases:     protoEnv.Aliases,
+	}
+	if protoEnv.StateInfo != nil {
+		env.StateInfo = &service.StateInfo{
+			State: service.State(protoEnv.StateInfo.State),
+			Info:  protoEnv.StateInfo.Info,
+		}
+		env.StateInfo.StartedAt, _ = ptypes.Timestamp(protoEnv.StateInfo.StartedAt)
+	}
+	if protoEnv.Config != nil {
+		env.Config = &service.Config{
+			SourceID: protoEnv.Config.SourceId,
+			Features: protoEnv.Config.Features,
+		}
+	}
+	return env, nil
+}
+
+func ListPtrEnvironmentToProto(envs []*service.Environment) ([]*pb.Environment, error) {
+	protoEnvironments := make([]*pb.Environment, 0, len(envs))
+	for _, environment := range envs {
+		protoEnvironment, err := PtrEnvironmentToProto(environment)
+		if err != nil {
+			return nil, err
+		}
+		protoEnvironments = append(protoEnvironments, protoEnvironment)
+	}
+	return protoEnvironments, nil
+}
+
+func ProtoToListPtrEnvironment(protoEnvs []*pb.Environment) ([]*service.Environment, error) {
+	environments := make([]*service.Environment, 0, len(protoEnvs))
+	for _, protoEnvironment := range protoEnvs {
+		environment, err := ProtoToPtrEnvironment(protoEnvironment)
+		if err != nil {
+			return nil, err
+		}
+		environments = append(environments, environment)
+	}
+	return environments, nil
+}
+
+func ElPtrMigrateOptionsToProto(options []*service.MigrateOptions) (*pb.MigrateOptions, error) {
+	opts := service.MergeMigrateOptions(options...)
+	return &pb.MigrateOptions{Wait: opts.Wait}, nil
+}
+
+func ProtoToElPtrMigrateOptions(protoOptions *pb.MigrateOptions) ([]*service.MigrateOptions, error) {
+	if protoOptions == nil {
+		return nil, nil
+	}
+	return []*service.MigrateOptions{{Wait: protoOptions.Wait}}, nil
+}
diff --git a/pkg/environments/transport/grpc/server.microgen.go b/pkg/environments/transport/grpc/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..1389d32692eaf27f5068073d330696d1ad221bd3
--- /dev/null
+++ b/pkg/environments/transport/grpc/server.microgen.go
@@ -0,0 +1,142 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// DO NOT EDIT.
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/environments/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/environments"
+	grpc "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	context "golang.org/x/net/context"
+)
+
+type environmentsServer struct {
+	create      grpc.Handler
+	get         grpc.Handler
+	list        grpc.Handler
+	update      grpc.Handler
+	delete      grpc.Handler
+	setAlias    grpc.Handler
+	removeAlias grpc.Handler
+	migrate     grpc.Handler
+
+	pb.UnimplementedEnvironmentsServer
+}
+
+func NewGRPCServer(endpoints *transport.EndpointsSet, opts ...grpc.ServerOption) pb.EnvironmentsServer {
+	return &environmentsServer{
+		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...,
+		),
+		migrate: grpc.NewServer(
+			endpoints.MigrateEndpoint,
+			_Decode_Migrate_Request,
+			_Encode_Migrate_Response,
+			opts...,
+		),
+		removeAlias: grpc.NewServer(
+			endpoints.RemoveAliasEndpoint,
+			_Decode_RemoveAlias_Request,
+			_Encode_RemoveAlias_Response,
+			opts...,
+		),
+		setAlias: grpc.NewServer(
+			endpoints.SetAliasEndpoint,
+			_Decode_SetAlias_Request,
+			_Encode_SetAlias_Response,
+			opts...,
+		),
+		update: grpc.NewServer(
+			endpoints.UpdateEndpoint,
+			_Decode_Update_Request,
+			_Encode_Update_Response,
+			opts...,
+		),
+	}
+}
+
+func (S *environmentsServer) 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 *environmentsServer) 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 *environmentsServer) 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 *environmentsServer) 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 *environmentsServer) 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
+}
+
+func (S *environmentsServer) SetAlias(ctx context.Context, req *pb.SetAliasRequest) (*empty.Empty, error) {
+	_, resp, err := S.setAlias.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
+
+func (S *environmentsServer) RemoveAlias(ctx context.Context, req *pb.RemoveAliasRequest) (*empty.Empty, error) {
+	_, resp, err := S.removeAlias.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
+
+func (S *environmentsServer) Migrate(ctx context.Context, req *pb.MigrateRequest) (*empty.Empty, error) {
+	_, resp, err := S.migrate.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
diff --git a/pkg/environments/transport/server.microgen.go b/pkg/environments/transport/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..3a0b20a55cdf3aff82beb803efb59c5833e5afd7
--- /dev/null
+++ b/pkg/environments/transport/server.microgen.go
@@ -0,0 +1,88 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+
+"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/environments"
+	endpoint "github.com/go-kit/kit/endpoint"
+)
+
+func Endpoints(svc environments.Environments) EndpointsSet {
+	return EndpointsSet{
+		CreateEndpoint:      CreateEndpoint(svc),
+		DeleteEndpoint:      DeleteEndpoint(svc),
+		GetEndpoint:         GetEndpoint(svc),
+		ListEndpoint:        ListEndpoint(svc),
+		MigrateEndpoint:     MigrateEndpoint(svc),
+		RemoveAliasEndpoint: RemoveAliasEndpoint(svc),
+		SetAliasEndpoint:    SetAliasEndpoint(svc),
+		UpdateEndpoint:      UpdateEndpoint(svc),
+	}
+}
+
+func CreateEndpoint(svc environments.Environments) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*CreateRequest)
+		res0, res1 := svc.Create(arg0, req.Env)
+		return &CreateResponse{Created: res0}, res1
+	}
+}
+
+func GetEndpoint(svc environments.Environments) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*GetRequest)
+		res0, res1 := svc.Get(arg0, req.SpaceId, req.EnvId)
+		return &GetResponse{Env: res0}, res1
+	}
+}
+
+func ListEndpoint(svc environments.Environments) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*ListRequest)
+		res0, res1 := svc.List(arg0, req.SpaceId)
+		return &ListResponse{Envs: res0}, res1
+	}
+}
+
+func UpdateEndpoint(svc environments.Environments) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*UpdateRequest)
+		res0 := svc.Update(arg0, req.Env)
+		return &UpdateResponse{}, res0
+	}
+}
+
+func DeleteEndpoint(svc environments.Environments) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*DeleteRequest)
+		res0 := svc.Delete(arg0, req.SpaceId, req.EnvId)
+		return &DeleteResponse{}, res0
+	}
+}
+
+func SetAliasEndpoint(svc environments.Environments) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*SetAliasRequest)
+		res0 := svc.SetAlias(arg0, req.SpaceId, req.EnvId, req.Alias)
+		return &SetAliasResponse{}, res0
+	}
+}
+
+func RemoveAliasEndpoint(svc environments.Environments) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*RemoveAliasRequest)
+		res0 := svc.RemoveAlias(arg0, req.SpaceId, req.EnvId, req.Alias)
+		return &RemoveAliasResponse{}, res0
+	}
+}
+
+func MigrateEndpoint(svc environments.Environments) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*MigrateRequest)
+		res0 := svc.Migrate(arg0, req.SpaceId, req.EnvId, req.Options...)
+		return &MigrateResponse{}, res0
+	}
+}
diff --git a/pkg/invitations/invitation.go b/pkg/invitations/invitation.go
new file mode 100644
index 0000000000000000000000000000000000000000..5dc5913774fc5774076823309abdf52b195c87c2
--- /dev/null
+++ b/pkg/invitations/invitation.go
@@ -0,0 +1,16 @@
+package invitations
+
+import "time"
+
+const InvitationTTL = 7 * 24 * time.Hour
+
+type Invitation struct {
+	ID         string     `bson:"_id"`
+	Email      string     `bson:"email"`
+	OrgID      string     `bson:"orgId"`
+	SpaceID    string     `bson:"spaceId"`
+	OwnerID    string     `bson:"ownerId"` // Invitation owner
+	Role       string     `bson:"role"`
+	CreatedAt  *time.Time `bson:"createdAt"`
+	ValidUntil *time.Time `bson:"validUntil"`
+}
diff --git a/pkg/invitations/mocks/Invitations.go b/pkg/invitations/mocks/Invitations.go
new file mode 100644
index 0000000000000000000000000000000000000000..610f9fff80d4867b35b80ff464bcf2a8a9d0e764
--- /dev/null
+++ b/pkg/invitations/mocks/Invitations.go
@@ -0,0 +1,120 @@
+// Code generated by mockery v2.7.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/invitations"
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+	"github.com/stretchr/testify/mock"
+)
+
+// Invitations is an autogenerated mock type for the Invitations type
+type Invitations struct {
+	mock.Mock
+}
+
+// Accept provides a mock function with given fields: ctx, invitationId, userId
+func (_m *Invitations) Accept(ctx context.Context, invitationId string, userId string) error {
+	ret := _m.Called(ctx, invitationId, userId)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
+		r0 = rf(ctx, invitationId, userId)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Create provides a mock function with given fields: ctx, invitation
+func (_m *Invitations) Create(ctx context.Context, invitation *invitations.Invitation) (*invitations.Invitation, error) {
+	ret := _m.Called(ctx, invitation)
+
+	var r0 *invitations.Invitation
+	if rf, ok := ret.Get(0).(func(context.Context, *invitations.Invitation) *invitations.Invitation); ok {
+		r0 = rf(ctx, invitation)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*invitations.Invitation)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *invitations.Invitation) error); ok {
+		r1 = rf(ctx, invitation)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Delete provides a mock function with given fields: ctx, invitationId
+func (_m *Invitations) Delete(ctx context.Context, invitationId string) error {
+	ret := _m.Called(ctx, invitationId)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
+		r0 = rf(ctx, invitationId)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Find provides a mock function with given fields: ctx, filter, opts
+func (_m *Invitations) Find(ctx context.Context, filter *invitations.Filter, opts *options.FindOptions) ([]*invitations.Invitation, int, error) {
+	ret := _m.Called(ctx, filter, opts)
+
+	var r0 []*invitations.Invitation
+	if rf, ok := ret.Get(0).(func(context.Context, *invitations.Filter, *options.FindOptions) []*invitations.Invitation); ok {
+		r0 = rf(ctx, filter, opts)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*invitations.Invitation)
+		}
+	}
+
+	var r1 int
+	if rf, ok := ret.Get(1).(func(context.Context, *invitations.Filter, *options.FindOptions) int); ok {
+		r1 = rf(ctx, filter, opts)
+	} else {
+		r1 = ret.Get(1).(int)
+	}
+
+	var r2 error
+	if rf, ok := ret.Get(2).(func(context.Context, *invitations.Filter, *options.FindOptions) error); ok {
+		r2 = rf(ctx, filter, opts)
+	} else {
+		r2 = ret.Error(2)
+	}
+
+	return r0, r1, r2
+}
+
+// Get provides a mock function with given fields: ctx, invitationId
+func (_m *Invitations) Get(ctx context.Context, invitationId string) (*invitations.Invitation, error) {
+	ret := _m.Called(ctx, invitationId)
+
+	var r0 *invitations.Invitation
+	if rf, ok := ret.Get(0).(func(context.Context, string) *invitations.Invitation); ok {
+		r0 = rf(ctx, invitationId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*invitations.Invitation)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, invitationId)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
diff --git a/pkg/invitations/service.go b/pkg/invitations/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..26426d67462cdcb51fc7fe8f1bd60dc1fde69260
--- /dev/null
+++ b/pkg/invitations/service.go
@@ -0,0 +1,27 @@
+package invitations
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+)
+
+type Filter struct {
+	ID      []string
+	Email   []string
+	OrgID   []string
+	SpaceID []string
+	OwnerID []string
+	Role    []string
+}
+
+// @microgen grpc
+// @protobuf git.perx.ru/perxis/perxis-go/proto/invitations
+// @grpc-addr content.invitations.Invitations
+type Invitations interface {
+	Create(ctx context.Context, invitation *Invitation) (created *Invitation, err error)
+	Get(ctx context.Context, invitationId string) (invitation *Invitation, err error)
+	Accept(ctx context.Context, invitationId, userId string) (err error)
+	Find(ctx context.Context, filter *Filter, opts *options.FindOptions) (invitations []*Invitation, total int, err error)
+	Delete(ctx context.Context, invitationId string) (err error)
+}
diff --git a/pkg/invitations/transport/client.microgen.go b/pkg/invitations/transport/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..6f896b0e67fa88a1eaa539c305a47fada11c9c8b
--- /dev/null
+++ b/pkg/invitations/transport/client.microgen.go
@@ -0,0 +1,79 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+	"errors"
+
+	invitations "git.perx.ru/perxis/perxis-go/pkg/invitations"
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+func (set EndpointsSet) Create(arg0 context.Context, arg1 *invitations.Invitation) (res0 *invitations.Invitation, res1 error) {
+	request := CreateRequest{Invitation: arg1}
+	response, res1 := set.CreateEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*CreateResponse).Created, res1
+}
+
+func (set EndpointsSet) Get(arg0 context.Context, arg1 string) (res0 *invitations.Invitation, res1 error) {
+	request := GetRequest{InvitationId: arg1}
+	response, res1 := set.GetEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*GetResponse).Invitation, res1
+}
+
+func (set EndpointsSet) Accept(arg0 context.Context, arg1 string, arg2 string) (res0 error) {
+	request := AcceptRequest{
+		InvitationId: arg1,
+		UserId:       arg2,
+	}
+	_, res0 = set.AcceptEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) Find(arg0 context.Context, arg1 *invitations.Filter, arg2 *options.FindOptions) (res0 []*invitations.Invitation, res1 int, res2 error) {
+	request := FindRequest{
+		Filter: arg1,
+		Opts:   arg2,
+	}
+	response, res2 := set.FindEndpoint(arg0, &request)
+	if res2 != nil {
+		if e, ok := status.FromError(res2); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res2 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*FindResponse).Invitations, response.(*FindResponse).Total, res2
+}
+
+func (set EndpointsSet) Delete(arg0 context.Context, arg1 string) (res0 error) {
+	request := DeleteRequest{InvitationId: arg1}
+	_, res0 = set.DeleteEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
diff --git a/pkg/invitations/transport/endpoints.microgen.go b/pkg/invitations/transport/endpoints.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..70d78bbcf04e68ad97c36d90b4f66f3287f108d2
--- /dev/null
+++ b/pkg/invitations/transport/endpoints.microgen.go
@@ -0,0 +1,14 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import endpoint "github.com/go-kit/kit/endpoint"
+
+// EndpointsSet implements Invitations API and used for transport purposes.
+type EndpointsSet struct {
+	CreateEndpoint endpoint.Endpoint
+	GetEndpoint    endpoint.Endpoint
+	AcceptEndpoint endpoint.Endpoint
+	FindEndpoint   endpoint.Endpoint
+	DeleteEndpoint endpoint.Endpoint
+}
diff --git a/pkg/invitations/transport/exchanges.microgen.go b/pkg/invitations/transport/exchanges.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..bb36e466cbcb25584c462931a3eab55ffd90732c
--- /dev/null
+++ b/pkg/invitations/transport/exchanges.microgen.go
@@ -0,0 +1,46 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"git.perx.ru/perxis/perxis-go/pkg/invitations"
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+)
+
+type (
+	CreateRequest struct {
+		Invitation *invitations.Invitation `json:"invitation"`
+	}
+	CreateResponse struct {
+		Created *invitations.Invitation `json:"created"`
+	}
+
+	GetRequest struct {
+		InvitationId string `json:"invitation_id"`
+	}
+	GetResponse struct {
+		Invitation *invitations.Invitation `json:"invitation"`
+	}
+
+	AcceptRequest struct {
+		InvitationId string `json:"invitation_id"`
+		UserId       string `json:"user_id"`
+	}
+	// Formal exchange type, please do not delete.
+	AcceptResponse struct{}
+
+	FindRequest struct {
+		Filter *invitations.Filter  `json:"filter"`
+		Opts   *options.FindOptions `json:"opts"`
+	}
+	FindResponse struct {
+		Invitations []*invitations.Invitation `json:"invitations"`
+		Total       int                       `json:"total"`
+	}
+
+	DeleteRequest struct {
+		InvitationId string `json:"invitation_id"`
+	}
+	// Formal exchange type, please do not delete.
+	DeleteResponse struct{}
+)
diff --git a/pkg/invitations/transport/grpc/client.microgen.go b/pkg/invitations/transport/grpc/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..e319e19f9d32b9d3efa1e0d6fe485372e177efa2
--- /dev/null
+++ b/pkg/invitations/transport/grpc/client.microgen.go
@@ -0,0 +1,54 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/invitations/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/invitations"
+	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.invitations.Invitations"
+	}
+	return transport.EndpointsSet{
+		AcceptEndpoint: grpckit.NewClient(
+			conn, addr, "Accept",
+			_Encode_Accept_Request,
+			_Decode_Accept_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+		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(),
+		FindEndpoint: grpckit.NewClient(
+			conn, addr, "Find",
+			_Encode_Find_Request,
+			_Decode_Find_Response,
+			pb.FindResponse{},
+			opts...,
+		).Endpoint(),
+		GetEndpoint: grpckit.NewClient(
+			conn, addr, "Get",
+			_Encode_Get_Request,
+			_Decode_Get_Response,
+			pb.GetResponse{},
+			opts...,
+		).Endpoint(),
+	}
+}
diff --git a/pkg/invitations/transport/grpc/protobuf_endpoint_converters.microgen.go b/pkg/invitations/transport/grpc/protobuf_endpoint_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..85403cab8c13fb71612e275a2851c02a904d8abc
--- /dev/null
+++ b/pkg/invitations/transport/grpc/protobuf_endpoint_converters.microgen.go
@@ -0,0 +1,223 @@
+// 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/invitations/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/invitations"
+	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)
+	pbInvitation, err := PtrInvitationToProto(req.Invitation)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateRequest{Invitation: pbInvitation}, 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{InvitationId: req.InvitationId}, nil
+}
+
+func _Encode_Accept_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil AcceptRequest")
+	}
+	req := request.(*transport.AcceptRequest)
+	return &pb.AcceptRequest{
+		InvitationId: req.InvitationId,
+		UserId:       req.UserId,
+	}, nil
+}
+
+func _Encode_Find_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil FindRequest")
+	}
+	req := request.(*transport.FindRequest)
+	reqFilter, err := PtrFilterToProto(req.Filter)
+	if err != nil {
+		return nil, err
+	}
+	reqOpts, err := PtrServicesFindOptionsToProto(req.Opts)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.FindRequest{
+		Filter: reqFilter,
+		Opts:   reqOpts,
+	}, 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{InvitationId: req.InvitationId}, 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)
+	respInvitation, err := PtrInvitationToProto(resp.Created)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateResponse{Invitation: respInvitation}, 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)
+	respInvitation, err := PtrInvitationToProto(resp.Invitation)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.GetResponse{Invitation: respInvitation}, nil
+}
+
+func _Encode_Accept_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_Find_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil FindResponse")
+	}
+	resp := response.(*transport.FindResponse)
+	respInvitations, err := ListPtrInvitationToProto(resp.Invitations)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.FindResponse{
+		Invitations: respInvitations,
+		Total:       int64(resp.Total),
+	}, 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)
+	invitation, err := ProtoToPtrInvitation(req.Invitation)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateRequest{Invitation: invitation}, 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{InvitationId: string(req.InvitationId)}, nil
+}
+
+func _Decode_Accept_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil AcceptRequest")
+	}
+	req := request.(*pb.AcceptRequest)
+	return &transport.AcceptRequest{
+		InvitationId: string(req.InvitationId),
+		UserId:       string(req.UserId),
+	}, nil
+}
+
+func _Decode_Find_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil FindRequest")
+	}
+	req := request.(*pb.FindRequest)
+	reqFilter, err := ProtoToPtrFilter(req.Filter)
+	if err != nil {
+		return nil, err
+	}
+	reqOpts, err := ProtoToPtrServicesFindOptions(req.Opts)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.FindRequest{
+		Filter: reqFilter,
+		Opts:   reqOpts,
+	}, 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{InvitationId: string(req.InvitationId)}, 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)
+	respInvitation, err := ProtoToPtrInvitation(resp.Invitation)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateResponse{Created: respInvitation}, 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)
+	respInvitation, err := ProtoToPtrInvitation(resp.Invitation)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.GetResponse{Invitation: respInvitation}, nil
+}
+
+func _Decode_Accept_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_Find_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil FindResponse")
+	}
+	resp := response.(*pb.FindResponse)
+	respInvitations, err := ProtoToListPtrInvitation(resp.Invitations)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.FindResponse{
+		Invitations: respInvitations,
+		Total:       int(resp.Total),
+	}, nil
+}
+
+func _Decode_Delete_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
diff --git a/pkg/invitations/transport/grpc/protobuf_type_converters.microgen.go b/pkg/invitations/transport/grpc/protobuf_type_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..9899e39885a8ca07de15f74f2eeda24d65296928
--- /dev/null
+++ b/pkg/invitations/transport/grpc/protobuf_type_converters.microgen.go
@@ -0,0 +1,161 @@
+// 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 (
+	"time"
+
+	service "git.perx.ru/perxis/perxis-go/pkg/invitations"
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+	pb "git.perx.ru/perxis/perxis-go/proto/invitations"
+	"github.com/golang/protobuf/ptypes"
+	timestamp "github.com/golang/protobuf/ptypes/timestamp"
+)
+
+func PtrTimeTimeToProto(validUntil *time.Time) (*timestamp.Timestamp, error) {
+	if validUntil == nil {
+		return nil, nil
+	}
+	t, err := ptypes.TimestampProto(*validUntil)
+	if err != nil {
+		return nil, err
+	}
+	return t, err
+}
+
+func ProtoToPtrTimeTime(protoValidUntil *timestamp.Timestamp) (*time.Time, error) {
+	if protoValidUntil == nil {
+		return nil, nil
+	}
+	t, err := ptypes.Timestamp(protoValidUntil)
+	if err != nil {
+		return nil, err
+	}
+	return &t, nil
+}
+
+func PtrInvitationToProto(invitation *service.Invitation) (*pb.Invitation, error) {
+	if invitation == nil {
+		return nil, nil
+	}
+	pi := &pb.Invitation{
+		Id:      invitation.ID,
+		Email:   invitation.Email,
+		OrgId:   invitation.OrgID,
+		SpaceId: invitation.SpaceID,
+		OwnerId: invitation.OwnerID,
+		Role:    invitation.Role,
+	}
+	if invitation.CreatedAt != nil && !invitation.CreatedAt.IsZero() {
+		t, _ := ptypes.TimestampProto(*invitation.CreatedAt)
+		pi.CreatedAt = t
+	}
+	if invitation.ValidUntil != nil && !invitation.ValidUntil.IsZero() {
+		t, _ := ptypes.TimestampProto(*invitation.ValidUntil)
+		pi.ValidUntil = t
+	}
+	return pi, nil
+}
+
+func ProtoToPtrInvitation(protoInvitation *pb.Invitation) (*service.Invitation, error) {
+	if protoInvitation == nil {
+		return nil, nil
+	}
+	i := &service.Invitation{
+		ID:      protoInvitation.Id,
+		Email:   protoInvitation.Email,
+		OrgID:   protoInvitation.OrgId,
+		SpaceID: protoInvitation.SpaceId,
+		OwnerID: protoInvitation.OwnerId,
+		Role:    protoInvitation.Role,
+	}
+	if protoInvitation.CreatedAt != nil {
+		t, _ := ptypes.Timestamp(protoInvitation.CreatedAt)
+		i.CreatedAt = &t
+	}
+	if protoInvitation.ValidUntil != nil {
+		t, _ := ptypes.Timestamp(protoInvitation.ValidUntil)
+		i.ValidUntil = &t
+	}
+	return i, nil
+}
+
+func PtrFilterToProto(filter *service.Filter) (*pb.Filter, error) {
+	if filter == nil {
+		return nil, nil
+	}
+	return &pb.Filter{
+		Id:      filter.ID,
+		Email:   filter.Email,
+		OrgId:   filter.OrgID,
+		SpaceId: filter.SpaceID,
+		OwnerId: filter.OwnerID,
+		Role:    filter.Role,
+	}, nil
+}
+
+func ProtoToPtrFilter(protoFilter *pb.Filter) (*service.Filter, error) {
+	if protoFilter == nil {
+		return nil, nil
+	}
+	return &service.Filter{
+		ID:      protoFilter.Id,
+		Email:   protoFilter.Email,
+		OrgID:   protoFilter.OrgId,
+		SpaceID: protoFilter.SpaceId,
+		OwnerID: protoFilter.OwnerId,
+		Role:    protoFilter.Role,
+	}, nil
+}
+
+func PtrServicesFindOptionsToProto(opts *options.FindOptions) (*pb.FindOptions, error) {
+	if opts == nil {
+		return nil, nil
+	}
+	return &pb.FindOptions{
+		Sort:     opts.Sort,
+		PageNum:  int32(opts.PageNum),
+		PageSize: int32(opts.PageSize),
+	}, nil
+}
+
+func ProtoToPtrServicesFindOptions(protoOpts *pb.FindOptions) (*options.FindOptions, error) {
+	if protoOpts == nil {
+		return nil, nil
+	}
+	return &options.FindOptions{
+		SortOptions: options.SortOptions{
+			Sort: protoOpts.Sort,
+		},
+		PaginationOptions: options.PaginationOptions{
+			PageNum:  int(protoOpts.PageNum),
+			PageSize: int(protoOpts.PageSize),
+		},
+	}, nil
+}
+
+func ListPtrInvitationToProto(invitations []*service.Invitation) ([]*pb.Invitation, error) {
+	protoInvitations := make([]*pb.Invitation, 0, len(invitations))
+	for _, i := range invitations {
+		pi, err := PtrInvitationToProto(i)
+		if err != nil {
+			return nil, err
+		}
+		protoInvitations = append(protoInvitations, pi)
+	}
+	return protoInvitations, nil
+}
+
+func ProtoToListPtrInvitation(protoInvitations []*pb.Invitation) ([]*service.Invitation, error) {
+	invitations := make([]*service.Invitation, 0, len(protoInvitations))
+	for _, pi := range protoInvitations {
+		p, err := ProtoToPtrInvitation(pi)
+		if err != nil {
+			return nil, err
+		}
+		invitations = append(invitations, p)
+	}
+	return invitations, nil
+}
diff --git a/pkg/invitations/transport/grpc/server.microgen.go b/pkg/invitations/transport/grpc/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..fa7cf241737173d3c41cadaa2ae9fa17ecb43fe7
--- /dev/null
+++ b/pkg/invitations/transport/grpc/server.microgen.go
@@ -0,0 +1,97 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// DO NOT EDIT.
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/invitations/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/invitations"
+	grpc "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	context "golang.org/x/net/context"
+)
+
+type invitationsServer struct {
+	create grpc.Handler
+	get    grpc.Handler
+	accept grpc.Handler
+	find   grpc.Handler
+	delete grpc.Handler
+
+	pb.UnimplementedInvitationsServer
+}
+
+func NewGRPCServer(endpoints *transport.EndpointsSet, opts ...grpc.ServerOption) pb.InvitationsServer {
+	return &invitationsServer{
+		accept: grpc.NewServer(
+			endpoints.AcceptEndpoint,
+			_Decode_Accept_Request,
+			_Encode_Accept_Response,
+			opts...,
+		),
+		create: grpc.NewServer(
+			endpoints.CreateEndpoint,
+			_Decode_Create_Request,
+			_Encode_Create_Response,
+			opts...,
+		),
+		delete: grpc.NewServer(
+			endpoints.DeleteEndpoint,
+			_Decode_Delete_Request,
+			_Encode_Delete_Response,
+			opts...,
+		),
+		find: grpc.NewServer(
+			endpoints.FindEndpoint,
+			_Decode_Find_Request,
+			_Encode_Find_Response,
+			opts...,
+		),
+		get: grpc.NewServer(
+			endpoints.GetEndpoint,
+			_Decode_Get_Request,
+			_Encode_Get_Response,
+			opts...,
+		),
+	}
+}
+
+func (S *invitationsServer) 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 *invitationsServer) 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 *invitationsServer) Accept(ctx context.Context, req *pb.AcceptRequest) (*empty.Empty, error) {
+	_, resp, err := S.accept.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
+
+func (S *invitationsServer) Find(ctx context.Context, req *pb.FindRequest) (*pb.FindResponse, error) {
+	_, resp, err := S.find.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.FindResponse), nil
+}
+
+func (S *invitationsServer) 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/invitations/transport/server.microgen.go b/pkg/invitations/transport/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..326659f2b813d8f6b306d8b9498c0590c6434011
--- /dev/null
+++ b/pkg/invitations/transport/server.microgen.go
@@ -0,0 +1,63 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+
+	invitations "git.perx.ru/perxis/perxis-go/pkg/invitations"
+	endpoint "github.com/go-kit/kit/endpoint"
+)
+
+func Endpoints(svc invitations.Invitations) EndpointsSet {
+	return EndpointsSet{
+		AcceptEndpoint: AcceptEndpoint(svc),
+		CreateEndpoint: CreateEndpoint(svc),
+		DeleteEndpoint: DeleteEndpoint(svc),
+		FindEndpoint:   FindEndpoint(svc),
+		GetEndpoint:    GetEndpoint(svc),
+	}
+}
+
+func CreateEndpoint(svc invitations.Invitations) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*CreateRequest)
+		res0, res1 := svc.Create(arg0, req.Invitation)
+		return &CreateResponse{Created: res0}, res1
+	}
+}
+
+func GetEndpoint(svc invitations.Invitations) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*GetRequest)
+		res0, res1 := svc.Get(arg0, req.InvitationId)
+		return &GetResponse{Invitation: res0}, res1
+	}
+}
+
+func AcceptEndpoint(svc invitations.Invitations) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*AcceptRequest)
+		res0 := svc.Accept(arg0, req.InvitationId, req.UserId)
+		return &AcceptResponse{}, res0
+	}
+}
+
+func FindEndpoint(svc invitations.Invitations) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*FindRequest)
+		res0, res1, res2 := svc.Find(arg0, req.Filter, req.Opts)
+		return &FindResponse{
+			Invitations: res0,
+			Total:       res1,
+		}, res2
+	}
+}
+
+func DeleteEndpoint(svc invitations.Invitations) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*DeleteRequest)
+		res0 := svc.Delete(arg0, req.InvitationId)
+		return &DeleteResponse{}, res0
+	}
+}
diff --git a/pkg/roles/mocks/Roles.go b/pkg/roles/mocks/Roles.go
new file mode 100644
index 0000000000000000000000000000000000000000..d7e61236e36d2a5f544950f0634c5204d4a30a3e
--- /dev/null
+++ b/pkg/roles/mocks/Roles.go
@@ -0,0 +1,112 @@
+// Code generated by mockery v2.7.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	"context"
+
+	roles "git.perx.ru/perxis/perxis-go/pkg/roles"
+	"github.com/stretchr/testify/mock"
+)
+
+// Roles is an autogenerated mock type for the Roles type
+type Roles struct {
+	mock.Mock
+}
+
+// Create provides a mock function with given fields: ctx, role
+func (_m *Roles) Create(ctx context.Context, role *roles.Role) (*roles.Role, error) {
+	ret := _m.Called(ctx, role)
+
+	var r0 *roles.Role
+	if rf, ok := ret.Get(0).(func(context.Context, *roles.Role) *roles.Role); ok {
+		r0 = rf(ctx, role)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*roles.Role)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *roles.Role) error); ok {
+		r1 = rf(ctx, role)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Delete provides a mock function with given fields: ctx, spaceId, roleId
+func (_m *Roles) Delete(ctx context.Context, spaceId string, roleId string) error {
+	ret := _m.Called(ctx, spaceId, roleId)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
+		r0 = rf(ctx, spaceId, roleId)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Get provides a mock function with given fields: ctx, spaceId, roleId
+func (_m *Roles) Get(ctx context.Context, spaceId string, roleId string) (*roles.Role, error) {
+	ret := _m.Called(ctx, spaceId, roleId)
+
+	var r0 *roles.Role
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) *roles.Role); ok {
+		r0 = rf(ctx, spaceId, roleId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*roles.Role)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
+		r1 = rf(ctx, spaceId, roleId)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// List provides a mock function with given fields: ctx, spaceId
+func (_m *Roles) List(ctx context.Context, spaceId string) ([]*roles.Role, error) {
+	ret := _m.Called(ctx, spaceId)
+
+	var r0 []*roles.Role
+	if rf, ok := ret.Get(0).(func(context.Context, string) []*roles.Role); ok {
+		r0 = rf(ctx, spaceId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*roles.Role)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, spaceId)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Update provides a mock function with given fields: ctx, role
+func (_m *Roles) Update(ctx context.Context, role *roles.Role) error {
+	ret := _m.Called(ctx, role)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *roles.Role) error); ok {
+		r0 = rf(ctx, role)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
diff --git a/pkg/roles/role.go b/pkg/roles/role.go
new file mode 100644
index 0000000000000000000000000000000000000000..4c284ee5d45026aedfc284ecc16e146e78b56bb2
--- /dev/null
+++ b/pkg/roles/role.go
@@ -0,0 +1,65 @@
+package roles
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/data"
+	"git.perx.ru/perxis/perxis-go/pkg/environments"
+	"git.perx.ru/perxis/perxis-go/pkg/permission"
+)
+
+const (
+	AnonymousRole  = "anonymous"
+	AuthorizedRole = "authorized"
+	ViewRole       = "view"
+)
+
+type Role struct {
+	// Внутренний идентификатор роли
+	ID string `json:"id" bson:"_id"`
+
+	// Идентификатор пространства
+	SpaceID string `json:"spaceId" bson:"-"`
+
+	// Описание роли, назначение
+	Description string `json:"description" bson:"description"`
+
+	// Список доступных окружений (ID или Alias)
+	Environments []string `json:"environments" bson:"environments"`
+
+	// Список правил доступа к коллекциям
+	Rules permission.Rules `json:"rules" bson:"rules"`
+
+	// Разрешить доступ API управления
+	AllowManagement bool `json:"allow_management" bson:"allow_management"`
+}
+
+func (r Role) CanAccessEnvironment(ctx context.Context, service environments.Environments, spaceID, envID string) bool {
+	if spaceID == "" || envID == "" {
+		return false
+	}
+
+	// Если явно не указаны доступные окружения - доступ по умолчанию к окружению master
+	if len(r.Environments) == 0 {
+		r.Environments = []string{environments.DefaultEnvironment}
+	}
+
+	if data.Contains(envID, r.Environments) {
+		return true
+	}
+
+	e, err := service.Get(ctx, spaceID, envID)
+	if err != nil || e == nil {
+		return false
+	}
+
+	aliases := append(e.Aliases, e.ID)
+
+	for _, ce := range r.Environments {
+		if data.Contains(ce, aliases) {
+			return true
+		}
+	}
+
+	return false
+}
diff --git a/pkg/roles/service.go b/pkg/roles/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..b003008b10c7c0a0de6549c60262741fdf441f65
--- /dev/null
+++ b/pkg/roles/service.go
@@ -0,0 +1,16 @@
+package roles
+
+import (
+	"context"
+)
+
+// @microgen grpc
+// @protobuf git.perx.ru/perxis/perxis-go/proto/roles
+// @grpc-addr content.roles.Roles
+type Roles interface {
+	Create(ctx context.Context, role *Role) (created *Role, err error)
+	Get(ctx context.Context, spaceId, roleId string) (role *Role, err error)
+	List(ctx context.Context, spaceId string) (roles []*Role, err error)
+	Update(ctx context.Context, role *Role) (err error)
+	Delete(ctx context.Context, spaceId, roleId string) (err error)
+}
diff --git a/pkg/roles/transport/client.microgen.go b/pkg/roles/transport/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..71b1de9e2b1b746962effe61dc63c37cf7977e69
--- /dev/null
+++ b/pkg/roles/transport/client.microgen.go
@@ -0,0 +1,78 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+	"errors"
+
+	roles "git.perx.ru/perxis/perxis-go/pkg/roles"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+func (set EndpointsSet) Create(arg0 context.Context, arg1 *roles.Role) (res0 *roles.Role, res1 error) {
+	request := CreateRequest{Role: arg1}
+	response, res1 := set.CreateEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*CreateResponse).Created, res1
+}
+
+func (set EndpointsSet) Get(arg0 context.Context, arg1 string, arg2 string) (res0 *roles.Role, res1 error) {
+	request := GetRequest{
+		RoleId:  arg2,
+		SpaceId: arg1,
+	}
+	response, res1 := set.GetEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*GetResponse).Role, res1
+}
+
+func (set EndpointsSet) List(arg0 context.Context, arg1 string) (res0 []*roles.Role, res1 error) {
+	request := ListRequest{SpaceId: arg1}
+	response, res1 := set.ListEndpoint(arg0, &request)
+	if res1 != nil {
+		if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res1 = errors.New(e.Message())
+		}
+		return
+	}
+	return response.(*ListResponse).Roles, res1
+}
+
+func (set EndpointsSet) Update(arg0 context.Context, arg1 *roles.Role) (res0 error) {
+	request := UpdateRequest{Role: arg1}
+	_, res0 = set.UpdateEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
+
+func (set EndpointsSet) Delete(arg0 context.Context, arg1 string, arg2 string) (res0 error) {
+	request := DeleteRequest{
+		RoleId:  arg2,
+		SpaceId: arg1,
+	}
+	_, res0 = set.DeleteEndpoint(arg0, &request)
+	if res0 != nil {
+		if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
+			res0 = errors.New(e.Message())
+		}
+		return
+	}
+	return res0
+}
diff --git a/pkg/roles/transport/endpoints.microgen.go b/pkg/roles/transport/endpoints.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..06fe337c3c48656eff8074237e5714c29eac8bb2
--- /dev/null
+++ b/pkg/roles/transport/endpoints.microgen.go
@@ -0,0 +1,14 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import endpoint "github.com/go-kit/kit/endpoint"
+
+// EndpointsSet implements Roles API and used for transport purposes.
+type EndpointsSet struct {
+	CreateEndpoint endpoint.Endpoint
+	GetEndpoint    endpoint.Endpoint
+	ListEndpoint   endpoint.Endpoint
+	UpdateEndpoint endpoint.Endpoint
+	DeleteEndpoint endpoint.Endpoint
+}
diff --git a/pkg/roles/transport/exchanges.microgen.go b/pkg/roles/transport/exchanges.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..bb7d8e5010877efb628ce34b4f89ae66857476eb
--- /dev/null
+++ b/pkg/roles/transport/exchanges.microgen.go
@@ -0,0 +1,42 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import roles "git.perx.ru/perxis/perxis-go/pkg/roles"
+
+type (
+	CreateRequest struct {
+		Role *roles.Role `json:"role"`
+	}
+	CreateResponse struct {
+		Created *roles.Role `json:"created"`
+	}
+
+	GetRequest struct {
+		SpaceId string `json:"space_id"`
+		RoleId  string `json:"role_id"`
+	}
+	GetResponse struct {
+		Role *roles.Role `json:"role"`
+	}
+
+	ListRequest struct {
+		SpaceId string `json:"space_id"`
+	}
+	ListResponse struct {
+		Roles []*roles.Role `json:"roles"`
+	}
+
+	UpdateRequest struct {
+		Role *roles.Role `json:"role"`
+	}
+	// Formal exchange type, please do not delete.
+	UpdateResponse struct{}
+
+	DeleteRequest struct {
+		SpaceId string `json:"space_id"`
+		RoleId  string `json:"role_id"`
+	}
+	// Formal exchange type, please do not delete.
+	DeleteResponse struct{}
+)
diff --git a/pkg/roles/transport/grpc/client.microgen.go b/pkg/roles/transport/grpc/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..747b1a8cada8b5e189e5e4c7994c79cb36df8b02
--- /dev/null
+++ b/pkg/roles/transport/grpc/client.microgen.go
@@ -0,0 +1,54 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/roles/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/roles"
+	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.roles.Roles"
+	}
+	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(),
+		UpdateEndpoint: grpckit.NewClient(
+			conn, addr, "Update",
+			_Encode_Update_Request,
+			_Decode_Update_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+	}
+}
diff --git a/pkg/roles/transport/grpc/protobuf_endpoint_converters.microgen.go b/pkg/roles/transport/grpc/protobuf_endpoint_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..aa66bf57105f290aae04a763c5a8e931a410fc53
--- /dev/null
+++ b/pkg/roles/transport/grpc/protobuf_endpoint_converters.microgen.go
@@ -0,0 +1,209 @@
+// 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/roles/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/roles"
+	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)
+	reqRole, err := PtrRoleToProto(req.Role)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateRequest{Role: reqRole}, 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{
+		RoleId:  req.RoleId,
+		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{SpaceId: req.SpaceId}, 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)
+	reqRole, err := PtrRoleToProto(req.Role)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.UpdateRequest{Role: reqRole}, 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{
+		RoleId:  req.RoleId,
+		SpaceId: req.SpaceId,
+	}, 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)
+	respRole, err := PtrRoleToProto(resp.Role)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.GetResponse{Role: respRole}, 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)
+	respRoles, err := ListPtrRoleToProto(resp.Roles)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.ListResponse{Roles: respRoles}, nil
+}
+
+func _Encode_Update_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)
+	reqRole, err := ProtoToPtrRole(req.Role)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateRequest{Role: reqRole}, 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{
+		RoleId:  string(req.RoleId),
+		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{SpaceId: string(req.SpaceId)}, 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)
+	reqRole, err := ProtoToPtrRole(req.Role)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.UpdateRequest{Role: reqRole}, 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{
+		RoleId:  string(req.RoleId),
+		SpaceId: string(req.SpaceId),
+	}, 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)
+	respRole, err := ProtoToPtrRole(resp.Role)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.GetResponse{Role: respRole}, 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)
+	respRoles, err := ProtoToListPtrRole(resp.Roles)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.ListResponse{Roles: respRoles}, nil
+}
+
+func _Decode_Update_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
+}
+
+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 := PtrRoleToProto(resp.Created)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateResponse{Created: respCreated}, 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 := ProtoToPtrRole(resp.Created)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateResponse{Created: respCreated}, nil
+}
diff --git a/pkg/roles/transport/grpc/protobuf_type_converters.microgen.go b/pkg/roles/transport/grpc/protobuf_type_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..10f207e5ebf3e07804009bf0bbb62401e0bdbf9a
--- /dev/null
+++ b/pkg/roles/transport/grpc/protobuf_type_converters.microgen.go
@@ -0,0 +1,110 @@
+// 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 (
+	permission "git.perx.ru/perxis/perxis-go/pkg/permission"
+	service "git.perx.ru/perxis/perxis-go/pkg/roles"
+	commonpb "git.perx.ru/perxis/perxis-go/proto/common"
+	pb "git.perx.ru/perxis/perxis-go/proto/roles"
+)
+
+func PtrRoleToProto(role *service.Role) (*pb.Role, error) {
+	if role == nil {
+		return nil, nil
+	}
+	rules := make([]*commonpb.Rule, 0, len(role.Rules))
+	for _, r := range role.Rules {
+		pr, _ := PtrPermissionRuleToProto(r)
+		rules = append(rules, pr)
+	}
+	return &pb.Role{
+		Id:              role.ID,
+		SpaceId:         role.SpaceID,
+		Description:     role.Description,
+		Rules:           rules,
+		Environments:    role.Environments,
+		AllowManagement: role.AllowManagement,
+	}, nil
+}
+
+func ProtoToPtrRole(protoRole *pb.Role) (*service.Role, error) {
+	if protoRole == nil {
+		return nil, nil
+	}
+
+	rules := make([]*permission.Rule, 0, len(protoRole.Rules))
+	for _, pr := range protoRole.Rules {
+		r, _ := ProtoToPtrPermissionRule(pr)
+		rules = append(rules, r)
+	}
+
+	return &service.Role{
+		ID:              protoRole.Id,
+		SpaceID:         protoRole.SpaceId,
+		Description:     protoRole.Description,
+		Rules:           rules,
+		Environments:    protoRole.Environments,
+		AllowManagement: protoRole.AllowManagement,
+	}, nil
+}
+
+func ListPtrRoleToProto(roles []*service.Role) ([]*pb.Role, error) {
+	protoRoles := make([]*pb.Role, 0, len(roles))
+	for _, r := range roles {
+		protoRole, _ := PtrRoleToProto(r)
+		protoRoles = append(protoRoles, protoRole)
+	}
+	return protoRoles, nil
+}
+
+func ProtoToListPtrRole(protoRoles []*pb.Role) ([]*service.Role, error) {
+	roles := make([]*service.Role, 0, len(protoRoles))
+	for _, r := range protoRoles {
+		role, _ := ProtoToPtrRole(r)
+		roles = append(roles, role)
+	}
+	return roles, nil
+}
+
+func PtrPermissionRuleToProto(rule *permission.Rule) (*commonpb.Rule, error) {
+	if rule == nil {
+		return nil, nil
+	}
+	actions := make([]commonpb.Action, 0, len(rule.Actions))
+	for _, a := range rule.Actions {
+		actions = append(actions, commonpb.Action(a))
+	}
+	return &commonpb.Rule{
+		CollectionId:    rule.CollectionID,
+		Actions:         actions,
+		Access:          commonpb.Access(rule.Access),
+		HiddenFields:    rule.HiddenFields,
+		ReadonlyFields:  rule.ReadonlyFields,
+		WriteonlyFields: rule.WriteonlyFields,
+		ReadFilter:      rule.ReadFilter,
+		WriteFilter:     rule.WriteFilter,
+	}, nil
+}
+
+func ProtoToPtrPermissionRule(protoRule *commonpb.Rule) (*permission.Rule, error) {
+	if protoRule == nil {
+		return nil, nil
+	}
+	actions := make([]permission.Action, 0, len(protoRule.Actions))
+	for _, a := range protoRule.Actions {
+		actions = append(actions, permission.Action(a))
+	}
+	return &permission.Rule{
+		CollectionID:    protoRule.CollectionId,
+		Actions:         actions,
+		Access:          permission.Access(protoRule.Access),
+		HiddenFields:    protoRule.HiddenFields,
+		ReadonlyFields:  protoRule.ReadonlyFields,
+		WriteonlyFields: protoRule.WriteonlyFields,
+		ReadFilter:      protoRule.ReadFilter,
+		WriteFilter:     protoRule.WriteFilter,
+	}, nil
+}
diff --git a/pkg/roles/transport/grpc/server.microgen.go b/pkg/roles/transport/grpc/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..dc012c75f1ff3a6965d9152f204c7d7f6b61d285
--- /dev/null
+++ b/pkg/roles/transport/grpc/server.microgen.go
@@ -0,0 +1,97 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// DO NOT EDIT.
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/roles/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/roles"
+	grpc "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	context "golang.org/x/net/context"
+)
+
+type rolesServer struct {
+	create grpc.Handler
+	get    grpc.Handler
+	list   grpc.Handler
+	update grpc.Handler
+	delete grpc.Handler
+
+	pb.UnimplementedRolesServer
+}
+
+func NewGRPCServer(endpoints *transport.EndpointsSet, opts ...grpc.ServerOption) pb.RolesServer {
+	return &rolesServer{
+		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...,
+		),
+	}
+}
+
+func (S *rolesServer) 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 *rolesServer) 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 *rolesServer) 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 *rolesServer) 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 *rolesServer) 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/roles/transport/server.microgen.go b/pkg/roles/transport/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..119aae999a13bab884d4f98d09346b1ef2d13071
--- /dev/null
+++ b/pkg/roles/transport/server.microgen.go
@@ -0,0 +1,60 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+
+	roles "git.perx.ru/perxis/perxis-go/pkg/roles"
+	endpoint "github.com/go-kit/kit/endpoint"
+)
+
+func Endpoints(svc roles.Roles) EndpointsSet {
+	return EndpointsSet{
+		CreateEndpoint: CreateEndpoint(svc),
+		DeleteEndpoint: DeleteEndpoint(svc),
+		GetEndpoint:    GetEndpoint(svc),
+		ListEndpoint:   ListEndpoint(svc),
+		UpdateEndpoint: UpdateEndpoint(svc),
+	}
+}
+
+func CreateEndpoint(svc roles.Roles) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*CreateRequest)
+		res0, res1 := svc.Create(arg0, req.Role)
+		return &CreateResponse{Created: res0}, res1
+	}
+}
+
+func GetEndpoint(svc roles.Roles) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*GetRequest)
+		res0, res1 := svc.Get(arg0, req.SpaceId, req.RoleId)
+		return &GetResponse{Role: res0}, res1
+	}
+}
+
+func ListEndpoint(svc roles.Roles) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*ListRequest)
+		res0, res1 := svc.List(arg0, req.SpaceId)
+		return &ListResponse{Roles: res0}, res1
+	}
+}
+
+func UpdateEndpoint(svc roles.Roles) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*UpdateRequest)
+		res0 := svc.Update(arg0, req.Role)
+		return &UpdateResponse{}, res0
+	}
+}
+
+func DeleteEndpoint(svc roles.Roles) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*DeleteRequest)
+		res0 := svc.Delete(arg0, req.SpaceId, req.RoleId)
+		return &DeleteResponse{}, res0
+	}
+}