diff --git a/pkg/clients/client.go b/pkg/clients/client.go
new file mode 100644
index 0000000000000000000000000000000000000000..f38b5acc9b442be39139523bcd316947d5d54fe7
--- /dev/null
+++ b/pkg/clients/client.go
@@ -0,0 +1,87 @@
+package clients
+
+// Client - приложение имеющее доступ к API
+type Client struct {
+	// Внутренний идентификатор клиента внутри системы
+	ID string `json:"id" bson:"_id"`
+
+	// Идентификатор пространства
+	SpaceID string `json:"space_id" bson:"-"`
+
+	// Имя приложения (обязательное поле)
+	Name string `json:"name" bson:"name"`
+
+	// Параметры аутентификации клиента
+	OAuth  *OAuth  `json:"oauth,omitempty" bson:"oauth,omitempty"`
+	TLS    *TLS    `json:"tls,omitempty" bson:"tls,omitempty"`
+	APIKey *APIKey `json:"api_key,omitempty" bson:"api_key,omitempty"`
+
+	// Описание клиента, назначение
+	Description string `json:"description" bson:"description"`
+
+	// Приложение отключено и не может авторизоваться
+	Disabled *bool `json:"disabled,omitempty" bson:"disabled,omitempty"`
+
+	// Роль приложения в пространстве
+	RoleID string `json:"role_id" bson:"role_id"`
+}
+
+type OAuth struct {
+	ClientID     string `bson:"client_id,omitempty" json:"client_id,omitempty"`         // Идентификатор клиента выданные IdP сервером, используется для идентификации клиента
+	AuthID       string `bson:"auth_id,omitempty" json:"auth_id,omitempty"`             // Сервис, который используется для авторизации клиента
+	TokenURL     string `bson:"token_url,omitempty" json:"token_url,omitempty"`         // URL для получения/обновления access token клиента (опционально)
+	ClientSecret string `bson:"client_secret,omitempty" json:"client_secret,omitempty"` // Секретный Ключ клиента, используется для идентификации клиента (опционально)
+}
+
+type APIKey struct {
+	Key    string `bson:"key,omitempty" json:"key,omitempty"`
+	Rotate bool   `bson:"-" json:"rotate,omitempty"`
+}
+
+type TLS struct {
+	Subject string `json:"subject,omitempty"`
+	CACert  string `json:"ca_cert,omitempty"`
+	Cert    string `json:"cert,omitempty"`
+	Key     string `json:"key,omitempty"`
+}
+
+func (c *Client) SetDisabled(b bool) *Client {
+	c.Disabled = &b
+	return c
+}
+
+func (c *Client) IsDisabled() bool {
+	if c.Disabled != nil && *c.Disabled {
+		return true
+	}
+	return false
+}
+
+func (c Client) Clone() *Client {
+	clone := &Client{
+		ID:          c.ID,
+		SpaceID:     c.SpaceID,
+		Name:        c.Name,
+		Description: c.Description,
+		RoleID:      c.RoleID,
+	}
+
+	if c.OAuth != nil {
+		temp := *c.OAuth
+		clone.OAuth = &temp
+	}
+	if c.TLS != nil {
+		temp := *c.TLS
+		clone.TLS = &temp
+	}
+	if c.APIKey != nil {
+		temp := *c.APIKey
+		clone.APIKey = &temp
+	}
+	if c.Disabled != nil {
+		temp := *c.Disabled
+		clone.Disabled = &temp
+	}
+
+	return clone
+}
diff --git a/pkg/clients/mocks/Clients.go b/pkg/clients/mocks/Clients.go
new file mode 100644
index 0000000000000000000000000000000000000000..bfeb7e946a1fe50d044479ba785aac15f484ac31
--- /dev/null
+++ b/pkg/clients/mocks/Clients.go
@@ -0,0 +1,149 @@
+// Code generated by mockery v2.7.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	clients "git.perx.ru/perxis/perxis-go/pkg/clients"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Clients is an autogenerated mock type for the Clients type
+type Clients struct {
+	mock.Mock
+}
+
+// Create provides a mock function with given fields: ctx, client
+func (_m *Clients) Create(ctx context.Context, client *clients.Client) (*clients.Client, error) {
+	ret := _m.Called(ctx, client)
+
+	var r0 *clients.Client
+	if rf, ok := ret.Get(0).(func(context.Context, *clients.Client) *clients.Client); ok {
+		r0 = rf(ctx, client)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*clients.Client)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *clients.Client) error); ok {
+		r1 = rf(ctx, client)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Delete provides a mock function with given fields: ctx, spaceId, id
+func (_m *Clients) Delete(ctx context.Context, spaceId string, id string) error {
+	ret := _m.Called(ctx, spaceId, id)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
+		r0 = rf(ctx, spaceId, id)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Enable provides a mock function with given fields: ctx, spaceId, id, enable
+func (_m *Clients) Enable(ctx context.Context, spaceId string, id string, enable bool) error {
+	ret := _m.Called(ctx, spaceId, id, enable)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string, bool) error); ok {
+		r0 = rf(ctx, spaceId, id, enable)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Get provides a mock function with given fields: ctx, spaceId, id
+func (_m *Clients) Get(ctx context.Context, spaceId string, id string) (*clients.Client, error) {
+	ret := _m.Called(ctx, spaceId, id)
+
+	var r0 *clients.Client
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) *clients.Client); ok {
+		r0 = rf(ctx, spaceId, id)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*clients.Client)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string, string) error); ok {
+		r1 = rf(ctx, spaceId, id)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetBy provides a mock function with given fields: ctx, spaceId, params
+func (_m *Clients) GetBy(ctx context.Context, spaceId string, params *clients.GetByParams) (*clients.Client, error) {
+	ret := _m.Called(ctx, spaceId, params)
+
+	var r0 *clients.Client
+	if rf, ok := ret.Get(0).(func(context.Context, string, *clients.GetByParams) *clients.Client); ok {
+		r0 = rf(ctx, spaceId, params)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*clients.Client)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string, *clients.GetByParams) error); ok {
+		r1 = rf(ctx, spaceId, params)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// List provides a mock function with given fields: ctx, spaceId
+func (_m *Clients) List(ctx context.Context, spaceId string) ([]*clients.Client, error) {
+	ret := _m.Called(ctx, spaceId)
+
+	var r0 []*clients.Client
+	if rf, ok := ret.Get(0).(func(context.Context, string) []*clients.Client); ok {
+		r0 = rf(ctx, spaceId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*clients.Client)
+		}
+	}
+
+	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, client
+func (_m *Clients) Update(ctx context.Context, client *clients.Client) error {
+	ret := _m.Called(ctx, client)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *clients.Client) error); ok {
+		r0 = rf(ctx, client)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
diff --git a/pkg/clients/service.go b/pkg/clients/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..823de82bdbe720e2a9ff24f069cb94458bf1cf25
--- /dev/null
+++ b/pkg/clients/service.go
@@ -0,0 +1,37 @@
+package clients
+
+import (
+	"context"
+)
+
+type GetByParams struct {
+	OAuthClientID string `json:"oauth_client_id,omitempty"`
+	APIKey        string `json:"api_key,omitempty"`
+	TLSSubject    string `json:"tls_subject,omitempty"`
+}
+
+// @microgen grpc, recovering, middleware
+// @protobuf git.perx.ru/perxis/perxis-go/proto/clients
+// @grpc-addr content.clients.Clients
+type Clients interface {
+	// Create - создает клиента (приложение) для работы с API
+	Create(ctx context.Context, client *Client) (created *Client, err error)
+
+	// Get - возвращает клиента по id
+	Get(ctx context.Context, spaceId, id string) (client *Client, err error)
+
+	// GetBy - возвращает клиента по идентификатору системы авторизации
+	GetBy(ctx context.Context, spaceId string, params *GetByParams) (client *Client, err error)
+
+	// List - возвращает список клиентов созданных в пространстве
+	List(ctx context.Context, spaceId string) (clients []*Client, err error)
+
+	// Update - обновляет параметры клиента
+	Update(ctx context.Context, client *Client) (err error)
+
+	// Delete - удаляет указанного клиента из пространстве
+	Delete(ctx context.Context, spaceId, id string) (err error)
+
+	// Enable - активирует/деактивирует клиента. Клиент не сможет обращаться к API платформы
+	Enable(ctx context.Context, spaceId, id string, enable bool) (err error)
+}
diff --git a/pkg/clients/transport/client.microgen.go b/pkg/clients/transport/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..633855af4d1f7fe74260ec7b4ece23db6063ebfb
--- /dev/null
+++ b/pkg/clients/transport/client.microgen.go
@@ -0,0 +1,108 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+	"errors"
+	clients "git.perx.ru/perxis/perxis-go/pkg/clients"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+func (set EndpointsSet) Create(arg0 context.Context, arg1 *clients.Client) (res0 *clients.Client, res1 error) {
+	request := CreateRequest{Client: 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 *clients.Client, res1 error) {
+	request := GetRequest{
+		Id:      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).Client, res1
+}
+
+func (set EndpointsSet) GetBy(arg0 context.Context, arg1 string, arg2 *clients.GetByParams) (res0 *clients.Client, res1 error) {
+	request := GetByRequest{
+		Config:  arg2,
+		SpaceId: arg1,
+	}
+	response, res1 := set.GetByEndpoint(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.(*GetByResponse).Client, res1
+}
+
+func (set EndpointsSet) List(arg0 context.Context, arg1 string) (res0 []*clients.Client, 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).Clients, res1
+}
+
+func (set EndpointsSet) Update(arg0 context.Context, arg1 *clients.Client) (res0 error) {
+	request := UpdateRequest{Client: 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{
+		Id:      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) Enable(arg0 context.Context, arg1 string, arg2 string, arg3 bool) (res0 error) {
+	request := EnableRequest{
+		Enable:  arg3,
+		Id:      arg2,
+		SpaceId: arg1,
+	}
+	_, res0 = set.EnableEndpoint(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/clients/transport/endpoints.microgen.go b/pkg/clients/transport/endpoints.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..bf73c784e68eb95f1dc631184046a4016bcca7a1
--- /dev/null
+++ b/pkg/clients/transport/endpoints.microgen.go
@@ -0,0 +1,16 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import endpoint "github.com/go-kit/kit/endpoint"
+
+// EndpointsSet implements Clients API and used for transport purposes.
+type EndpointsSet struct {
+	CreateEndpoint endpoint.Endpoint
+	GetEndpoint    endpoint.Endpoint
+	GetByEndpoint  endpoint.Endpoint
+	ListEndpoint   endpoint.Endpoint
+	UpdateEndpoint endpoint.Endpoint
+	DeleteEndpoint endpoint.Endpoint
+	EnableEndpoint endpoint.Endpoint
+}
diff --git a/pkg/clients/transport/exchanges.microgen.go b/pkg/clients/transport/exchanges.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..2a1a8e65967661b117a20ff62fc6e20837afce2b
--- /dev/null
+++ b/pkg/clients/transport/exchanges.microgen.go
@@ -0,0 +1,58 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import clients "git.perx.ru/perxis/perxis-go/pkg/clients"
+
+type (
+	CreateRequest struct {
+		Client *clients.Client `json:"client"`
+	}
+	CreateResponse struct {
+		Created *clients.Client `json:"created"`
+	}
+
+	GetRequest struct {
+		SpaceId string `json:"space_id"`
+		Id      string `json:"id"`
+	}
+	GetResponse struct {
+		Client *clients.Client `json:"client"`
+	}
+
+	GetByRequest struct {
+		SpaceId string               `json:"space_id"`
+		Config  *clients.GetByParams `json:"config"`
+	}
+	GetByResponse struct {
+		Client *clients.Client `json:"client"`
+	}
+
+	ListRequest struct {
+		SpaceId string `json:"space_id"`
+	}
+	ListResponse struct {
+		Clients []*clients.Client `json:"clients"`
+	}
+
+	UpdateRequest struct {
+		Client *clients.Client `json:"client"`
+	}
+	// Formal exchange type, please do not delete.
+	UpdateResponse struct{}
+
+	DeleteRequest struct {
+		SpaceId string `json:"space_id"`
+		Id      string `json:"id"`
+	}
+	// Formal exchange type, please do not delete.
+	DeleteResponse struct{}
+
+	EnableRequest struct {
+		SpaceId string `json:"space_id"`
+		Id      string `json:"id"`
+		Enable  bool   `json:"enable"`
+	}
+	// Formal exchange type, please do not delete.
+	EnableResponse struct{}
+)
diff --git a/pkg/clients/transport/grpc/client.microgen.go b/pkg/clients/transport/grpc/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..421a0178c29b691c33085761bc1710e3c4cbe4eb
--- /dev/null
+++ b/pkg/clients/transport/grpc/client.microgen.go
@@ -0,0 +1,68 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/clients/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/clients"
+	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.clients.Clients"
+	}
+	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(),
+		EnableEndpoint: grpckit.NewClient(
+			conn, addr, "Enable",
+			_Encode_Enable_Request,
+			_Decode_Enable_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+		GetByEndpoint: grpckit.NewClient(
+			conn, addr, "GetBy",
+			_Encode_GetBy_Request,
+			_Decode_GetBy_Response,
+			pb.GetByResponse{},
+			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/clients/transport/grpc/protobuf_endpoint_converters.microgen.go b/pkg/clients/transport/grpc/protobuf_endpoint_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..fea7765facc0f5ba2c6f07a5c7016d2e326a5d23
--- /dev/null
+++ b/pkg/clients/transport/grpc/protobuf_endpoint_converters.microgen.go
@@ -0,0 +1,295 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// Please, do not change functions names!
+package transportgrpc
+
+import (
+	"context"
+	"errors"
+
+	"git.perx.ru/perxis/perxis-go/pkg/clients"
+	transport "git.perx.ru/perxis/perxis-go/pkg/clients/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/clients"
+	empty "github.com/golang/protobuf/ptypes/empty"
+)
+
+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{
+		Id:      req.Id,
+		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_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{
+		Id:      req.Id,
+		SpaceId: req.SpaceId,
+	}, nil
+}
+
+func _Encode_Enable_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil EnableRequest")
+	}
+	req := request.(*transport.EnableRequest)
+	return &pb.EnableRequest{
+		Enable:  req.Enable,
+		Id:      req.Id,
+		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)
+	respClient, err := PtrClientToProto(resp.Client)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.GetResponse{Client: respClient}, 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)
+	respClients, err := ListPtrClientToProto(resp.Clients)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.ListResponse{Clients: respClients}, 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_Enable_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, 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{
+		Id:      string(req.Id),
+		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_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{
+		Id:      string(req.Id),
+		SpaceId: string(req.SpaceId),
+	}, nil
+}
+
+func _Decode_Enable_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil EnableRequest")
+	}
+	req := request.(*pb.EnableRequest)
+	return &transport.EnableRequest{
+		Enable:  bool(req.Enable),
+		Id:      string(req.Id),
+		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)
+	respClient, err := ProtoToPtrClient(resp.Client)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.GetResponse{Client: respClient}, 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)
+	respClients, err := ProtoToListPtrClient(resp.Clients)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.ListResponse{Clients: respClients}, 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_Enable_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_Create_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil CreateRequest")
+	}
+	req := request.(*transport.CreateRequest)
+	reqClient, err := PtrClientToProto(req.Client)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateRequest{Client: reqClient}, 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)
+	reqClient, err := PtrClientToProto(req.Client)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.UpdateRequest{Client: reqClient}, 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 := PtrClientToProto(resp.Created)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateResponse{Created: respCreated}, 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)
+	reqClient, err := ProtoToPtrClient(req.Client)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateRequest{Client: reqClient}, 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)
+	reqClient, err := ProtoToPtrClient(req.Client)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.UpdateRequest{Client: reqClient}, 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 := ProtoToPtrClient(resp.Created)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateResponse{Created: respCreated}, nil
+}
+
+func _Encode_GetBy_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetByRequest")
+	}
+	req := request.(*transport.GetByRequest)
+	pbreq := &pb.GetByRequest{SpaceId: req.SpaceId}
+	if req != nil && req.Config != nil {
+		pbreq.ApiKey = req.Config.APIKey
+		pbreq.TlsSubject = req.Config.TLSSubject
+		pbreq.OauthClientId = req.Config.OAuthClientID
+	}
+	return pbreq, nil
+}
+
+func _Encode_GetBy_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetByResponse")
+	}
+	resp := response.(*transport.GetByResponse)
+	respClient, err := PtrClientToProto(resp.Client)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.GetByResponse{Client: respClient}, nil
+}
+
+func _Decode_GetBy_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetByRequest")
+	}
+	req := request.(*pb.GetByRequest)
+	return &transport.GetByRequest{
+		Config: &clients.GetByParams{
+			OAuthClientID: req.OauthClientId,
+			APIKey:        req.ApiKey,
+			TLSSubject:    req.TlsSubject,
+		},
+		SpaceId: string(req.SpaceId),
+	}, nil
+}
+
+func _Decode_GetBy_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetByResponse")
+	}
+	resp := response.(*pb.GetByResponse)
+	respClient, err := ProtoToPtrClient(resp.Client)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.GetByResponse{Client: respClient}, nil
+}
diff --git a/pkg/clients/transport/grpc/protobuf_type_converters.microgen.go b/pkg/clients/transport/grpc/protobuf_type_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..5212c0b5d2dc9c96533f5f64aee9ed58ab241b41
--- /dev/null
+++ b/pkg/clients/transport/grpc/protobuf_type_converters.microgen.go
@@ -0,0 +1,164 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// It is better for you if you do not change functions names!
+// This file will never be overwritten.
+package transportgrpc
+
+import (
+	service "git.perx.ru/perxis/perxis-go/pkg/clients"
+	permission "git.perx.ru/perxis/perxis-go/pkg/permission"
+	pb "git.perx.ru/perxis/perxis-go/proto/clients"
+	commonpb "git.perx.ru/perxis/perxis-go/proto/common"
+)
+
+func ListStringToProto(environments []string) ([]string, error) {
+	return environments, nil
+}
+
+func ProtoToListString(protoEnvironments []string) ([]string, error) {
+	return protoEnvironments, nil
+}
+
+func PtrClientToProto(client *service.Client) (*pb.Client, error) {
+	if client == nil {
+		return nil, nil
+	}
+
+	var oauth *pb.Client_OAuth
+	var tls *pb.Client_TLS
+	var apikey *pb.Client_APIKey
+
+	if client.OAuth != nil {
+		oauth = &pb.Client_OAuth{
+			ClientId:     client.OAuth.ClientID,
+			AuthId:       client.OAuth.AuthID,
+			TokenUrl:     client.OAuth.TokenURL,
+			ClientSecret: client.OAuth.ClientSecret,
+		}
+	}
+	if client.TLS != nil {
+		tls = &pb.Client_TLS{
+			Subject: client.TLS.Subject,
+		}
+	}
+	if client.APIKey != nil {
+		apikey = &pb.Client_APIKey{
+			Key:    client.APIKey.Key,
+			Rotate: client.APIKey.Rotate,
+		}
+	}
+
+	return &pb.Client{
+		Id:          client.ID,
+		SpaceId:     client.SpaceID,
+		Name:        client.Name,
+		Description: client.Description,
+		Disabled:    client.Disabled,
+		RoleId:      client.RoleID,
+		//Environments: client.Environments,
+		//Rules:        rules,
+		Oauth:  oauth,
+		Tls:    tls,
+		ApiKey: apikey,
+	}, nil
+}
+
+func ProtoToPtrClient(protoClient *pb.Client) (*service.Client, error) {
+	if protoClient == nil {
+		return nil, nil
+	}
+
+	var oauth *service.OAuth
+	var tls *service.TLS
+	var apikey *service.APIKey
+
+	if protoClient.Oauth != nil {
+		oauth = &service.OAuth{
+			ClientID:     protoClient.Oauth.ClientId,
+			AuthID:       protoClient.Oauth.AuthId,
+			TokenURL:     protoClient.Oauth.TokenUrl,
+			ClientSecret: protoClient.Oauth.ClientSecret,
+		}
+	}
+	if protoClient.Tls != nil {
+		tls = &service.TLS{
+			Subject: protoClient.Tls.Subject,
+		}
+	}
+	if protoClient.ApiKey != nil {
+		apikey = &service.APIKey{
+			Key:    protoClient.ApiKey.Key,
+			Rotate: protoClient.ApiKey.Rotate,
+		}
+	}
+
+	return &service.Client{
+		ID:          protoClient.Id,
+		SpaceID:     protoClient.SpaceId,
+		Name:        protoClient.Name,
+		Description: protoClient.Description,
+		Disabled:    protoClient.Disabled,
+		RoleID:      protoClient.RoleId,
+		OAuth:       oauth,
+		TLS:         tls,
+		APIKey:      apikey,
+	}, nil
+}
+
+func ListPtrClientToProto(clients []*service.Client) ([]*pb.Client, error) {
+	protoClients := make([]*pb.Client, 0, len(clients))
+	for _, c := range clients {
+		protoClient, _ := PtrClientToProto(c)
+		protoClients = append(protoClients, protoClient)
+	}
+	return protoClients, nil
+}
+
+func ProtoToListPtrClient(protoClients []*pb.Client) ([]*service.Client, error) {
+	clients := make([]*service.Client, 0, len(protoClients))
+	for _, c := range protoClients {
+		client, _ := ProtoToPtrClient(c)
+		clients = append(clients, client)
+	}
+	return clients, 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/clients/transport/grpc/server.microgen.go b/pkg/clients/transport/grpc/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..7408e691dd4fb1e8e772ffe9e530fcd112d02729
--- /dev/null
+++ b/pkg/clients/transport/grpc/server.microgen.go
@@ -0,0 +1,127 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// DO NOT EDIT.
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/clients/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/clients"
+	grpc "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	context "golang.org/x/net/context"
+)
+
+type clientsServer struct {
+	create grpc.Handler
+	get    grpc.Handler
+	getBy  grpc.Handler
+	list   grpc.Handler
+	update grpc.Handler
+	delete grpc.Handler
+	enable grpc.Handler
+
+	pb.UnimplementedClientsServer
+}
+
+func NewGRPCServer(endpoints *transport.EndpointsSet, opts ...grpc.ServerOption) pb.ClientsServer {
+	return &clientsServer{
+		create: grpc.NewServer(
+			endpoints.CreateEndpoint,
+			_Decode_Create_Request,
+			_Encode_Create_Response,
+			opts...,
+		),
+		delete: grpc.NewServer(
+			endpoints.DeleteEndpoint,
+			_Decode_Delete_Request,
+			_Encode_Delete_Response,
+			opts...,
+		),
+		enable: grpc.NewServer(
+			endpoints.EnableEndpoint,
+			_Decode_Enable_Request,
+			_Encode_Enable_Response,
+			opts...,
+		),
+		get: grpc.NewServer(
+			endpoints.GetEndpoint,
+			_Decode_Get_Request,
+			_Encode_Get_Response,
+			opts...,
+		),
+		getBy: grpc.NewServer(
+			endpoints.GetByEndpoint,
+			_Decode_GetBy_Request,
+			_Encode_GetBy_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 *clientsServer) 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 *clientsServer) 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 *clientsServer) GetBy(ctx context.Context, req *pb.GetByRequest) (*pb.GetByResponse, error) {
+	_, resp, err := S.getBy.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.GetByResponse), nil
+}
+
+func (S *clientsServer) 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 *clientsServer) 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 *clientsServer) 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 *clientsServer) Enable(ctx context.Context, req *pb.EnableRequest) (*empty.Empty, error) {
+	_, resp, err := S.enable.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
diff --git a/pkg/clients/transport/server.microgen.go b/pkg/clients/transport/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..4c8b6a3e538dd5d1f28c3e7fd2b9f56af54cae8e
--- /dev/null
+++ b/pkg/clients/transport/server.microgen.go
@@ -0,0 +1,77 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+	clients "git.perx.ru/perxis/perxis-go/pkg/clients"
+	endpoint "github.com/go-kit/kit/endpoint"
+)
+
+func Endpoints(svc clients.Clients) EndpointsSet {
+	return EndpointsSet{
+		CreateEndpoint: CreateEndpoint(svc),
+		DeleteEndpoint: DeleteEndpoint(svc),
+		EnableEndpoint: EnableEndpoint(svc),
+		GetByEndpoint:  GetByEndpoint(svc),
+		GetEndpoint:    GetEndpoint(svc),
+		ListEndpoint:   ListEndpoint(svc),
+		UpdateEndpoint: UpdateEndpoint(svc),
+	}
+}
+
+func CreateEndpoint(svc clients.Clients) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*CreateRequest)
+		res0, res1 := svc.Create(arg0, req.Client)
+		return &CreateResponse{Created: res0}, res1
+	}
+}
+
+func GetEndpoint(svc clients.Clients) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*GetRequest)
+		res0, res1 := svc.Get(arg0, req.SpaceId, req.Id)
+		return &GetResponse{Client: res0}, res1
+	}
+}
+
+func GetByEndpoint(svc clients.Clients) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*GetByRequest)
+		res0, res1 := svc.GetBy(arg0, req.SpaceId, req.Config)
+		return &GetByResponse{Client: res0}, res1
+	}
+}
+
+func ListEndpoint(svc clients.Clients) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*ListRequest)
+		res0, res1 := svc.List(arg0, req.SpaceId)
+		return &ListResponse{Clients: res0}, res1
+	}
+}
+
+func UpdateEndpoint(svc clients.Clients) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*UpdateRequest)
+		res0 := svc.Update(arg0, req.Client)
+		return &UpdateResponse{}, res0
+	}
+}
+
+func DeleteEndpoint(svc clients.Clients) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*DeleteRequest)
+		res0 := svc.Delete(arg0, req.SpaceId, req.Id)
+		return &DeleteResponse{}, res0
+	}
+}
+
+func EnableEndpoint(svc clients.Clients) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*EnableRequest)
+		res0 := svc.Enable(arg0, req.SpaceId, req.Id, req.Enable)
+		return &EnableResponse{}, res0
+	}
+}
diff --git a/pkg/locales/locale.go b/pkg/locales/locale.go
new file mode 100644
index 0000000000000000000000000000000000000000..d3cc43c6c625a5090d975409d7aa2beb16eefc3a
--- /dev/null
+++ b/pkg/locales/locale.go
@@ -0,0 +1,7 @@
+package locales
+
+type Locale struct {
+	ID      string `json:"id" bson:"_id"` // (Пример: "en", "en-US")
+	SpaceID string `json:"spaceId" bson:"-"`
+	Name    string `json:"name" bson:"name"` // (Пример: "English", "English (US)" )
+}
diff --git a/pkg/locales/mocks/Locales.go b/pkg/locales/mocks/Locales.go
new file mode 100644
index 0000000000000000000000000000000000000000..3e63dfedfff975f52f886d19e1e8982bccb96b7e
--- /dev/null
+++ b/pkg/locales/mocks/Locales.go
@@ -0,0 +1,75 @@
+// Code generated by mockery v2.7.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	locales "git.perx.ru/perxis/perxis-go/pkg/locales"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Locales is an autogenerated mock type for the Locales type
+type Locales struct {
+	mock.Mock
+}
+
+// Create provides a mock function with given fields: ctx, locale
+func (_m *Locales) Create(ctx context.Context, locale *locales.Locale) (*locales.Locale, error) {
+	ret := _m.Called(ctx, locale)
+
+	var r0 *locales.Locale
+	if rf, ok := ret.Get(0).(func(context.Context, *locales.Locale) *locales.Locale); ok {
+		r0 = rf(ctx, locale)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*locales.Locale)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *locales.Locale) error); ok {
+		r1 = rf(ctx, locale)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Delete provides a mock function with given fields: ctx, spaceId, localeId
+func (_m *Locales) Delete(ctx context.Context, spaceId string, localeId string) error {
+	ret := _m.Called(ctx, spaceId, localeId)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
+		r0 = rf(ctx, spaceId, localeId)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// List provides a mock function with given fields: ctx, spaceId
+func (_m *Locales) List(ctx context.Context, spaceId string) ([]*locales.Locale, error) {
+	ret := _m.Called(ctx, spaceId)
+
+	var r0 []*locales.Locale
+	if rf, ok := ret.Get(0).(func(context.Context, string) []*locales.Locale); ok {
+		r0 = rf(ctx, spaceId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*locales.Locale)
+		}
+	}
+
+	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
+}
diff --git a/pkg/locales/service.go b/pkg/locales/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..7724d7f7ecdb2f4733d97bff01113d105adabdeb
--- /dev/null
+++ b/pkg/locales/service.go
@@ -0,0 +1,14 @@
+package locales
+
+import (
+	"context"
+)
+
+// @microgen grpc
+// @protobuf git.perx.ru/perxis/perxis-go/proto/locales
+// @grpc-addr content.locales.Locales
+type Locales interface {
+	Create(ctx context.Context, locale *Locale) (created *Locale, err error)
+	List(ctx context.Context, spaceId string) (locales []*Locale, err error)
+	Delete(ctx context.Context, spaceId, localeId string) (err error)
+}
diff --git a/pkg/locales/transport/client.microgen.go b/pkg/locales/transport/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..f8cd9dee23dafeff1d635b3f5ee42f6e5c8667f1
--- /dev/null
+++ b/pkg/locales/transport/client.microgen.go
@@ -0,0 +1,51 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+	"errors"
+
+	locales "git.perx.ru/perxis/perxis-go/pkg/locales"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+func (set EndpointsSet) Create(arg0 context.Context, arg1 *locales.Locale) (res0 *locales.Locale, res1 error) {
+	request := CreateRequest{Locale: 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) List(arg0 context.Context, arg1 string) (res0 []*locales.Locale, 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).Locales, res1
+}
+
+func (set EndpointsSet) Delete(arg0 context.Context, arg1 string, arg2 string) (res0 error) {
+	request := DeleteRequest{
+		LocaleId: 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/locales/transport/endpoints.microgen.go b/pkg/locales/transport/endpoints.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..ffca7318747104f8b58af332e90646f8cc6a8b9c
--- /dev/null
+++ b/pkg/locales/transport/endpoints.microgen.go
@@ -0,0 +1,12 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import endpoint "github.com/go-kit/kit/endpoint"
+
+// EndpointsSet implements Locales API and used for transport purposes.
+type EndpointsSet struct {
+	CreateEndpoint endpoint.Endpoint
+	ListEndpoint   endpoint.Endpoint
+	DeleteEndpoint endpoint.Endpoint
+}
diff --git a/pkg/locales/transport/exchanges.microgen.go b/pkg/locales/transport/exchanges.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..a07204e13a233fd7bae40c74e9984061871a92b7
--- /dev/null
+++ b/pkg/locales/transport/exchanges.microgen.go
@@ -0,0 +1,28 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import locales "git.perx.ru/perxis/perxis-go/pkg/locales"
+
+type (
+	CreateRequest struct {
+		Locale *locales.Locale `json:"locale"`
+	}
+	CreateResponse struct {
+		Created *locales.Locale `json:"created"`
+	}
+
+	ListRequest struct {
+		SpaceId string `json:"space_id"`
+	}
+	ListResponse struct {
+		Locales []*locales.Locale `json:"locales"`
+	}
+
+	DeleteRequest struct {
+		SpaceId  string `json:"space_id"`
+		LocaleId string `json:"locale_id"`
+	}
+	// Formal exchange type, please do not delete.
+	DeleteResponse struct{}
+)
diff --git a/pkg/locales/transport/grpc/client.microgen.go b/pkg/locales/transport/grpc/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..3af5bbae0ed86bf079d93622cc8f1aa4f7fbdfd8
--- /dev/null
+++ b/pkg/locales/transport/grpc/client.microgen.go
@@ -0,0 +1,40 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/locales/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/locales"
+	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.locales.Locales"
+	}
+	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(),
+		ListEndpoint: grpckit.NewClient(
+			conn, addr, "List",
+			_Encode_List_Request,
+			_Decode_List_Response,
+			pb.ListResponse{},
+			opts...,
+		).Endpoint(),
+	}
+}
diff --git a/pkg/locales/transport/grpc/protobuf_endpoint_converters.microgen.go b/pkg/locales/transport/grpc/protobuf_endpoint_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..5d06e8232c89f226e4f7c0a80ca6d52eb219d48f
--- /dev/null
+++ b/pkg/locales/transport/grpc/protobuf_endpoint_converters.microgen.go
@@ -0,0 +1,131 @@
+// 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/locales/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/locales"
+	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)
+	pbLocale, err := PtrLocaleToProto(req.Locale)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateRequest{Locale: pbLocale}, 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_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{
+		LocaleId: req.LocaleId,
+		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)
+	respLocale, err := PtrLocaleToProto(resp.Created)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateResponse{Locale: respLocale}, 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)
+	respLocales, err := ListPtrLocaleToProto(resp.Locales)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.ListResponse{Locales: respLocales}, 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)
+	locale, err := ProtoToPtrLocale(req.Locale)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateRequest{Locale: locale}, 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_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{
+		LocaleId: string(req.LocaleId),
+		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)
+	respLocale, err := ProtoToPtrLocale(resp.Locale)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateResponse{Created: respLocale}, 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)
+	respLocales, err := ProtoToListPtrLocale(resp.Locales)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.ListResponse{Locales: respLocales}, nil
+}
+
+func _Decode_Delete_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
diff --git a/pkg/locales/transport/grpc/protobuf_type_converters.microgen.go b/pkg/locales/transport/grpc/protobuf_type_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..6dca0bb8afa68dfe3fa17b53d315f3716c48ae36
--- /dev/null
+++ b/pkg/locales/transport/grpc/protobuf_type_converters.microgen.go
@@ -0,0 +1,48 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// It is better for you if you do not change functions names!
+// This file will never be overwritten.
+package transportgrpc
+
+import (
+	service "git.perx.ru/perxis/perxis-go/pkg/locales"
+	pb "git.perx.ru/perxis/perxis-go/proto/locales"
+)
+
+func PtrLocaleToProto(locale *service.Locale) (*pb.Locale, error) {
+	if locale == nil {
+		return nil, nil
+	}
+	return &pb.Locale{Id: locale.ID, Name: locale.Name, SpaceId: locale.SpaceID}, nil
+}
+
+func ProtoToPtrLocale(protoLocale *pb.Locale) (*service.Locale, error) {
+	if protoLocale == nil {
+		return nil, nil
+	}
+	return &service.Locale{ID: protoLocale.Id, Name: protoLocale.Name, SpaceID: protoLocale.SpaceId}, nil
+}
+
+func ListPtrLocaleToProto(locales []*service.Locale) ([]*pb.Locale, error) {
+	protoLocales := make([]*pb.Locale, 0, len(locales))
+	for _, l := range locales {
+		pl, err := PtrLocaleToProto(l)
+		if err != nil {
+			return nil, err
+		}
+		protoLocales = append(protoLocales, pl)
+	}
+	return protoLocales, nil
+}
+
+func ProtoToListPtrLocale(protoLocales []*pb.Locale) ([]*service.Locale, error) {
+	locales := make([]*service.Locale, 0, len(protoLocales))
+	for _, pl := range protoLocales {
+		l, err := ProtoToPtrLocale(pl)
+		if err != nil {
+			return nil, err
+		}
+		locales = append(locales, l)
+	}
+	return locales, nil
+}
diff --git a/pkg/locales/transport/grpc/server.microgen.go b/pkg/locales/transport/grpc/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..88e549f6e23e99a04d037fcefb4603a13821d24a
--- /dev/null
+++ b/pkg/locales/transport/grpc/server.microgen.go
@@ -0,0 +1,67 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// DO NOT EDIT.
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/locales/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/locales"
+	grpc "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	context "golang.org/x/net/context"
+)
+
+type localesServer struct {
+	create grpc.Handler
+	list   grpc.Handler
+	delete grpc.Handler
+
+	pb.UnimplementedLocalesServer
+}
+
+func NewGRPCServer(endpoints *transport.EndpointsSet, opts ...grpc.ServerOption) pb.LocalesServer {
+	return &localesServer{
+		create: grpc.NewServer(
+			endpoints.CreateEndpoint,
+			_Decode_Create_Request,
+			_Encode_Create_Response,
+			opts...,
+		),
+		delete: grpc.NewServer(
+			endpoints.DeleteEndpoint,
+			_Decode_Delete_Request,
+			_Encode_Delete_Response,
+			opts...,
+		),
+		list: grpc.NewServer(
+			endpoints.ListEndpoint,
+			_Decode_List_Request,
+			_Encode_List_Response,
+			opts...,
+		),
+	}
+}
+
+func (S *localesServer) 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 *localesServer) 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 *localesServer) 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/locales/transport/server.microgen.go b/pkg/locales/transport/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..5ce815dcb52415a93ddfeb095f99e22fb14c4492
--- /dev/null
+++ b/pkg/locales/transport/server.microgen.go
@@ -0,0 +1,42 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+
+	locales "git.perx.ru/perxis/perxis-go/pkg/locales"
+	endpoint "github.com/go-kit/kit/endpoint"
+)
+
+func Endpoints(svc locales.Locales) EndpointsSet {
+	return EndpointsSet{
+		CreateEndpoint: CreateEndpoint(svc),
+		DeleteEndpoint: DeleteEndpoint(svc),
+		ListEndpoint:   ListEndpoint(svc),
+	}
+}
+
+func CreateEndpoint(svc locales.Locales) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*CreateRequest)
+		res0, res1 := svc.Create(arg0, req.Locale)
+		return &CreateResponse{Created: res0}, res1
+	}
+}
+
+func ListEndpoint(svc locales.Locales) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*ListRequest)
+		res0, res1 := svc.List(arg0, req.SpaceId)
+		return &ListResponse{Locales: res0}, res1
+	}
+}
+
+func DeleteEndpoint(svc locales.Locales) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*DeleteRequest)
+		res0 := svc.Delete(arg0, req.SpaceId, req.LocaleId)
+		return &DeleteResponse{}, res0
+	}
+}
diff --git a/pkg/options/options.go b/pkg/options/options.go
new file mode 100644
index 0000000000000000000000000000000000000000..7f8b0cd624db7e6c4c35fe62333a67ba633fbe7e
--- /dev/null
+++ b/pkg/options/options.go
@@ -0,0 +1,122 @@
+package options
+
+import "time"
+
+// SortOptions настройки сортировки результатов
+type SortOptions struct {
+	Sort []string
+}
+
+// PaginationOptions настройки возвращаемых страниц результатов
+type PaginationOptions struct {
+	PageNum  int
+	PageSize int
+}
+
+// FieldOptions настройки включения/исключения полей из результатов запроса
+type FieldOptions struct {
+	// Fields - Наименования полей для включения/исключения из результатов запроса (только указанные поля)
+	// Если `ExcludeFields` не установлен, то результат содержит только указанные поля
+	// Если `ExcludeFields` установлен, то результат содержит все поля кроме указанных
+	Fields []string
+
+	// ExcludeFields- Если флаг установлен, то перечисленные поля `Fields` следует исключить из результатов
+	ExcludeFields bool
+}
+
+// FindOptions настройки возвращаемых результатов поиска
+type FindOptions struct {
+	SortOptions
+	PaginationOptions
+	FieldOptions
+}
+
+// NewFindOptions создает новые результаты поиска
+func NewFindOptions(pageNum, pageSize int, sort ...string) *FindOptions {
+	return &FindOptions{
+		PaginationOptions: PaginationOptions{
+			PageNum:  pageNum,
+			PageSize: pageSize,
+		},
+		SortOptions: SortOptions{
+			Sort: sort,
+		},
+	}
+}
+
+// MergeFindOptions объединяет в FindOptions различные варианты настроек
+func MergeFindOptions(opts ...interface{}) *FindOptions {
+	fo := &FindOptions{}
+	for _, opt := range opts {
+		if opt == nil {
+			continue
+		}
+
+		switch o := opt.(type) {
+		case FindOptions:
+			fo.SortOptions = MergeSortOptions(fo.SortOptions, o.SortOptions)
+			fo.PaginationOptions = MergePaginationOptions(fo.PaginationOptions, o.PaginationOptions)
+			fo.FieldOptions = MergeFieldOptions(fo.FieldOptions, o.FieldOptions)
+		case *FindOptions:
+			fo.SortOptions = MergeSortOptions(fo.SortOptions, o.SortOptions)
+			fo.PaginationOptions = MergePaginationOptions(fo.PaginationOptions, o.PaginationOptions)
+			fo.FieldOptions = MergeFieldOptions(fo.FieldOptions, o.FieldOptions)
+		case SortOptions:
+			fo.SortOptions = MergeSortOptions(fo.SortOptions, o)
+		case *SortOptions:
+			fo.SortOptions = MergeSortOptions(fo.SortOptions, *o)
+		case PaginationOptions:
+			fo.PaginationOptions = MergePaginationOptions(fo.PaginationOptions, o)
+		case *PaginationOptions:
+			fo.PaginationOptions = MergePaginationOptions(fo.PaginationOptions, *o)
+		case FieldOptions:
+			fo.FieldOptions = o
+		case *FieldOptions:
+			fo.FieldOptions = *o
+		}
+	}
+	return fo
+}
+
+type TimeFilter struct {
+	Before, After time.Time
+}
+
+// MergeSortOptions объединяет настройки сортировки
+func MergeSortOptions(options ...SortOptions) SortOptions {
+	fo := SortOptions{}
+	for _, opt := range options {
+		if len(opt.Sort) == 0 {
+			continue
+		}
+		fo.Sort = append(fo.Sort, opt.Sort...)
+	}
+	return fo
+}
+
+// MergePaginationOptions объединяет настройки страниц
+func MergePaginationOptions(options ...PaginationOptions) PaginationOptions {
+	fo := PaginationOptions{}
+	for _, opt := range options {
+		if opt.PageSize == 0 && opt.PageNum == 0 {
+			continue
+		}
+		fo.PageNum = opt.PageNum
+		fo.PageSize = opt.PageSize
+	}
+	return fo
+}
+
+// MergeFieldOptions выполняет слияние опций для возвращаемых полей.
+// Выбирается не пустое значение.
+func MergeFieldOptions(options ...FieldOptions) FieldOptions {
+	fo := FieldOptions{}
+	for _, opt := range options {
+		if len(opt.Fields) > 0 {
+			fo.Fields = opt.Fields
+			fo.ExcludeFields = opt.ExcludeFields
+			return fo
+		}
+	}
+	return fo
+}
diff --git a/pkg/options/options_test.go b/pkg/options/options_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..981849a0e2625a8bd7e796a22eaa8529d606ef01
--- /dev/null
+++ b/pkg/options/options_test.go
@@ -0,0 +1,60 @@
+package options
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestOptions_MergePaginationOptions(t *testing.T) {
+
+	var tt = []struct {
+		name     string
+		options  []PaginationOptions
+		expected PaginationOptions
+	}{
+		{
+			name:     "Nil option",
+			options:  nil,
+			expected: PaginationOptions{},
+		},
+		{
+			name:     "Empty options",
+			options:  []PaginationOptions{},
+			expected: PaginationOptions{},
+		},
+		{
+			name:     "One option",
+			options:  []PaginationOptions{{PageNum: 10, PageSize: 100}},
+			expected: PaginationOptions{PageNum: 10, PageSize: 100},
+		},
+		{
+			name:     "Merge #1",
+			options:  []PaginationOptions{{PageNum: 0, PageSize: 0}, {PageNum: 10, PageSize: 100}},
+			expected: PaginationOptions{PageNum: 10, PageSize: 100},
+		},
+		{
+			name:     "Merge #2",
+			options:  []PaginationOptions{{PageNum: 10, PageSize: 100}, {PageNum: 0, PageSize: 0}},
+			expected: PaginationOptions{PageNum: 10, PageSize: 100},
+		},
+		{
+			name:     "Merge #3",
+			options:  []PaginationOptions{{PageNum: 0, PageSize: 0}, {PageNum: 10, PageSize: 100}, {PageNum: 0, PageSize: 0}},
+			expected: PaginationOptions{PageNum: 10, PageSize: 100},
+		},
+		{
+			name:     "Merge #4",
+			options:  []PaginationOptions{{PageNum: 10, PageSize: 100}, {}},
+			expected: PaginationOptions{PageNum: 10, PageSize: 100},
+		},
+	}
+
+	for _, v := range tt {
+
+		t.Run(v.name, func(t *testing.T) {
+			actual := MergePaginationOptions(v.options...)
+			assert.Equal(t, v.expected, actual)
+		})
+	}
+}
diff --git a/pkg/users/mocks/Users.go b/pkg/users/mocks/Users.go
new file mode 100644
index 0000000000000000000000000000000000000000..6e54f18c4dfc78d8473e5499262eb7d65c783e4d
--- /dev/null
+++ b/pkg/users/mocks/Users.go
@@ -0,0 +1,143 @@
+// Code generated by mockery v2.7.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	options "git.perx.ru/perxis/perxis-go/pkg/options"
+	users "git.perx.ru/perxis/perxis-go/pkg/users"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Users is an autogenerated mock type for the Users type
+type Users struct {
+	mock.Mock
+}
+
+// Create provides a mock function with given fields: ctx, create
+func (_m *Users) Create(ctx context.Context, create *users.User) (*users.User, error) {
+	ret := _m.Called(ctx, create)
+
+	var r0 *users.User
+	if rf, ok := ret.Get(0).(func(context.Context, *users.User) *users.User); ok {
+		r0 = rf(ctx, create)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*users.User)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *users.User) error); ok {
+		r1 = rf(ctx, create)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Delete provides a mock function with given fields: ctx, userId
+func (_m *Users) Delete(ctx context.Context, userId string) error {
+	ret := _m.Called(ctx, userId)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
+		r0 = rf(ctx, userId)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// Find provides a mock function with given fields: ctx, filter, options
+func (_m *Users) Find(ctx context.Context, filter *users.Filter, opts *options.FindOptions) ([]*users.User, int, error) {
+	ret := _m.Called(ctx, filter, opts)
+
+	var r0 []*users.User
+	if rf, ok := ret.Get(0).(func(context.Context, *users.Filter, *options.FindOptions) []*users.User); ok {
+		r0 = rf(ctx, filter, opts)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).([]*users.User)
+		}
+	}
+
+	var r1 int
+	if rf, ok := ret.Get(1).(func(context.Context, *users.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, *users.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, userId
+func (_m *Users) Get(ctx context.Context, userId string) (*users.User, error) {
+	ret := _m.Called(ctx, userId)
+
+	var r0 *users.User
+	if rf, ok := ret.Get(0).(func(context.Context, string) *users.User); ok {
+		r0 = rf(ctx, userId)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*users.User)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, userId)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// GetByIdentity provides a mock function with given fields: ctx, identity
+func (_m *Users) GetByIdentity(ctx context.Context, identity string) (*users.User, error) {
+	ret := _m.Called(ctx, identity)
+
+	var r0 *users.User
+	if rf, ok := ret.Get(0).(func(context.Context, string) *users.User); ok {
+		r0 = rf(ctx, identity)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*users.User)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
+		r1 = rf(ctx, identity)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Update provides a mock function with given fields: ctx, update
+func (_m *Users) Update(ctx context.Context, update *users.User) error {
+	ret := _m.Called(ctx, update)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *users.User) error); ok {
+		r0 = rf(ctx, update)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
diff --git a/pkg/users/service.go b/pkg/users/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..d64a5ceceed51d3731eaa6641baf64409cc7389d
--- /dev/null
+++ b/pkg/users/service.go
@@ -0,0 +1,30 @@
+package users
+
+import (
+	"context"
+
+	"git.perx.ru/perxis/perxis-go/pkg/options"
+)
+
+// @microgen grpc
+// @protobuf git.perx.ru/perxis/perxis-go/proto/users
+// @grpc-addr account.users.Users
+type Users interface {
+	Create(ctx context.Context, create *User) (user *User, err error)
+	Get(ctx context.Context, userId string) (user *User, err error)
+	Find(ctx context.Context, filter *Filter, options *options.FindOptions) (users []*User, total int, err error)
+	Update(ctx context.Context, update *User) (err error)
+	Delete(ctx context.Context, userId string) (err error)
+	GetByIdentity(ctx context.Context, identity string) (user *User, err error)
+}
+
+type Filter struct {
+	ID            []string
+	Name          []string
+	Identities    []string
+	DisplayName   []string
+	Email         []string
+	AvatarUri     []string
+	EmailVerified *bool
+	System        *bool
+}
diff --git a/pkg/users/transport/client.microgen.go b/pkg/users/transport/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..74ca261a3bc5ec1cf99c655c82ec5b0345489ce3
--- /dev/null
+++ b/pkg/users/transport/client.microgen.go
@@ -0,0 +1,88 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+	"errors"
+
+	options "git.perx.ru/perxis/perxis-go/pkg/options"
+	users "git.perx.ru/perxis/perxis-go/pkg/users"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+func (set EndpointsSet) Create(arg0 context.Context, arg1 *users.User) (res0 *users.User, res1 error) {
+	request := CreateRequest{Create: 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).User, res1
+}
+
+func (set EndpointsSet) Get(arg0 context.Context, arg1 string) (res0 *users.User, res1 error) {
+	request := GetRequest{UserId: 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).User, res1
+}
+
+func (set EndpointsSet) Find(arg0 context.Context, arg1 *users.Filter, arg2 *options.FindOptions) (res0 []*users.User, res1 int, res2 error) {
+	request := FindRequest{
+		Filter:  arg1,
+		Options: 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).Users, response.(*FindResponse).Total, res2
+}
+
+func (set EndpointsSet) Update(arg0 context.Context, arg1 *users.User) (res0 error) {
+	request := UpdateRequest{Update: 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) (res0 error) {
+	request := DeleteRequest{UserId: 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) GetByIdentity(arg0 context.Context, arg1 string) (res0 *users.User, res1 error) {
+	request := GetByIdentityRequest{Identity: arg1}
+	response, res1 := set.GetByIdentityEndpoint(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.(*GetByIdentityResponse).User, res1
+}
diff --git a/pkg/users/transport/endpoints.microgen.go b/pkg/users/transport/endpoints.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..264025bfc25edd39b423f5ca983188bc6e3a9e60
--- /dev/null
+++ b/pkg/users/transport/endpoints.microgen.go
@@ -0,0 +1,15 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import endpoint "github.com/go-kit/kit/endpoint"
+
+// EndpointsSet implements Users API and used for transport purposes.
+type EndpointsSet struct {
+	CreateEndpoint        endpoint.Endpoint
+	GetEndpoint           endpoint.Endpoint
+	FindEndpoint          endpoint.Endpoint
+	UpdateEndpoint        endpoint.Endpoint
+	DeleteEndpoint        endpoint.Endpoint
+	GetByIdentityEndpoint endpoint.Endpoint
+}
diff --git a/pkg/users/transport/exchanges.microgen.go b/pkg/users/transport/exchanges.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..f70b8cdfe93eb8b843ec66136e8fa63bccc735ee
--- /dev/null
+++ b/pkg/users/transport/exchanges.microgen.go
@@ -0,0 +1,52 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	options "git.perx.ru/perxis/perxis-go/pkg/options"
+	users "git.perx.ru/perxis/perxis-go/pkg/users"
+)
+
+type (
+	CreateRequest struct {
+		Create *users.User `json:"create"`
+	}
+	CreateResponse struct {
+		User *users.User `json:"user"`
+	}
+
+	GetRequest struct {
+		UserId string `json:"user_id"`
+	}
+	GetResponse struct {
+		User *users.User `json:"user"`
+	}
+
+	FindRequest struct {
+		Filter  *users.Filter        `json:"filter"`
+		Options *options.FindOptions `json:"options"`
+	}
+	FindResponse struct {
+		Users []*users.User `json:"users"`
+		Total int           `json:"total"`
+	}
+
+	UpdateRequest struct {
+		Update *users.User `json:"update"`
+	}
+	// Formal exchange type, please do not delete.
+	UpdateResponse struct{}
+
+	DeleteRequest struct {
+		UserId string `json:"user_id"`
+	}
+	// Formal exchange type, please do not delete.
+	DeleteResponse struct{}
+
+	GetByIdentityRequest struct {
+		Identity string `json:"identity"`
+	}
+	GetByIdentityResponse struct {
+		User *users.User `json:"user"`
+	}
+)
diff --git a/pkg/users/transport/grpc/client.microgen.go b/pkg/users/transport/grpc/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..98f4b74c1d23e667d8c1d2c7290cd136f5e20a5d
--- /dev/null
+++ b/pkg/users/transport/grpc/client.microgen.go
@@ -0,0 +1,61 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/users/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/users"
+	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 = "account.users.Users"
+	}
+	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(),
+		FindEndpoint: grpckit.NewClient(
+			conn, addr, "Find",
+			_Encode_Find_Request,
+			_Decode_Find_Response,
+			pb.FindResponse{},
+			opts...,
+		).Endpoint(),
+		GetByIdentityEndpoint: grpckit.NewClient(
+			conn, addr, "GetByIdentity",
+			_Encode_GetByIdentity_Request,
+			_Decode_GetByIdentity_Response,
+			pb.GetByIdentityResponse{},
+			opts...,
+		).Endpoint(),
+		GetEndpoint: grpckit.NewClient(
+			conn, addr, "Get",
+			_Encode_Get_Request,
+			_Decode_Get_Response,
+			pb.GetResponse{},
+			opts...,
+		).Endpoint(),
+		UpdateEndpoint: grpckit.NewClient(
+			conn, addr, "Update",
+			_Encode_Update_Request,
+			_Decode_Update_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+	}
+}
diff --git a/pkg/users/transport/grpc/protobuf_endpoint_converters.microgen.go b/pkg/users/transport/grpc/protobuf_endpoint_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..1837d41081572ab74ebfd7c9594a8292b4c9de29
--- /dev/null
+++ b/pkg/users/transport/grpc/protobuf_endpoint_converters.microgen.go
@@ -0,0 +1,265 @@
+// 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/users/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/users"
+	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)
+	reqCreate, err := PtrUserToProto(req.Create)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateRequest{Create: reqCreate}, 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{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
+	}
+	reqOptions, err := PtrServicesFindOptionsToProto(req.Options)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.FindRequest{
+		Filter:  reqFilter,
+		Options: reqOptions,
+	}, 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)
+	reqUpdate, err := PtrUserToProto(req.Update)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.UpdateRequest{Update: reqUpdate}, 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{UserId: req.UserId}, 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)
+	respUser, err := PtrUserToProto(resp.User)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CreateResponse{User: respUser}, 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)
+	respUser, err := PtrUserToProto(resp.User)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.GetResponse{User: respUser}, 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)
+	respUsers, err := ListPtrUserToProto(resp.Users)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.FindResponse{
+		Total: int64(resp.Total),
+		Users: respUsers,
+	}, 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)
+	reqCreate, err := ProtoToPtrUser(req.Create)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateRequest{Create: reqCreate}, 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{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
+	}
+	reqOptions, err := ProtoToPtrServicesFindOptions(req.Options)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.FindRequest{
+		Filter:  reqFilter,
+		Options: reqOptions,
+	}, 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)
+	reqUpdate, err := ProtoToPtrUser(req.Update)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.UpdateRequest{Update: reqUpdate}, 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{UserId: string(req.UserId)}, 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)
+	respUser, err := ProtoToPtrUser(resp.User)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CreateResponse{User: respUser}, 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)
+	respUser, err := ProtoToPtrUser(resp.User)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.GetResponse{User: respUser}, 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)
+	respUsers, err := ProtoToListPtrUser(resp.Users)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.FindResponse{
+		Total: int(resp.Total),
+		Users: respUsers,
+	}, nil
+}
+
+func _Decode_Delete_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_GetByIdentity_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetByIdentityRequest")
+	}
+	req := request.(*transport.GetByIdentityRequest)
+	return &pb.GetByIdentityRequest{Identity: req.Identity}, nil
+}
+
+func _Encode_GetByIdentity_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetByIdentityResponse")
+	}
+	resp := response.(*transport.GetByIdentityResponse)
+	respUser, err := PtrUserToProto(resp.User)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.GetByIdentityResponse{User: respUser}, nil
+}
+
+func _Decode_GetByIdentity_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetByIdentityRequest")
+	}
+	req := request.(*pb.GetByIdentityRequest)
+	return &transport.GetByIdentityRequest{Identity: string(req.Identity)}, nil
+}
+
+func _Decode_GetByIdentity_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetByIdentityResponse")
+	}
+	resp := response.(*pb.GetByIdentityResponse)
+	respUser, err := ProtoToPtrUser(resp.User)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.GetByIdentityResponse{User: respUser}, nil
+}
+
+func _Encode_Update_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_Update_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
diff --git a/pkg/users/transport/grpc/protobuf_type_converters.microgen.go b/pkg/users/transport/grpc/protobuf_type_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..d0789e52420b5383530e10f89e06100197a32bbb
--- /dev/null
+++ b/pkg/users/transport/grpc/protobuf_type_converters.microgen.go
@@ -0,0 +1,153 @@
+// 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 (
+	options "git.perx.ru/perxis/perxis-go/pkg/options"
+	service "git.perx.ru/perxis/perxis-go/pkg/users"
+	common "git.perx.ru/perxis/perxis-go/proto/common"
+	pb "git.perx.ru/perxis/perxis-go/proto/users"
+	"github.com/golang/protobuf/ptypes/wrappers"
+)
+
+func PtrUserToProto(create *service.User) (*pb.User, error) {
+	if create == nil {
+		return nil, nil
+	}
+	u := &pb.User{
+		Id:          create.ID,
+		Name:        create.Name,
+		Identities:  create.Identities,
+		DisplayName: create.DisplayName,
+		Email:       create.Email,
+		AvatarUrl:   create.AvatarURL,
+	}
+	if create.EmailVerified != nil {
+		u.EmailVerified = &wrappers.BoolValue{
+			Value: *create.EmailVerified,
+		}
+	}
+	if create.System != nil {
+		u.System = &wrappers.BoolValue{
+			Value: *create.System,
+		}
+	}
+	return u, nil
+}
+
+func ProtoToPtrUser(protoCreate *pb.User) (*service.User, error) {
+	if protoCreate == nil {
+		return nil, nil
+	}
+	user := &service.User{
+		ID:          protoCreate.Id,
+		Name:        protoCreate.Name,
+		DisplayName: protoCreate.DisplayName,
+		Identities:  protoCreate.Identities,
+		Email:       protoCreate.Email,
+		AvatarURL:   protoCreate.AvatarUrl,
+	}
+	if protoCreate.EmailVerified != nil {
+		user.EmailVerified = &protoCreate.EmailVerified.Value
+	}
+	if protoCreate.System != nil {
+		user.System = &protoCreate.System.Value
+	}
+	return user, nil
+}
+
+func PtrFilterToProto(filter *service.Filter) (*pb.Filter, error) {
+	if filter == nil {
+		return nil, nil
+	}
+	f := &pb.Filter{
+		Id:          filter.ID,
+		Name:        filter.Name,
+		Identities:  filter.Identities,
+		DisplayName: filter.DisplayName,
+		Email:       filter.Email,
+	}
+	if filter.EmailVerified != nil {
+		f.EmailVerified = &wrappers.BoolValue{
+			Value: *filter.EmailVerified,
+		}
+	}
+	if filter.System != nil {
+		f.System = &wrappers.BoolValue{
+			Value: *filter.System,
+		}
+	}
+	return f, nil
+}
+
+func ProtoToPtrFilter(protoFilter *pb.Filter) (*service.Filter, error) {
+	if protoFilter == nil {
+		return nil, nil
+	}
+	f := &service.Filter{
+		ID:          protoFilter.Id,
+		Name:        protoFilter.Name,
+		Identities:  protoFilter.Identities,
+		DisplayName: protoFilter.DisplayName,
+		Email:       protoFilter.Email,
+	}
+	if protoFilter.EmailVerified != nil {
+		f.EmailVerified = &protoFilter.EmailVerified.Value
+	}
+	if protoFilter.System != nil {
+		f.System = &protoFilter.System.Value
+	}
+	return f, nil
+}
+
+func ListPtrUserToProto(users []*service.User) ([]*pb.User, error) {
+	protoUsers := make([]*pb.User, 0, len(users))
+	for _, u := range users {
+		pu, err := PtrUserToProto(u)
+		if err != nil {
+			return nil, err
+		}
+		protoUsers = append(protoUsers, pu)
+	}
+	return protoUsers, nil
+}
+
+func ProtoToListPtrUser(protoCreates []*pb.User) ([]*service.User, error) {
+	users := make([]*service.User, 0, len(protoCreates))
+	for _, pu := range protoCreates {
+		u, err := ProtoToPtrUser(pu)
+		if err != nil {
+			return nil, err
+		}
+		users = append(users, u)
+	}
+	return users, nil
+}
+
+func PtrServicesFindOptionsToProto(opts *options.FindOptions) (*common.FindOptions, error) {
+	if opts == nil {
+		return nil, nil
+	}
+	return &common.FindOptions{
+		Sort:     opts.Sort,
+		PageNum:  int32(opts.PageNum),
+		PageSize: int32(opts.PageSize),
+	}, nil
+}
+
+func ProtoToPtrServicesFindOptions(protoOpts *common.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
+}
diff --git a/pkg/users/transport/grpc/server.microgen.go b/pkg/users/transport/grpc/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..2be01e97a5a98fcf5189b386fbb36d1a60f43db7
--- /dev/null
+++ b/pkg/users/transport/grpc/server.microgen.go
@@ -0,0 +1,112 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// DO NOT EDIT.
+package transportgrpc
+
+import (
+	transport "git.perx.ru/perxis/perxis-go/pkg/users/transport"
+	pb "git.perx.ru/perxis/perxis-go/proto/users"
+	grpc "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	context "golang.org/x/net/context"
+)
+
+type usersServer struct {
+	create        grpc.Handler
+	get           grpc.Handler
+	find          grpc.Handler
+	update        grpc.Handler
+	delete        grpc.Handler
+	getByIdentity grpc.Handler
+
+	pb.UnimplementedUsersServer
+}
+
+func NewGRPCServer(endpoints *transport.EndpointsSet, opts ...grpc.ServerOption) pb.UsersServer {
+	return &usersServer{
+		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...,
+		),
+		getByIdentity: grpc.NewServer(
+			endpoints.GetByIdentityEndpoint,
+			_Decode_GetByIdentity_Request,
+			_Encode_GetByIdentity_Response,
+			opts...,
+		),
+		update: grpc.NewServer(
+			endpoints.UpdateEndpoint,
+			_Decode_Update_Request,
+			_Encode_Update_Response,
+			opts...,
+		),
+	}
+}
+
+func (S *usersServer) 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 *usersServer) 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 *usersServer) 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 *usersServer) 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 *usersServer) 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 *usersServer) GetByIdentity(ctx context.Context, req *pb.GetByIdentityRequest) (*pb.GetByIdentityResponse, error) {
+	_, resp, err := S.getByIdentity.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.GetByIdentityResponse), nil
+}
diff --git a/pkg/users/transport/server.microgen.go b/pkg/users/transport/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..e12645efc79921da957e359848d9dca606f6364b
--- /dev/null
+++ b/pkg/users/transport/server.microgen.go
@@ -0,0 +1,72 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+
+	users "git.perx.ru/perxis/perxis-go/pkg/users"
+	endpoint "github.com/go-kit/kit/endpoint"
+)
+
+func Endpoints(svc users.Users) EndpointsSet {
+	return EndpointsSet{
+		CreateEndpoint:        CreateEndpoint(svc),
+		DeleteEndpoint:        DeleteEndpoint(svc),
+		FindEndpoint:          FindEndpoint(svc),
+		GetByIdentityEndpoint: GetByIdentityEndpoint(svc),
+		GetEndpoint:           GetEndpoint(svc),
+		UpdateEndpoint:        UpdateEndpoint(svc),
+	}
+}
+
+func CreateEndpoint(svc users.Users) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*CreateRequest)
+		res0, res1 := svc.Create(arg0, req.Create)
+		return &CreateResponse{User: res0}, res1
+	}
+}
+
+func GetEndpoint(svc users.Users) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*GetRequest)
+		res0, res1 := svc.Get(arg0, req.UserId)
+		return &GetResponse{User: res0}, res1
+	}
+}
+
+func FindEndpoint(svc users.Users) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*FindRequest)
+		res0, res1, res2 := svc.Find(arg0, req.Filter, req.Options)
+		return &FindResponse{
+			Total: res1,
+			Users: res0,
+		}, res2
+	}
+}
+
+func UpdateEndpoint(svc users.Users) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*UpdateRequest)
+		res0 := svc.Update(arg0, req.Update)
+		return &UpdateResponse{}, res0
+	}
+}
+
+func DeleteEndpoint(svc users.Users) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*DeleteRequest)
+		res0 := svc.Delete(arg0, req.UserId)
+		return &DeleteResponse{}, res0
+	}
+}
+
+func GetByIdentityEndpoint(svc users.Users) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*GetByIdentityRequest)
+		res0, res1 := svc.GetByIdentity(arg0, req.Identity)
+		return &GetByIdentityResponse{User: res0}, res1
+	}
+}
diff --git a/pkg/users/user.go b/pkg/users/user.go
new file mode 100644
index 0000000000000000000000000000000000000000..9eca6efc24569c2ef951d2bcb059440d54896ecc
--- /dev/null
+++ b/pkg/users/user.go
@@ -0,0 +1,31 @@
+package users
+
+// Current - Идентификатор, который можно использовать для получения/обновления/регистрации
+// пользователя, от имени которого был сделан запрос.
+const Current = "current"
+
+type User struct {
+	ID            string   `json:"id" bson:"_id"`
+	Name          string   `json:"name" bson:"name"`
+	DisplayName   string   `json:"displayName" bson:"displayName"`
+	Identities    []string `json:"identities" bson:"identities"`
+	Email         string   `json:"email" bson:"email"`
+	EmailVerified *bool    `json:"emailVerified" bson:"emailVerified"`
+	AvatarURL     string   `json:"avatarUrl" bson:"avatarUrl,omitempty"`
+	System        *bool    `json:"system" bson:"system"`
+}
+
+func (u User) GetID() string {
+	return u.ID
+}
+
+func (u User) IsSystem() bool {
+	if u.System != nil {
+		return *u.System
+	}
+	return false
+}
+
+func (u User) Clone() *User {
+	return &u
+}