diff --git a/pkg/files/file.go b/pkg/files/file.go
new file mode 100644
index 0000000000000000000000000000000000000000..c60ace139992d80e1bf8f41cd28b3bb0e883007a
--- /dev/null
+++ b/pkg/files/file.go
@@ -0,0 +1,90 @@
+package files
+
+import (
+	"fmt"
+	"io/fs"
+	"strings"
+
+	"git.perx.ru/perxis/perxis-go/pkg/id"
+)
+
+const (
+	TemporaryPrefix = "tmp"
+)
+
+// File - описание файла в системе хранения perxis
+type File struct {
+	ID       string  `mapstructure:"id,omitempty" json:"id"`                                       // Уникальный идентификатор файла в хранилище
+	Name     string  `mapstructure:"name,omitempty" json:"name" bson:"name,omitempty"`             // Имя файла
+	Size     int     `mapstructure:"size,omitempty" json:"size" bson:"size,omitempty"`             // Размер файла
+	MimeType string  `mapstructure:"mimeType,omitempty" json:"mimeType" bson:"mimeType,omitempty"` // Mime-type файла
+	URL      string  `mapstructure:"url,omitempty" json:"url" bson:"url,omitempty"`                // Адрес для загрузки файла
+	Key      string  `mapstructure:"key,omitempty" json:"key" bson:"key,omitempty"`                // Ключ для хранения файла в хранилище
+	File     fs.File `mapstructure:"-" json:"-" bson:"-"`                                          // Файл для загрузки(из файловой системы)
+}
+
+func (f File) Clone() *File {
+	return &f
+}
+
+func (f File) Temporary() bool {
+	return strings.HasPrefix(f.ID, TemporaryPrefix)
+}
+
+func (f File) Fetch(i interface{}) interface{} {
+	p, _ := i.(string)
+	switch p {
+	case "id":
+		return f.ID
+	case "name":
+		return f.Name
+	case "size":
+		return f.Size
+	case "mime_type":
+		return f.MimeType
+	case "url":
+		return f.URL
+	case "key":
+		return f.Key
+	default:
+		panic("unknown parameter")
+	}
+	return nil
+}
+
+func NewFile(name, mimeType string, size int, temp bool) *File {
+	i := id.GenerateNewID()
+	if temp {
+		i = fmt.Sprintf("%s%s", TemporaryPrefix, i)
+	}
+	return &File{
+		ID:       i,
+		Name:     name,
+		Size:     size,
+		MimeType: mimeType,
+	}
+}
+
+// MultipartUpload - описание загрузки файла
+type MultipartUpload struct {
+	File
+	UploadID string           `json:"upload_id"` // Идентификатор загрузки хранилища
+	PartSize int              `json:"part_size"` // Размер блока для загрузки
+	PartURLs []string         `json:"part_urls"` // Адреса для загрузки полного файла
+	Parts    []*CompletedPart `json:"parts"`     // Идентификаторы загруженных блоков (S3 ETAGs)
+}
+
+// Upload - описание загрузки файла
+type Upload struct {
+	File
+	UploadURL string `json:"upload_url"` // Идентификатор загрузки хранилища
+}
+
+type CompletedPart struct {
+	Number int    `json:"part_number"`
+	ID     string `json:"id"`
+}
+
+func (u MultipartUpload) Clone() *MultipartUpload {
+	return &u
+}
diff --git a/pkg/files/mocks/Downloader.go b/pkg/files/mocks/Downloader.go
new file mode 100644
index 0000000000000000000000000000000000000000..d44b6d19a56fe50f1ad00647026007a1ec85d850
--- /dev/null
+++ b/pkg/files/mocks/Downloader.go
@@ -0,0 +1,30 @@
+// Code generated by mockery v2.7.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	io "io"
+
+	files "git.perx.ru/perxis/perxis-go/pkg/files"
+
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Downloader is an autogenerated mock type for the Downloader type
+type Downloader struct {
+	mock.Mock
+}
+
+// Download provides a mock function with given fields: dst, file
+func (_m *Downloader) Download(dst io.Writer, file *files.File) error {
+	ret := _m.Called(dst, file)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(io.Writer, *files.File) error); ok {
+		r0 = rf(dst, file)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
diff --git a/pkg/files/mocks/FileService.go b/pkg/files/mocks/FileService.go
new file mode 100644
index 0000000000000000000000000000000000000000..244850fbb1824ba599c64c4551a5346fe45c5b7f
--- /dev/null
+++ b/pkg/files/mocks/FileService.go
@@ -0,0 +1,158 @@
+// Code generated by mockery v2.7.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	file "git.perx.ru/perxis/perxis-go/pkg/files"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// FileService is an autogenerated mock type for the FileService type
+type FileService struct {
+	mock.Mock
+}
+
+// AbortUpload provides a mock function with given fields: ctx, upload
+func (_m *FileService) AbortUpload(ctx context.Context, upload *file.MultipartUpload) error {
+	ret := _m.Called(ctx, upload)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *file.MultipartUpload) error); ok {
+		r0 = rf(ctx, upload)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// CompleteUpload provides a mock function with given fields: ctx, upload
+func (_m *FileService) CompleteUpload(ctx context.Context, upload *file.MultipartUpload) (*file.MultipartUpload, error) {
+	ret := _m.Called(ctx, upload)
+
+	var r0 *file.MultipartUpload
+	if rf, ok := ret.Get(0).(func(context.Context, *file.MultipartUpload) *file.MultipartUpload); ok {
+		r0 = rf(ctx, upload)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*file.MultipartUpload)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *file.MultipartUpload) error); ok {
+		r1 = rf(ctx, upload)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// DeleteFile provides a mock function with given fields: ctx, _a1
+func (_m *FileService) DeleteFile(ctx context.Context, _a1 *file.File) error {
+	ret := _m.Called(ctx, _a1)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *file.File) error); ok {
+		r0 = rf(ctx, _a1)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// GetFile provides a mock function with given fields: ctx, _a1
+func (_m *FileService) GetFile(ctx context.Context, _a1 *file.File) (*file.File, error) {
+	ret := _m.Called(ctx, _a1)
+
+	var r0 *file.File
+	if rf, ok := ret.Get(0).(func(context.Context, *file.File) *file.File); ok {
+		r0 = rf(ctx, _a1)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*file.File)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *file.File) error); ok {
+		r1 = rf(ctx, _a1)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// MoveUpload provides a mock function with given fields: ctx, upload
+func (_m *FileService) MoveUpload(ctx context.Context, upload *file.MultipartUpload) (*file.File, error) {
+	ret := _m.Called(ctx, upload)
+
+	var r0 *file.File
+	if rf, ok := ret.Get(0).(func(context.Context, *file.MultipartUpload) *file.File); ok {
+		r0 = rf(ctx, upload)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*file.File)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *file.MultipartUpload) error); ok {
+		r1 = rf(ctx, upload)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// StartUpload provides a mock function with given fields: ctx, upload
+func (_m *FileService) StartUpload(ctx context.Context, upload *file.MultipartUpload) (*file.MultipartUpload, error) {
+	ret := _m.Called(ctx, upload)
+
+	var r0 *file.MultipartUpload
+	if rf, ok := ret.Get(0).(func(context.Context, *file.MultipartUpload) *file.MultipartUpload); ok {
+		r0 = rf(ctx, upload)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*file.MultipartUpload)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *file.MultipartUpload) error); ok {
+		r1 = rf(ctx, upload)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Upload provides a mock function with given fields: ctx, _a1
+func (_m *FileService) Upload(ctx context.Context, _a1 *file.File) (*file.Upload, error) {
+	ret := _m.Called(ctx, _a1)
+
+	var r0 *file.Upload
+	if rf, ok := ret.Get(0).(func(context.Context, *file.File) *file.Upload); ok {
+		r0 = rf(ctx, _a1)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*file.Upload)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *file.File) error); ok {
+		r1 = rf(ctx, _a1)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
diff --git a/pkg/files/mocks/FileStorage.go b/pkg/files/mocks/FileStorage.go
new file mode 100644
index 0000000000000000000000000000000000000000..84820da52ed3267935ceebbb7baa24416c4d1be1
--- /dev/null
+++ b/pkg/files/mocks/FileStorage.go
@@ -0,0 +1,158 @@
+// Code generated by mockery v2.7.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	file "git.perx.ru/perxis/perxis-go/pkg/files"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// FileStorage is an autogenerated mock type for the FileStorage type
+type FileStorage struct {
+	mock.Mock
+}
+
+// AbortUpload provides a mock function with given fields: ctx, upload
+func (_m *FileStorage) AbortUpload(ctx context.Context, upload *file.MultipartUpload) error {
+	ret := _m.Called(ctx, upload)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *file.MultipartUpload) error); ok {
+		r0 = rf(ctx, upload)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// CompleteUpload provides a mock function with given fields: ctx, upload
+func (_m *FileStorage) CompleteUpload(ctx context.Context, upload *file.MultipartUpload) (*file.MultipartUpload, error) {
+	ret := _m.Called(ctx, upload)
+
+	var r0 *file.MultipartUpload
+	if rf, ok := ret.Get(0).(func(context.Context, *file.MultipartUpload) *file.MultipartUpload); ok {
+		r0 = rf(ctx, upload)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*file.MultipartUpload)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *file.MultipartUpload) error); ok {
+		r1 = rf(ctx, upload)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// DeleteFile provides a mock function with given fields: ctx, _a1
+func (_m *FileStorage) DeleteFile(ctx context.Context, _a1 *file.File) error {
+	ret := _m.Called(ctx, _a1)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *file.File) error); ok {
+		r0 = rf(ctx, _a1)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// GetFile provides a mock function with given fields: ctx, _a1
+func (_m *FileStorage) GetFile(ctx context.Context, _a1 *file.File) (*file.File, error) {
+	ret := _m.Called(ctx, _a1)
+
+	var r0 *file.File
+	if rf, ok := ret.Get(0).(func(context.Context, *file.File) *file.File); ok {
+		r0 = rf(ctx, _a1)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*file.File)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *file.File) error); ok {
+		r1 = rf(ctx, _a1)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// MoveUpload provides a mock function with given fields: ctx, src, dst
+func (_m *FileStorage) Move(ctx context.Context, src *file.File, dst *file.File) (*file.File, error) {
+	ret := _m.Called(ctx, src, dst)
+
+	var r0 *file.File
+	if rf, ok := ret.Get(0).(func(context.Context, *file.File, *file.File) *file.File); ok {
+		r0 = rf(ctx, src, dst)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*file.File)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *file.File, *file.File) error); ok {
+		r1 = rf(ctx, src, dst)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// StartUpload provides a mock function with given fields: ctx, upload
+func (_m *FileStorage) StartUpload(ctx context.Context, upload *file.MultipartUpload) (*file.MultipartUpload, error) {
+	ret := _m.Called(ctx, upload)
+
+	var r0 *file.MultipartUpload
+	if rf, ok := ret.Get(0).(func(context.Context, *file.MultipartUpload) *file.MultipartUpload); ok {
+		r0 = rf(ctx, upload)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*file.MultipartUpload)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *file.MultipartUpload) error); ok {
+		r1 = rf(ctx, upload)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Upload provides a mock function with given fields: ctx, _a1
+func (_m *FileStorage) Upload(ctx context.Context, _a1 *file.File) (*file.Upload, error) {
+	ret := _m.Called(ctx, _a1)
+
+	var r0 *file.Upload
+	if rf, ok := ret.Get(0).(func(context.Context, *file.File) *file.Upload); ok {
+		r0 = rf(ctx, _a1)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*file.Upload)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *file.File) error); ok {
+		r1 = rf(ctx, _a1)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
diff --git a/pkg/files/mocks/Files.go b/pkg/files/mocks/Files.go
new file mode 100644
index 0000000000000000000000000000000000000000..c6cdc0fa17f4674b98fb4c966d6ad7f87203276b
--- /dev/null
+++ b/pkg/files/mocks/Files.go
@@ -0,0 +1,158 @@
+// Code generated by mockery v2.7.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	files "git.perx.ru/perxis/perxis-go/pkg/files"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Files is an autogenerated mock type for the Files type
+type Files struct {
+	mock.Mock
+}
+
+// AbortUpload provides a mock function with given fields: ctx, upload
+func (_m *Files) AbortUpload(ctx context.Context, upload *files.MultipartUpload) error {
+	ret := _m.Called(ctx, upload)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *files.MultipartUpload) error); ok {
+		r0 = rf(ctx, upload)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// CompleteUpload provides a mock function with given fields: ctx, upload
+func (_m *Files) CompleteUpload(ctx context.Context, upload *files.MultipartUpload) (*files.MultipartUpload, error) {
+	ret := _m.Called(ctx, upload)
+
+	var r0 *files.MultipartUpload
+	if rf, ok := ret.Get(0).(func(context.Context, *files.MultipartUpload) *files.MultipartUpload); ok {
+		r0 = rf(ctx, upload)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*files.MultipartUpload)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *files.MultipartUpload) error); ok {
+		r1 = rf(ctx, upload)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// DeleteFile provides a mock function with given fields: ctx, file
+func (_m *Files) DeleteFile(ctx context.Context, file *files.File) error {
+	ret := _m.Called(ctx, file)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *files.File) error); ok {
+		r0 = rf(ctx, file)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// GetFile provides a mock function with given fields: ctx, file
+func (_m *Files) GetFile(ctx context.Context, file *files.File) (*files.File, error) {
+	ret := _m.Called(ctx, file)
+
+	var r0 *files.File
+	if rf, ok := ret.Get(0).(func(context.Context, *files.File) *files.File); ok {
+		r0 = rf(ctx, file)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*files.File)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *files.File) error); ok {
+		r1 = rf(ctx, file)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// MoveUpload provides a mock function with given fields: ctx, upload
+func (_m *Files) MoveUpload(ctx context.Context, upload *files.MultipartUpload) (*files.File, error) {
+	ret := _m.Called(ctx, upload)
+
+	var r0 *files.File
+	if rf, ok := ret.Get(0).(func(context.Context, *files.MultipartUpload) *files.File); ok {
+		r0 = rf(ctx, upload)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*files.File)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *files.MultipartUpload) error); ok {
+		r1 = rf(ctx, upload)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// StartUpload provides a mock function with given fields: ctx, upload
+func (_m *Files) StartUpload(ctx context.Context, upload *files.MultipartUpload) (*files.MultipartUpload, error) {
+	ret := _m.Called(ctx, upload)
+
+	var r0 *files.MultipartUpload
+	if rf, ok := ret.Get(0).(func(context.Context, *files.MultipartUpload) *files.MultipartUpload); ok {
+		r0 = rf(ctx, upload)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*files.MultipartUpload)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *files.MultipartUpload) error); ok {
+		r1 = rf(ctx, upload)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Upload provides a mock function with given fields: ctx, file
+func (_m *Files) Upload(ctx context.Context, file *files.File) (*files.Upload, error) {
+	ret := _m.Called(ctx, file)
+
+	var r0 *files.Upload
+	if rf, ok := ret.Get(0).(func(context.Context, *files.File) *files.Upload); ok {
+		r0 = rf(ctx, file)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*files.Upload)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *files.File) error); ok {
+		r1 = rf(ctx, file)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
diff --git a/pkg/files/mocks/Middleware.go b/pkg/files/mocks/Middleware.go
new file mode 100644
index 0000000000000000000000000000000000000000..3f5db554b0144ee86c033e3b6c95dc8fde11ebb7
--- /dev/null
+++ b/pkg/files/mocks/Middleware.go
@@ -0,0 +1,29 @@
+// Code generated by mockery v2.7.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	files "git.perx.ru/perxis/perxis-go/pkg/files"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Middleware is an autogenerated mock type for the Middleware type
+type Middleware struct {
+	mock.Mock
+}
+
+// Execute provides a mock function with given fields: _a0
+func (_m *Middleware) Execute(_a0 files.Files) files.Files {
+	ret := _m.Called(_a0)
+
+	var r0 files.Files
+	if rf, ok := ret.Get(0).(func(files.Files) files.Files); ok {
+		r0 = rf(_a0)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(files.Files)
+		}
+	}
+
+	return r0
+}
diff --git a/pkg/files/mocks/Storage.go b/pkg/files/mocks/Storage.go
new file mode 100644
index 0000000000000000000000000000000000000000..60ad2736bff8fbdfd7cb114ee0a1418053544cee
--- /dev/null
+++ b/pkg/files/mocks/Storage.go
@@ -0,0 +1,158 @@
+// Code generated by mockery v2.7.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	context "context"
+
+	files "git.perx.ru/perxis/perxis-go/pkg/files"
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Storage is an autogenerated mock type for the Storage type
+type Storage struct {
+	mock.Mock
+}
+
+// AbortUpload provides a mock function with given fields: ctx, upload
+func (_m *Storage) AbortUpload(ctx context.Context, upload *files.MultipartUpload) error {
+	ret := _m.Called(ctx, upload)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *files.MultipartUpload) error); ok {
+		r0 = rf(ctx, upload)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// CompleteUpload provides a mock function with given fields: ctx, upload
+func (_m *Storage) CompleteUpload(ctx context.Context, upload *files.MultipartUpload) (*files.MultipartUpload, error) {
+	ret := _m.Called(ctx, upload)
+
+	var r0 *files.MultipartUpload
+	if rf, ok := ret.Get(0).(func(context.Context, *files.MultipartUpload) *files.MultipartUpload); ok {
+		r0 = rf(ctx, upload)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*files.MultipartUpload)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *files.MultipartUpload) error); ok {
+		r1 = rf(ctx, upload)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// DeleteFile provides a mock function with given fields: ctx, file
+func (_m *Storage) DeleteFile(ctx context.Context, file *files.File) error {
+	ret := _m.Called(ctx, file)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *files.File) error); ok {
+		r0 = rf(ctx, file)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// GetFile provides a mock function with given fields: ctx, file
+func (_m *Storage) GetFile(ctx context.Context, file *files.File) (*files.File, error) {
+	ret := _m.Called(ctx, file)
+
+	var r0 *files.File
+	if rf, ok := ret.Get(0).(func(context.Context, *files.File) *files.File); ok {
+		r0 = rf(ctx, file)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*files.File)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *files.File) error); ok {
+		r1 = rf(ctx, file)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Move provides a mock function with given fields: ctx, src, dst
+func (_m *Storage) Move(ctx context.Context, src *files.File, dst *files.File) (*files.File, error) {
+	ret := _m.Called(ctx, src, dst)
+
+	var r0 *files.File
+	if rf, ok := ret.Get(0).(func(context.Context, *files.File, *files.File) *files.File); ok {
+		r0 = rf(ctx, src, dst)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*files.File)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *files.File, *files.File) error); ok {
+		r1 = rf(ctx, src, dst)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// StartUpload provides a mock function with given fields: ctx, upload
+func (_m *Storage) StartUpload(ctx context.Context, upload *files.MultipartUpload) (*files.MultipartUpload, error) {
+	ret := _m.Called(ctx, upload)
+
+	var r0 *files.MultipartUpload
+	if rf, ok := ret.Get(0).(func(context.Context, *files.MultipartUpload) *files.MultipartUpload); ok {
+		r0 = rf(ctx, upload)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*files.MultipartUpload)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *files.MultipartUpload) error); ok {
+		r1 = rf(ctx, upload)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// Upload provides a mock function with given fields: ctx, file
+func (_m *Storage) Upload(ctx context.Context, file *files.File) (*files.Upload, error) {
+	ret := _m.Called(ctx, file)
+
+	var r0 *files.Upload
+	if rf, ok := ret.Get(0).(func(context.Context, *files.File) *files.Upload); ok {
+		r0 = rf(ctx, file)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*files.Upload)
+		}
+	}
+
+	var r1 error
+	if rf, ok := ret.Get(1).(func(context.Context, *files.File) error); ok {
+		r1 = rf(ctx, file)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
diff --git a/pkg/files/mocks/Uploader.go b/pkg/files/mocks/Uploader.go
new file mode 100644
index 0000000000000000000000000000000000000000..1489c5e523564b6f523efab9e49ad19b8e0165bb
--- /dev/null
+++ b/pkg/files/mocks/Uploader.go
@@ -0,0 +1,30 @@
+// Code generated by mockery v2.7.4. DO NOT EDIT.
+
+package mocks
+
+import (
+	io "io"
+
+	files "git.perx.ru/perxis/perxis-go/pkg/files"
+
+	mock "github.com/stretchr/testify/mock"
+)
+
+// Uploader is an autogenerated mock type for the Uploader type
+type Uploader struct {
+	mock.Mock
+}
+
+// Upload provides a mock function with given fields: src, upload
+func (_m *Uploader) Upload(src io.Reader, upload *files.Upload) error {
+	ret := _m.Called(src, upload)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(io.Reader, *files.Upload) error); ok {
+		r0 = rf(src, upload)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
diff --git a/pkg/files/service.go b/pkg/files/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..b97af13a01deed33131c6c87a7d5d8f52b70d29a
--- /dev/null
+++ b/pkg/files/service.go
@@ -0,0 +1,36 @@
+package files
+
+import (
+	"context"
+)
+
+// Files - описывает интерфейс файлового сервиса
+// @microgen grpc, recovering, middleware
+// @protobuf git.perx.ru/perxis/perxis-go/proto/files
+// @grpc-addr files.Files
+type Files interface {
+	// StartUpload - инициирует процедуру загрузки файла в файловое хранилище.
+	// Используется клиентским приложением для начала загрузки файла
+	StartUpload(ctx context.Context, upload *MultipartUpload) (u *MultipartUpload, err error)
+
+	// CompleteUpload - завершает процедуру загрузку файла
+	CompleteUpload(ctx context.Context, upload *MultipartUpload) (u *MultipartUpload, err error)
+
+	// AbortUpload - прерывает процедуру загрузки файла, все загруженные части файла удаляются их хранилища
+	AbortUpload(ctx context.Context, upload *MultipartUpload) (err error)
+
+	// MoveUpload - перемещает загруженный файл из временного расположения в постоянное месторасположения.
+	// После перемещение загрузки хранилище выдает новый идентификатор постоянного файла
+	MoveUpload(ctx context.Context, upload *MultipartUpload) (file *File, err error)
+
+	// Upload - инициация загрузки файла в хранилище. Возвращает объект, содержащий подписанный URL.
+	// Завершение загрузки файла осуществляется выполнением POST-запроса
+	Upload(ctx context.Context, file *File) (u *Upload, err error)
+
+	// GetFile - получить объект файла
+	GetFile(ctx context.Context, file *File) (f *File, err error)
+
+	// DeleteFile - удаляет файл. Если происходит удаление оригинала,
+	// удаляются и все связанные структуры
+	DeleteFile(ctx context.Context, file *File) (err error)
+}
diff --git a/pkg/files/transport/client.microgen.go b/pkg/files/transport/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..e576fb7acbaab62a5e85c317b45cb98decd33758
--- /dev/null
+++ b/pkg/files/transport/client.microgen.go
@@ -0,0 +1,95 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+	"errors"
+	files "git.perx.ru/perxis/perxis/services/files"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+func (set EndpointsSet) StartUpload(arg0 context.Context, arg1 *files.MultipartUpload) (res0 *files.MultipartUpload, res1 error) {
+	request := StartUploadRequest{Upload: arg1}
+	response, res1 := set.StartUploadEndpoint(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.(*StartUploadResponse).U, res1
+}
+
+func (set EndpointsSet) CompleteUpload(arg0 context.Context, arg1 *files.MultipartUpload) (res0 *files.MultipartUpload, res1 error) {
+	request := CompleteUploadRequest{Upload: arg1}
+	response, res1 := set.CompleteUploadEndpoint(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.(*CompleteUploadResponse).U, res1
+}
+
+func (set EndpointsSet) AbortUpload(arg0 context.Context, arg1 *files.MultipartUpload) (res0 error) {
+	request := AbortUploadRequest{Upload: arg1}
+	_, res0 = set.AbortUploadEndpoint(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) MoveUpload(arg0 context.Context, arg1 *files.MultipartUpload) (res0 *files.File, res1 error) {
+	request := MoveUploadRequest{Upload: arg1}
+	response, res1 := set.MoveUploadEndpoint(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.(*MoveUploadResponse).File, res1
+}
+
+func (set EndpointsSet) Upload(arg0 context.Context, arg1 *files.File) (res0 *files.Upload, res1 error) {
+	request := UploadRequest{File: arg1}
+	response, res1 := set.UploadEndpoint(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.(*UploadResponse).U, res1
+}
+
+func (set EndpointsSet) GetFile(arg0 context.Context, arg1 *files.File) (res0 *files.File, res1 error) {
+	request := GetFileRequest{File: arg1}
+	response, res1 := set.GetFileEndpoint(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.(*GetFileResponse).F, res1
+}
+
+func (set EndpointsSet) DeleteFile(arg0 context.Context, arg1 *files.File) (res0 error) {
+	request := DeleteFileRequest{File: arg1}
+	_, res0 = set.DeleteFileEndpoint(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/files/transport/endpoints.microgen.go b/pkg/files/transport/endpoints.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..96652f6f2ab22546cbdc7375363584173f315069
--- /dev/null
+++ b/pkg/files/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 Files API and used for transport purposes.
+type EndpointsSet struct {
+	StartUploadEndpoint    endpoint.Endpoint
+	CompleteUploadEndpoint endpoint.Endpoint
+	AbortUploadEndpoint    endpoint.Endpoint
+	MoveUploadEndpoint     endpoint.Endpoint
+	UploadEndpoint         endpoint.Endpoint
+	GetFileEndpoint        endpoint.Endpoint
+	DeleteFileEndpoint     endpoint.Endpoint
+}
diff --git a/pkg/files/transport/exchanges.microgen.go b/pkg/files/transport/exchanges.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..7f211c4b6581ed4590b2312bdcc8c23d62f840c5
--- /dev/null
+++ b/pkg/files/transport/exchanges.microgen.go
@@ -0,0 +1,54 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import files "git.perx.ru/perxis/perxis/services/files"
+
+type (
+	StartUploadRequest struct {
+		Upload *files.MultipartUpload `json:"upload"`
+	}
+	StartUploadResponse struct {
+		U *files.MultipartUpload `json:"u"`
+	}
+
+	CompleteUploadRequest struct {
+		Upload *files.MultipartUpload `json:"upload"`
+	}
+	CompleteUploadResponse struct {
+		U *files.MultipartUpload `json:"u"`
+	}
+
+	AbortUploadRequest struct {
+		Upload *files.MultipartUpload `json:"upload"`
+	}
+	// Formal exchange type, please do not delete.
+	AbortUploadResponse struct{}
+
+	MoveUploadRequest struct {
+		Upload *files.MultipartUpload `json:"upload"`
+	}
+	MoveUploadResponse struct {
+		File *files.File `json:"file"`
+	}
+
+	UploadRequest struct {
+		File *files.File `json:"file"`
+	}
+	UploadResponse struct {
+		U *files.Upload `json:"u"`
+	}
+
+	GetFileRequest struct {
+		File *files.File `json:"file"`
+	}
+	GetFileResponse struct {
+		F *files.File `json:"f"`
+	}
+
+	DeleteFileRequest struct {
+		File *files.File `json:"file"`
+	}
+	// Formal exchange type, please do not delete.
+	DeleteFileResponse struct{}
+)
diff --git a/pkg/files/transport/grpc/client.microgen.go b/pkg/files/transport/grpc/client.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..5a9fdcb8dbbd99b3406416566a295d9f55c62124
--- /dev/null
+++ b/pkg/files/transport/grpc/client.microgen.go
@@ -0,0 +1,68 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transportgrpc
+
+import (
+	pb "git.perx.ru/perxis/perxis-go/proto/files"
+	transport "git.perx.ru/perxis/perxis/services/files/transport"
+	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 = "files.Files"
+	}
+	return transport.EndpointsSet{
+		AbortUploadEndpoint: grpckit.NewClient(
+			conn, addr, "AbortUpload",
+			_Encode_AbortUpload_Request,
+			_Decode_AbortUpload_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+		CompleteUploadEndpoint: grpckit.NewClient(
+			conn, addr, "CompleteUpload",
+			_Encode_CompleteUpload_Request,
+			_Decode_CompleteUpload_Response,
+			pb.CompleteUploadResponse{},
+			opts...,
+		).Endpoint(),
+		DeleteFileEndpoint: grpckit.NewClient(
+			conn, addr, "DeleteFile",
+			_Encode_DeleteFile_Request,
+			_Decode_DeleteFile_Response,
+			empty.Empty{},
+			opts...,
+		).Endpoint(),
+		GetFileEndpoint: grpckit.NewClient(
+			conn, addr, "GetFile",
+			_Encode_GetFile_Request,
+			_Decode_GetFile_Response,
+			pb.GetFileResponse{},
+			opts...,
+		).Endpoint(),
+		MoveUploadEndpoint: grpckit.NewClient(
+			conn, addr, "MoveUpload",
+			_Encode_MoveUpload_Request,
+			_Decode_MoveUpload_Response,
+			pb.MoveUploadResponse{},
+			opts...,
+		).Endpoint(),
+		StartUploadEndpoint: grpckit.NewClient(
+			conn, addr, "StartUpload",
+			_Encode_StartUpload_Request,
+			_Decode_StartUpload_Response,
+			pb.StartUploadResponse{},
+			opts...,
+		).Endpoint(),
+		UploadEndpoint: grpckit.NewClient(
+			conn, addr, "Upload",
+			_Encode_Upload_Request,
+			_Decode_Upload_Response,
+			pb.UploadResponse{},
+			opts...,
+		).Endpoint(),
+	}
+}
diff --git a/pkg/files/transport/grpc/protobuf_endpoint_converters.microgen.go b/pkg/files/transport/grpc/protobuf_endpoint_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..fe10ff27a8b7aaa92213b33a1ce7d0e2134d57de
--- /dev/null
+++ b/pkg/files/transport/grpc/protobuf_endpoint_converters.microgen.go
@@ -0,0 +1,310 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// Please, do not change functions names!
+package transportgrpc
+
+import (
+	"context"
+	"errors"
+
+	pb "git.perx.ru/perxis/perxis-go/proto/files"
+	"git.perx.ru/perxis/perxis/services/files"
+	"git.perx.ru/perxis/perxis/services/files/transport"
+	empty "github.com/golang/protobuf/ptypes/empty"
+)
+
+func _Encode_StartUpload_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil StartUploadRequest")
+	}
+	req := request.(*transport.StartUploadRequest)
+	reqUpload, err := PtrMultipartUploadToProto(req.Upload)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.StartUploadRequest{Upload: reqUpload}, nil
+}
+
+func _Encode_CompleteUpload_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil CompleteUploadRequest")
+	}
+	req := request.(*transport.CompleteUploadRequest)
+	reqUpload, err := PtrMultipartUploadToProto(req.Upload)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CompleteUploadRequest{Upload: reqUpload}, nil
+}
+
+func _Encode_AbortUpload_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil AbortUploadRequest")
+	}
+	req := request.(*transport.AbortUploadRequest)
+	reqUpload, err := PtrMultipartUploadToProto(req.Upload)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.AbortUploadRequest{Upload: reqUpload}, nil
+}
+
+func _Encode_MoveUpload_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil MoveUploadRequest")
+	}
+	req := request.(*transport.MoveUploadRequest)
+	reqUpload, err := PtrMultipartUploadToProto(req.Upload)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.MoveUploadRequest{Upload: reqUpload}, nil
+}
+
+func _Encode_DeleteFile_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil DeleteFileRequest")
+	}
+	req := request.(*transport.DeleteFileRequest)
+	reqFile, err := PtrFileToProto(req.File)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.DeleteFileRequest{File: reqFile}, nil
+}
+
+func _Encode_StartUpload_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil StartUploadResponse")
+	}
+	resp := response.(*transport.StartUploadResponse)
+	respU, err := PtrMultipartUploadToProto(resp.U)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.StartUploadResponse{Upload: respU}, nil
+}
+
+func _Encode_CompleteUpload_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil CompleteUploadResponse")
+	}
+	resp := response.(*transport.CompleteUploadResponse)
+	respU, err := PtrMultipartUploadToProto(resp.U)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.CompleteUploadResponse{Upload: respU}, nil
+}
+
+func _Encode_AbortUpload_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_MoveUpload_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil MoveUploadResponse")
+	}
+	resp := response.(*transport.MoveUploadResponse)
+	respFile, err := PtrFileToProto(resp.File)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.MoveUploadResponse{File: respFile}, nil
+}
+
+func _Encode_DeleteFile_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_StartUpload_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil StartUploadRequest")
+	}
+	req := request.(*pb.StartUploadRequest)
+	reqUpload, err := ProtoToPtrMultipartUpload(req.Upload)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.StartUploadRequest{Upload: reqUpload}, nil
+}
+
+func _Decode_CompleteUpload_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil CompleteUploadRequest")
+	}
+	req := request.(*pb.CompleteUploadRequest)
+	reqUpload, err := ProtoToPtrMultipartUpload(req.Upload)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CompleteUploadRequest{Upload: reqUpload}, nil
+}
+
+func _Decode_AbortUpload_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil AbortUploadRequest")
+	}
+	req := request.(*pb.AbortUploadRequest)
+	reqUpload, err := ProtoToPtrMultipartUpload(req.Upload)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.AbortUploadRequest{Upload: reqUpload}, nil
+}
+
+func _Decode_MoveUpload_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil MoveUploadRequest")
+	}
+	req := request.(*pb.MoveUploadRequest)
+	reqUpload, err := ProtoToPtrMultipartUpload(req.Upload)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.MoveUploadRequest{Upload: reqUpload}, nil
+}
+
+func _Decode_DeleteFile_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil DeleteFileRequest")
+	}
+	req := request.(*pb.DeleteFileRequest)
+	reqFile, err := ProtoToPtrFile(req.File)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.DeleteFileRequest{File: reqFile}, nil
+}
+
+func _Decode_StartUpload_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil StartUploadResponse")
+	}
+	resp := response.(*pb.StartUploadResponse)
+	respU, err := ProtoToPtrMultipartUpload(resp.Upload)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.StartUploadResponse{U: respU}, nil
+}
+
+func _Decode_CompleteUpload_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil CompleteUploadResponse")
+	}
+	resp := response.(*pb.CompleteUploadResponse)
+	respU, err := ProtoToPtrMultipartUpload(resp.Upload)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.CompleteUploadResponse{U: respU}, nil
+}
+
+func _Decode_AbortUpload_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Decode_MoveUpload_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil MoveUploadResponse")
+	}
+	resp := response.(*pb.MoveUploadResponse)
+	respFile, err := ProtoToPtrFile(resp.File)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.MoveUploadResponse{File: respFile}, nil
+}
+
+func _Decode_DeleteFile_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	return &empty.Empty{}, nil
+}
+
+func _Encode_GetFile_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetFileRequest")
+	}
+	req := request.(*transport.GetFileRequest)
+	return &pb.GetFileRequest{Id: req.File.ID}, nil
+}
+
+func _Encode_GetFile_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetFileResponse")
+	}
+	resp := response.(*transport.GetFileResponse)
+	respF, err := PtrFileToProto(resp.F)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.GetFileResponse{File: respF}, nil
+}
+
+func _Decode_GetFile_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil GetFileRequest")
+	}
+	req := request.(*pb.GetFileRequest)
+	return &transport.GetFileRequest{File: &files.File{ID: req.Id}}, nil
+}
+
+func _Decode_GetFile_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil GetFileResponse")
+	}
+	resp := response.(*pb.GetFileResponse)
+	respF, err := ProtoToPtrFile(resp.File)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.GetFileResponse{F: respF}, nil
+}
+
+func _Encode_Upload_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil UploadRequest")
+	}
+	req := request.(*transport.UploadRequest)
+	reqFile, err := PtrFileToProto(req.File)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.UploadRequest{File: reqFile}, nil
+}
+
+func _Encode_Upload_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil UploadResponse")
+	}
+	resp := response.(*transport.UploadResponse)
+	respFile, err := PtrUploadToProto(resp.U)
+	if err != nil {
+		return nil, err
+	}
+	return &pb.UploadResponse{Upload: respFile}, nil
+}
+
+func _Decode_Upload_Request(ctx context.Context, request interface{}) (interface{}, error) {
+	if request == nil {
+		return nil, errors.New("nil UploadRequest")
+	}
+	req := request.(*pb.UploadRequest)
+	reqFile, err := ProtoToPtrFile(req.File)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.UploadRequest{File: reqFile}, nil
+}
+
+func _Decode_Upload_Response(ctx context.Context, response interface{}) (interface{}, error) {
+	if response == nil {
+		return nil, errors.New("nil UploadResponse")
+	}
+	resp := response.(*pb.UploadResponse)
+	respUpload, err := ProtoToPtrUpload(resp.Upload)
+	if err != nil {
+		return nil, err
+	}
+	return &transport.UploadResponse{U: respUpload}, nil
+}
diff --git a/pkg/files/transport/grpc/protobuf_type_converters.microgen.go b/pkg/files/transport/grpc/protobuf_type_converters.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..812c21d620ea42827db1014f9ea52b457d3b2121
--- /dev/null
+++ b/pkg/files/transport/grpc/protobuf_type_converters.microgen.go
@@ -0,0 +1,118 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+// It is better for you if you do not change functions names!
+// This files will never be overwritten.
+package transportgrpc
+
+import (
+	pb "git.perx.ru/perxis/perxis-go/proto/files"
+	"git.perx.ru/perxis/perxis/services/files"
+)
+
+func PtrMultipartUploadToProto(upload *files.MultipartUpload) (*pb.MultipartUpload, error) {
+	if upload == nil {
+		return nil, nil
+	}
+	pbUpload := &pb.MultipartUpload{
+		UploadId: upload.UploadID,
+		PartSize: int32(upload.PartSize),
+		PartUrls: upload.PartURLs,
+	}
+	pbUpload.Parts = make([]*pb.CompletedPart, 0, len(upload.Parts))
+	for _, part := range upload.Parts {
+		pbUpload.Parts = append(pbUpload.Parts, &pb.CompletedPart{
+			Number: int32(part.Number),
+			Id:     part.ID,
+		})
+	}
+	pbFile, err := PtrFileToProto(&upload.File)
+	if err != nil {
+		return nil, err
+	}
+	pbUpload.File = pbFile
+	return pbUpload, nil
+}
+
+func ProtoToPtrMultipartUpload(protoUpload *pb.MultipartUpload) (*files.MultipartUpload, error) {
+	if protoUpload == nil {
+		return nil, nil
+	}
+	upload := &files.MultipartUpload{
+		UploadID: protoUpload.UploadId,
+		PartSize: int(protoUpload.PartSize),
+		PartURLs: protoUpload.PartUrls,
+	}
+	upload.Parts = make([]*files.CompletedPart, 0, len(protoUpload.Parts))
+	for _, part := range protoUpload.Parts {
+		upload.Parts = append(upload.Parts, &files.CompletedPart{
+			Number: int(part.Number),
+			ID:     part.Id,
+		})
+	}
+	file, err := ProtoToPtrFile(protoUpload.File)
+	if err != nil {
+		return nil, err
+	}
+	if file != nil {
+		upload.File = *file
+	}
+	return upload, nil
+}
+
+func PtrUploadToProto(upload *files.Upload) (*pb.Upload, error) {
+	if upload == nil {
+		return nil, nil
+	}
+	pbUpload := &pb.Upload{
+		UploadUrl: upload.UploadURL,
+	}
+	pbFile, err := PtrFileToProto(&upload.File)
+	if err != nil {
+		return nil, err
+	}
+	pbUpload.File = pbFile
+	return pbUpload, nil
+}
+
+func ProtoToPtrUpload(protoUpload *pb.Upload) (*files.Upload, error) {
+	if protoUpload == nil {
+		return nil, nil
+	}
+	upload := &files.Upload{
+		UploadURL: protoUpload.UploadUrl,
+	}
+	file, err := ProtoToPtrFile(protoUpload.File)
+	if err != nil {
+		return nil, err
+	}
+	upload.File = *file
+	return upload, nil
+}
+
+func PtrFileToProto(file *files.File) (*pb.File, error) {
+	if file == nil {
+		return nil, nil
+	}
+	pbFile := &pb.File{
+		Id:       file.ID,
+		Name:     file.Name,
+		Size:     int32(file.Size),
+		MimeType: file.MimeType,
+		Url:      file.URL,
+	}
+	return pbFile, nil
+}
+
+func ProtoToPtrFile(protoFile *pb.File) (*files.File, error) {
+	if protoFile == nil {
+		return nil, nil
+	}
+	file := &files.File{
+		ID:       protoFile.Id,
+		Name:     protoFile.Name,
+		Size:     int(protoFile.Size),
+		MimeType: protoFile.MimeType,
+		URL:      protoFile.Url,
+	}
+	return file, nil
+}
diff --git a/pkg/files/transport/grpc/server.microgen.go b/pkg/files/transport/grpc/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..7f08a8d9515816328ba48dce63a7f72cd2108edc
--- /dev/null
+++ b/pkg/files/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 (
+	pb "git.perx.ru/perxis/perxis-go/proto/files"
+	transport "git.perx.ru/perxis/perxis/services/files/transport"
+	grpc "github.com/go-kit/kit/transport/grpc"
+	empty "github.com/golang/protobuf/ptypes/empty"
+	context "golang.org/x/net/context"
+)
+
+type filesServer struct {
+	startUpload    grpc.Handler
+	completeUpload grpc.Handler
+	abortUpload    grpc.Handler
+	moveUpload     grpc.Handler
+	upload         grpc.Handler
+	getFile        grpc.Handler
+	deleteFile     grpc.Handler
+
+	pb.UnimplementedFilesServer
+}
+
+func NewGRPCServer(endpoints *transport.EndpointsSet, opts ...grpc.ServerOption) pb.FilesServer {
+	return &filesServer{
+		abortUpload: grpc.NewServer(
+			endpoints.AbortUploadEndpoint,
+			_Decode_AbortUpload_Request,
+			_Encode_AbortUpload_Response,
+			opts...,
+		),
+		completeUpload: grpc.NewServer(
+			endpoints.CompleteUploadEndpoint,
+			_Decode_CompleteUpload_Request,
+			_Encode_CompleteUpload_Response,
+			opts...,
+		),
+		deleteFile: grpc.NewServer(
+			endpoints.DeleteFileEndpoint,
+			_Decode_DeleteFile_Request,
+			_Encode_DeleteFile_Response,
+			opts...,
+		),
+		getFile: grpc.NewServer(
+			endpoints.GetFileEndpoint,
+			_Decode_GetFile_Request,
+			_Encode_GetFile_Response,
+			opts...,
+		),
+		moveUpload: grpc.NewServer(
+			endpoints.MoveUploadEndpoint,
+			_Decode_MoveUpload_Request,
+			_Encode_MoveUpload_Response,
+			opts...,
+		),
+		startUpload: grpc.NewServer(
+			endpoints.StartUploadEndpoint,
+			_Decode_StartUpload_Request,
+			_Encode_StartUpload_Response,
+			opts...,
+		),
+		upload: grpc.NewServer(
+			endpoints.UploadEndpoint,
+			_Decode_Upload_Request,
+			_Encode_Upload_Response,
+			opts...,
+		),
+	}
+}
+
+func (S *filesServer) StartUpload(ctx context.Context, req *pb.StartUploadRequest) (*pb.StartUploadResponse, error) {
+	_, resp, err := S.startUpload.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.StartUploadResponse), nil
+}
+
+func (S *filesServer) CompleteUpload(ctx context.Context, req *pb.CompleteUploadRequest) (*pb.CompleteUploadResponse, error) {
+	_, resp, err := S.completeUpload.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.CompleteUploadResponse), nil
+}
+
+func (S *filesServer) AbortUpload(ctx context.Context, req *pb.AbortUploadRequest) (*empty.Empty, error) {
+	_, resp, err := S.abortUpload.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
+
+func (S *filesServer) MoveUpload(ctx context.Context, req *pb.MoveUploadRequest) (*pb.MoveUploadResponse, error) {
+	_, resp, err := S.moveUpload.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.MoveUploadResponse), nil
+}
+
+func (S *filesServer) Upload(ctx context.Context, req *pb.UploadRequest) (*pb.UploadResponse, error) {
+	_, resp, err := S.upload.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.UploadResponse), nil
+}
+
+func (S *filesServer) GetFile(ctx context.Context, req *pb.GetFileRequest) (*pb.GetFileResponse, error) {
+	_, resp, err := S.getFile.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*pb.GetFileResponse), nil
+}
+
+func (S *filesServer) DeleteFile(ctx context.Context, req *pb.DeleteFileRequest) (*empty.Empty, error) {
+	_, resp, err := S.deleteFile.ServeGRPC(ctx, req)
+	if err != nil {
+		return nil, err
+	}
+	return resp.(*empty.Empty), nil
+}
diff --git a/pkg/files/transport/http/server.go b/pkg/files/transport/http/server.go
new file mode 100644
index 0000000000000000000000000000000000000000..644cb6213a44c873b565b245294d89384d819241
--- /dev/null
+++ b/pkg/files/transport/http/server.go
@@ -0,0 +1,41 @@
+package transporthttp
+
+import (
+	"context"
+	"net/http"
+
+	"git.perx.ru/perxis/perxis/internal/urlsigner"
+	"git.perx.ru/perxis/perxis/services/files"
+	"github.com/gorilla/mux"
+)
+
+func NewHTTPHandler(fs files.Files, s urlsigner.URLSigner) http.Handler {
+	m := mux.NewRouter()
+	m.Methods("GET").
+		Path("/{id}").
+		HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+			if !s.Check(r.URL) {
+				w.WriteHeader(http.StatusForbidden)
+				return
+			}
+			w.Header().Set("Content-Type", "text/html; charset=utf-8")
+
+			f := &files.File{
+				ID: mux.Vars(r)["id"],
+			}
+			res, err := fs.GetFile(context.Background(), f)
+			if err != nil {
+				w.WriteHeader(http.StatusInternalServerError)
+				w.Write([]byte(err.Error()))
+				return
+			}
+
+			if res == nil || res.URL == "" {
+				w.WriteHeader(http.StatusNotFound)
+				return
+			}
+			w.Header().Set("Location", res.URL)
+			w.WriteHeader(http.StatusMovedPermanently)
+		})
+	return m
+}
diff --git a/pkg/files/transport/server.microgen.go b/pkg/files/transport/server.microgen.go
new file mode 100644
index 0000000000000000000000000000000000000000..8eece9940bd2c41800efaa8fc50c3c5790186952
--- /dev/null
+++ b/pkg/files/transport/server.microgen.go
@@ -0,0 +1,77 @@
+// Code generated by microgen 0.9.1. DO NOT EDIT.
+
+package transport
+
+import (
+	"context"
+	files "git.perx.ru/perxis/perxis/services/files"
+	endpoint "github.com/go-kit/kit/endpoint"
+)
+
+func Endpoints(svc files.Files) EndpointsSet {
+	return EndpointsSet{
+		AbortUploadEndpoint:    AbortUploadEndpoint(svc),
+		CompleteUploadEndpoint: CompleteUploadEndpoint(svc),
+		DeleteFileEndpoint:     DeleteFileEndpoint(svc),
+		GetFileEndpoint:        GetFileEndpoint(svc),
+		MoveUploadEndpoint:     MoveUploadEndpoint(svc),
+		StartUploadEndpoint:    StartUploadEndpoint(svc),
+		UploadEndpoint:         UploadEndpoint(svc),
+	}
+}
+
+func StartUploadEndpoint(svc files.Files) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*StartUploadRequest)
+		res0, res1 := svc.StartUpload(arg0, req.Upload)
+		return &StartUploadResponse{U: res0}, res1
+	}
+}
+
+func CompleteUploadEndpoint(svc files.Files) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*CompleteUploadRequest)
+		res0, res1 := svc.CompleteUpload(arg0, req.Upload)
+		return &CompleteUploadResponse{U: res0}, res1
+	}
+}
+
+func AbortUploadEndpoint(svc files.Files) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*AbortUploadRequest)
+		res0 := svc.AbortUpload(arg0, req.Upload)
+		return &AbortUploadResponse{}, res0
+	}
+}
+
+func MoveUploadEndpoint(svc files.Files) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*MoveUploadRequest)
+		res0, res1 := svc.MoveUpload(arg0, req.Upload)
+		return &MoveUploadResponse{File: res0}, res1
+	}
+}
+
+func UploadEndpoint(svc files.Files) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*UploadRequest)
+		res0, res1 := svc.Upload(arg0, req.File)
+		return &UploadResponse{U: res0}, res1
+	}
+}
+
+func GetFileEndpoint(svc files.Files) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*GetFileRequest)
+		res0, res1 := svc.GetFile(arg0, req.File)
+		return &GetFileResponse{F: res0}, res1
+	}
+}
+
+func DeleteFileEndpoint(svc files.Files) endpoint.Endpoint {
+	return func(arg0 context.Context, request interface{}) (interface{}, error) {
+		req := request.(*DeleteFileRequest)
+		res0 := svc.DeleteFile(arg0, req.File)
+		return &DeleteFileResponse{}, res0
+	}
+}