diff --git a/id/bson.go b/id/bson.go
new file mode 100644
index 0000000000000000000000000000000000000000..9d44c5a8074361b381524dd7c99ad77906cfc68c
--- /dev/null
+++ b/id/bson.go
@@ -0,0 +1,32 @@
+package id
+
+import (
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/bsonrw"
+	"go.mongodb.org/mongo-driver/bson/bsontype"
+)
+
+func (id *ID) MarshalBSONValue() (bsontype.Type, []byte, error) {
+	return bson.MarshalValue(id.String())
+}
+
+func (id *ID) UnmarshalBSONValue(btype bsontype.Type, data []byte) error {
+	if btype != bson.TypeString {
+		return errors.New("cannot unmarshal non-string bson value to ID")
+	}
+	dec, err := bson.NewDecoder(bsonrw.NewBSONValueReader(btype, data))
+	if err != nil {
+		return err
+	}
+	var str string
+	if err = dec.Decode(&str); err != nil {
+		return err
+	}
+	t, err := Parse(str)
+	if err != nil {
+		return err
+	}
+	*id = *t
+	return nil
+}
diff --git a/id/bson_test.go b/id/bson_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..c8080b5d7ae12181343a8f6502e76484c414e578
--- /dev/null
+++ b/id/bson_test.go
@@ -0,0 +1,107 @@
+package id
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"go.mongodb.org/mongo-driver/bson"
+)
+
+func TestID_MarshalUnmarshalBSON(t *testing.T) {
+	tests := []struct {
+		name string
+		id   *ID
+	}{
+		{
+			name: "OrganizationID",
+			id:   &ID{Descriptor: &OrganizationID{OrganizationID: "1"}},
+		},
+		{
+			name: "UserID",
+			id:   &ID{Descriptor: &UserID{UserID: "1"}},
+		},
+		{
+			name: "ServiceID",
+			id:   &ID{Descriptor: &ServiceID{ServiceID: "1"}},
+		},
+		{
+			name: "SpaceID",
+			id:   &ID{Descriptor: &SpaceID{SpaceID: "1"}},
+		},
+		{
+			name: "EnvironmentID",
+			id:   &ID{Descriptor: &EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+		},
+		{
+			name: "ClientID",
+			id:   &ID{Descriptor: &ClientID{ClientID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+		},
+		{
+			name: "RoleID",
+			id:   &ID{Descriptor: &RoleID{RoleID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+		},
+		{
+			name: "CollectionID",
+			id:   &ID{Descriptor: &CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
+		},
+		{
+			name: "SchemaID",
+			id:   &ID{Descriptor: &SchemaID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
+		},
+		{
+			name: "ItemID",
+			id:   &ID{Descriptor: &ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}},
+		},
+		{
+			name: "RevisionID",
+			id:   &ID{Descriptor: &RevisionID{RevisionID: "1", ItemID: ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
+		},
+		{
+			name: "FieldID",
+			id:   &ID{Descriptor: &FieldID{FieldName: "1", ItemID: ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
+		},
+		{
+			name: "SystemID",
+			id:   &ID{Descriptor: &SystemID{}},
+		},
+	}
+	type test struct {
+		Text string
+		ID   *ID
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := &test{Text: tt.name, ID: tt.id}
+
+			b, err := bson.Marshal(s)
+			require.NoError(t, err)
+
+			var v *test
+			require.NoError(t, bson.Unmarshal(b, &v))
+			assert.Equal(t, s, v, "после Unmarshal объект должен быть идентичен исходному")
+		})
+	}
+}
+
+func TestID_ExampleBSON(t *testing.T) {
+	type data struct {
+		ID     *ID
+		Text   string
+		Number int
+	}
+
+	test := &data{
+		ID:     &ID{Descriptor: &SpaceID{SpaceID: Space}},
+		Text:   "text",
+		Number: 1,
+	}
+
+	b, err := bson.Marshal(test)
+	require.NoError(t, err)
+
+	buf := new(data)
+	err = bson.Unmarshal(b, &buf)
+	require.NoError(t, err)
+	assert.Equal(t, test, buf, "после Unmarshal объект должен совпадать с исходным")
+}
diff --git a/id/client.go b/id/client.go
new file mode 100644
index 0000000000000000000000000000000000000000..db941c5ad54b539551193004674ac15f15d314be
--- /dev/null
+++ b/id/client.go
@@ -0,0 +1,61 @@
+package id
+
+const (
+	Client        = "client"
+	ClientsPrefix = "clients"
+)
+
+type ClientID struct {
+	SpaceID
+	ClientID string `json:"client_id,omitempty" bson:"client_id,omitempty"`
+}
+
+func (t *ClientID) Type() string { return Client }
+
+func (t *ClientID) String() string {
+	return Join(t.SpaceID.String(), ClientsPrefix, t.ClientID)
+
+}
+
+func (t *ClientID) ToMap() map[string]any {
+	m := t.SpaceID.ToMap()
+	m["client_id"] = t.ClientID
+	m["type"] = Client
+	return m
+}
+
+func (t *ClientID) FromMap(m map[string]any) error {
+	if err := t.SpaceID.FromMap(m); err != nil {
+		return err
+	}
+	t.ClientID = m["client_id"].(string)
+	return nil
+}
+
+func (t *ClientID) Validate() error {
+	if t.ClientID == "" {
+		return ErrInvalidID
+	}
+
+	return t.SpaceID.Validate()
+}
+
+func parseClientID(parts []string) (*ClientID, error) {
+	if len(parts) != 4 || parts[2] != ClientsPrefix {
+		return nil, ErrInvalidID
+	}
+
+	spaceID, err := parseSpaceID(parts[:2])
+	if err != nil {
+		return nil, err
+	}
+
+	var id ClientID
+	id.SpaceID = *spaceID
+	id.ClientID = parts[3]
+	return &id, nil
+}
+
+func NewClientID(spaceID, id string) *ID {
+	return &ID{Descriptor: &ClientID{SpaceID: SpaceID{SpaceID: spaceID}, ClientID: id}}
+}
diff --git a/id/collection.go b/id/collection.go
new file mode 100644
index 0000000000000000000000000000000000000000..e0f37585b41e77b58530cc50f249283beb6d8eca
--- /dev/null
+++ b/id/collection.go
@@ -0,0 +1,60 @@
+package id
+
+const (
+	Collection        = "collection"
+	CollectionsPrefix = "cols"
+)
+
+type CollectionID struct {
+	EnvironmentID
+	CollectionID string `json:"col_id,omitempty" bson:"col_id, omitempty"`
+}
+
+func (t *CollectionID) Type() string { return Collection }
+
+func (t *CollectionID) String() string {
+	return Join(t.EnvironmentID.String(), CollectionsPrefix, t.CollectionID)
+}
+
+func (t *CollectionID) ToMap() map[string]any {
+	m := t.EnvironmentID.ToMap()
+	m["col_id"] = t.CollectionID
+	m["type"] = Collection
+	return m
+}
+
+func (t *CollectionID) FromMap(m map[string]any) error {
+	if err := t.EnvironmentID.FromMap(m); err != nil {
+		return err
+	}
+	t.CollectionID = m["col_id"].(string)
+	return nil
+}
+
+func (t *CollectionID) Validate() error {
+	if t.CollectionID == "" {
+		return ErrInvalidID
+	}
+
+	return t.EnvironmentID.Validate()
+}
+
+func parseCollectionID(parts []string) (*CollectionID, error) {
+	if len(parts) != 6 || parts[4] != CollectionsPrefix {
+		return nil, ErrInvalidID
+	}
+
+	envID, err := parseEnvironmentID(parts[:4])
+	if err != nil {
+		return nil, err
+	}
+
+	var id CollectionID
+	id.CollectionID = parts[5]
+	id.EnvironmentID = *envID
+	return &id, nil
+}
+
+func NewCollectionID(spaceID, envID, id string) *ID {
+	return &ID{Descriptor: &CollectionID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: id}}
+}
diff --git a/id/environment.go b/id/environment.go
new file mode 100644
index 0000000000000000000000000000000000000000..d42df3e658bc04ae70989d199257a7836bd30f00
--- /dev/null
+++ b/id/environment.go
@@ -0,0 +1,61 @@
+package id
+
+const (
+	Environment        = "environment"
+	EnvironmentsPrefix = "envs"
+)
+
+type EnvironmentID struct {
+	SpaceID
+	EnvironmentID string `json:"env_id,omitempty" bson:"env_id,omitempty"`
+}
+
+func (t *EnvironmentID) Type() string { return Environment }
+
+func (t *EnvironmentID) String() string {
+	return Join(t.SpaceID.String(), EnvironmentsPrefix, t.EnvironmentID)
+
+}
+
+func (t *EnvironmentID) ToMap() map[string]any {
+	m := t.SpaceID.ToMap()
+	m["env_id"] = t.EnvironmentID
+	m["type"] = Environment
+	return m
+}
+
+func (t *EnvironmentID) FromMap(m map[string]any) error {
+	if err := t.SpaceID.FromMap(m); err != nil {
+		return err
+	}
+	t.EnvironmentID = m["env_id"].(string)
+	return nil
+}
+
+func (t *EnvironmentID) Validate() error {
+	if t.EnvironmentID == "" {
+		return ErrInvalidID
+	}
+
+	return t.SpaceID.Validate()
+}
+
+func parseEnvironmentID(parts []string) (*EnvironmentID, error) {
+	if len(parts) != 4 || parts[2] != EnvironmentsPrefix {
+		return nil, ErrInvalidID
+	}
+
+	spaceID, err := parseSpaceID(parts[:2])
+	if err != nil {
+		return nil, err
+	}
+
+	var id EnvironmentID
+	id.EnvironmentID = parts[3]
+	id.SpaceID = *spaceID
+	return &id, nil
+}
+
+func NewEnvironmentID(spaceID, id string) *ID {
+	return &ID{Descriptor: &EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: id}}
+}
diff --git a/id/field.go b/id/field.go
new file mode 100644
index 0000000000000000000000000000000000000000..ca1577552dc7df38d555549ddfc53a51e70e63f1
--- /dev/null
+++ b/id/field.go
@@ -0,0 +1,60 @@
+package id
+
+const (
+	Field        = "field"
+	FieldsPrefix = "fields"
+)
+
+type FieldID struct {
+	ItemID
+	FieldName string `json:"field_name,omitempty" bson:"field_name,omitempty"`
+}
+
+func (t *FieldID) Type() string { return Field }
+
+func (t *FieldID) String() string {
+	return Join(t.ItemID.String(), FieldsPrefix, t.FieldName)
+
+}
+
+func (t *FieldID) ToMap() map[string]any {
+	m := t.ItemID.ToMap()
+	m["field_name"] = t.FieldName
+	m["type"] = Field
+	return m
+}
+
+func (t *FieldID) FromMap(m map[string]any) error {
+	if err := t.ItemID.FromMap(m); err != nil {
+		return err
+	}
+	t.FieldName = m["field_name"].(string)
+	return nil
+}
+
+func (t *FieldID) Validate() error {
+	if t.FieldName == "" {
+		return ErrInvalidID
+	}
+
+	return t.ItemID.Validate()
+}
+
+func parseFieldID(parts []string) (*FieldID, error) {
+	if len(parts) != 10 || parts[8] != FieldsPrefix {
+		return nil, ErrInvalidID
+	}
+
+	itemID, err := parseItemID(parts[:8])
+	if err != nil {
+		return nil, err
+	}
+
+	var id FieldID
+	id.ItemID = *itemID
+	id.FieldName = parts[9]
+	return &id, nil
+}
+func NewFieldID(spaceID, envID, collID, itemID, id string) *ID {
+	return &ID{Descriptor: &FieldID{ItemID: ItemID{CollectionID: CollectionID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: collID}, ItemID: itemID}, FieldName: id}}
+}
diff --git a/id/id.go b/id/id.go
new file mode 100644
index 0000000000000000000000000000000000000000..ca75c29f94b6fe9100b435e8f546835991bc9fa3
--- /dev/null
+++ b/id/id.go
@@ -0,0 +1,143 @@
+package id
+
+import (
+	"strings"
+
+	"git.perx.ru/perxis/perxis-go/pkg/errors"
+)
+
+const Separator = '/'
+
+var (
+	ErrInvalidID = errors.New("invalid id")
+)
+
+type Descriptor interface {
+	String() string
+	Type() string
+	ToMap() map[string]any
+	FromMap(map[string]any) error
+	Validate() error
+}
+
+type ID struct {
+	Descriptor
+}
+
+func Parse(s string) (*ID, error) {
+	parts := Split(s)
+
+	if id, _ := parseServiceID(parts); id != nil {
+		return &ID{Descriptor: id}, nil
+	}
+
+	if id, _ := parseUserID(parts); id != nil {
+		return &ID{Descriptor: id}, nil
+	}
+
+	if id, _ := parseOrganizationID(parts); id != nil {
+		return &ID{Descriptor: id}, nil
+	}
+
+	if id, _ := parseSpaceID(parts); id != nil {
+		return &ID{Descriptor: id}, nil
+	}
+
+	if id, _ := parseEnvironmentID(parts); id != nil {
+		return &ID{Descriptor: id}, nil
+	}
+
+	if id, _ := parseClientID(parts); id != nil {
+		return &ID{Descriptor: id}, nil
+	}
+
+	if id, _ := parseRoleID(parts); id != nil {
+		return &ID{Descriptor: id}, nil
+	}
+
+	if id, _ := parseCollectionID(parts); id != nil {
+		return &ID{Descriptor: id}, nil
+	}
+
+	if id, _ := parseSchemaID(parts); id != nil {
+		return &ID{Descriptor: id}, nil
+	}
+
+	if id, _ := parseItemID(parts); id != nil {
+		return &ID{Descriptor: id}, nil
+	}
+
+	if id, _ := parseRevisionID(parts); id != nil {
+		return &ID{Descriptor: id}, nil
+	}
+
+	if id, _ := parseFieldID(parts); id != nil {
+		return &ID{Descriptor: id}, nil
+	}
+
+	if id, _ := parseSystemID(parts); id != nil {
+		return &ID{Descriptor: id}, nil
+	}
+
+	return nil, ErrInvalidID
+}
+
+func Split(id string) []string {
+	if id[0] != Separator {
+		return nil
+	}
+	return strings.Split(id[1:], string(Separator))
+}
+
+func Join(parts ...string) string {
+	s := strings.Join(parts, string(Separator))
+	if s[0] != Separator {
+		s = string(Separator) + s
+	}
+	return s
+}
+
+func FromMap(m map[string]any) (*ID, error) {
+	if m == nil {
+		return nil, errors.New("nil map")
+	}
+
+	v := new(ID)
+
+	switch m["type"] {
+	case Organization:
+		v.Descriptor = new(OrganizationID)
+	case Service:
+		v.Descriptor = new(ServiceID)
+	case User:
+		v.Descriptor = new(UserID)
+	case Space:
+		v.Descriptor = new(SpaceID)
+	case Environment:
+		v.Descriptor = new(EnvironmentID)
+	case Client:
+		v.Descriptor = new(ClientID)
+	case Role:
+		v.Descriptor = new(RoleID)
+	case Collection:
+		v.Descriptor = new(CollectionID)
+	case Schema:
+		v.Descriptor = new(SchemaID)
+	case Item:
+		v.Descriptor = new(ItemID)
+	case Revision:
+		v.Descriptor = new(RevisionID)
+	case Field:
+		v.Descriptor = new(FieldID)
+	case System:
+		v.Descriptor = new(SystemID)
+	default:
+		return nil, errors.New("unknown type")
+	}
+
+	if err := v.Descriptor.FromMap(m); err != nil {
+		return nil, err
+	}
+
+	return v, nil
+}
diff --git a/id/id_test.go b/id/id_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..041f1cdc6018e6c84c0c4c6a4be4b3944ef34082
--- /dev/null
+++ b/id/id_test.go
@@ -0,0 +1,301 @@
+package id
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func Test_ParseID(t *testing.T) {
+	tests := []struct {
+		name      string
+		id        string
+		result    *ID
+		wantError bool
+	}{
+		{
+			name:   "ServiceID",
+			id:     "/services/<service_id>",
+			result: &ID{Descriptor: &ServiceID{ServiceID: "<service_id>"}},
+		},
+		{
+			name:   "UserID",
+			id:     "/users/<user_id>",
+			result: &ID{Descriptor: &UserID{UserID: "<user_id>"}},
+		},
+		{
+			name:   "OrganizationID",
+			id:     "/orgs/<org_id>",
+			result: &ID{Descriptor: &OrganizationID{OrganizationID: "<org_id>"}},
+		},
+		{
+			name:   "SpaceID",
+			id:     "/spaces/<space_id>",
+			result: &ID{Descriptor: &SpaceID{SpaceID: "<space_id>"}},
+		},
+		{
+			name: "ClientID",
+			id:   "/spaces/<space_id>/clients/<client_id>",
+			result: &ID{Descriptor: &ClientID{
+				SpaceID:  SpaceID{SpaceID: "<space_id>"},
+				ClientID: "<client_id>",
+			}},
+		},
+		{
+			name: "RoleID",
+			id:   "/spaces/<space_id>/roles/<role_id>",
+			result: &ID{Descriptor: &RoleID{
+				SpaceID: SpaceID{SpaceID: "<space_id>"},
+				RoleID:  "<role_id>",
+			}},
+		},
+		{
+			name: "EnvironmentID",
+			id:   "/spaces/<space_id>/envs/<env_id>",
+			result: &ID{Descriptor: &EnvironmentID{
+				SpaceID:       SpaceID{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+		},
+		{
+			name: "CollectionID",
+			id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>",
+			result: &ID{Descriptor: &CollectionID{
+				EnvironmentID: EnvironmentID{
+					SpaceID:       SpaceID{SpaceID: "<space_id>"},
+					EnvironmentID: "<env_id>",
+				},
+				CollectionID: "<collection_id>",
+			}},
+		},
+		{
+			name: "SchemaID",
+			id:   "/spaces/<space_id>/envs/<env_id>/schema/<collection_id>",
+			result: &ID{Descriptor: &SchemaID{
+				EnvironmentID: EnvironmentID{
+					SpaceID:       SpaceID{SpaceID: "<space_id>"},
+					EnvironmentID: "<env_id>",
+				},
+				CollectionID: "<collection_id>",
+			}},
+		},
+		{
+			name: "ItemID",
+			id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>",
+			result: &ID{Descriptor: &ItemID{
+				CollectionID: CollectionID{
+					EnvironmentID: EnvironmentID{
+						SpaceID:       SpaceID{SpaceID: "<space_id>"},
+						EnvironmentID: "<env_id>",
+					},
+					CollectionID: "<collection_id>",
+				},
+				ItemID: "<item_id>",
+			}},
+		},
+		{
+			name: "RevisionID",
+			id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/revs/<rev_id>",
+			result: &ID{Descriptor: &RevisionID{
+				ItemID: ItemID{
+					CollectionID: CollectionID{
+						EnvironmentID: EnvironmentID{
+							SpaceID:       SpaceID{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				RevisionID: "<rev_id>",
+			}},
+		},
+		{
+			name: "FieldID",
+			id:   "/spaces/<space_id>/envs/<env_id>/cols/<collection_id>/items/<item_id>/fields/<field_name>",
+			result: &ID{Descriptor: &FieldID{
+				ItemID: ItemID{
+					CollectionID: CollectionID{
+						EnvironmentID: EnvironmentID{
+							SpaceID:       SpaceID{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				FieldName: "<field_name>",
+			}},
+		},
+		{
+			name:   "SystemID",
+			id:     "/system",
+			result: &ID{Descriptor: &SystemID{}},
+		},
+		{
+			name:      "With error #1: no backslash in the beginning of id",
+			id:        "spaces/<space_id>",
+			result:    nil,
+			wantError: true,
+		},
+		{
+			name:      "With error #2: backslash in the end of id",
+			id:        "/spaces/<space_id>/",
+			result:    nil,
+			wantError: true,
+		},
+		{
+			name:      "With error #3: typo in 'spaces'",
+			id:        "/space/<space_id>",
+			result:    nil,
+			wantError: true,
+		},
+		{
+			name:      "With error #4: no space_id in id",
+			id:        "/spaces",
+			result:    nil,
+			wantError: true,
+		},
+		{
+			name:      "With error #5: multiple backslashes in the end of id",
+			id:        "/spaces/<space_id>///",
+			result:    nil,
+			wantError: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			id, err := Parse(tt.id)
+			if tt.wantError {
+				require.Error(t, err)
+				return
+			}
+			require.NoError(t, err)
+			require.Equal(t, tt.result, id)
+			require.Equal(t, tt.id, id.String(), "проверяем корректность работы метода String, полученное ID должно совпадать с исходным")
+		})
+	}
+}
+
+func Test_Map(t *testing.T) {
+	tests := []struct {
+		name string
+		id   *ID
+	}{
+		{
+			name: "ServiceID",
+			id:   &ID{Descriptor: &ServiceID{ServiceID: "<service_id>"}},
+		},
+		{
+			name: "UserID",
+			id:   &ID{Descriptor: &UserID{UserID: "<user_id>"}},
+		},
+		{
+			name: "OrganizationID",
+			id:   &ID{Descriptor: &OrganizationID{OrganizationID: "<org_id>"}},
+		},
+		{
+			name: "SpaceID",
+			id:   &ID{Descriptor: &SpaceID{SpaceID: "<space_id>"}},
+		},
+		{
+			name: "ClientID",
+			id: &ID{Descriptor: &ClientID{
+				SpaceID:  SpaceID{SpaceID: "<space_id>"},
+				ClientID: "<client_id>",
+			}},
+		},
+		{
+			name: "RoleID",
+			id: &ID{Descriptor: &RoleID{
+				SpaceID: SpaceID{SpaceID: "<space_id>"},
+				RoleID:  "<role_id>",
+			}},
+		},
+		{
+			name: "EnvironmentID",
+			id: &ID{Descriptor: &EnvironmentID{
+				SpaceID:       SpaceID{SpaceID: "<space_id>"},
+				EnvironmentID: "<env_id>",
+			}},
+		},
+		{
+			name: "CollectionID",
+			id: &ID{Descriptor: &CollectionID{
+				EnvironmentID: EnvironmentID{
+					SpaceID:       SpaceID{SpaceID: "<space_id>"},
+					EnvironmentID: "<env_id>",
+				},
+				CollectionID: "<collection_id>",
+			}},
+		},
+		{
+			name: "Schema ID",
+			id: &ID{Descriptor: &SchemaID{
+				EnvironmentID: EnvironmentID{
+					SpaceID:       SpaceID{SpaceID: "<space_id>"},
+					EnvironmentID: "<env_id>",
+				},
+				CollectionID: "<collection_id>",
+			}},
+		},
+		{
+			name: "ItemID",
+			id: &ID{Descriptor: &ItemID{
+				CollectionID: CollectionID{
+					EnvironmentID: EnvironmentID{
+						SpaceID:       SpaceID{SpaceID: "<space_id>"},
+						EnvironmentID: "<env_id>",
+					},
+					CollectionID: "<collection_id>",
+				},
+				ItemID: "<item_id>",
+			}},
+		},
+		{
+			name: "RevisionID",
+			id: &ID{Descriptor: &RevisionID{
+				ItemID: ItemID{
+					CollectionID: CollectionID{
+						EnvironmentID: EnvironmentID{
+							SpaceID:       SpaceID{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				RevisionID: "<rev_id>",
+			}},
+		},
+		{
+			name: "FieldID",
+			id: &ID{Descriptor: &FieldID{
+				ItemID: ItemID{
+					CollectionID: CollectionID{
+						EnvironmentID: EnvironmentID{
+							SpaceID:       SpaceID{SpaceID: "<space_id>"},
+							EnvironmentID: "<env_id>",
+						},
+						CollectionID: "<collection_id>",
+					},
+					ItemID: "<item_id>",
+				},
+				FieldName: "<field_name>",
+			}},
+		},
+		{
+			name: "SystemID",
+			id:   &ID{Descriptor: &SystemID{}},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			v, err := FromMap(tt.id.ToMap())
+			require.NoError(t, err)
+			assert.Equal(t, tt.id, v, "проверка FromMap для типа ID, должен быть равен исходному значению")
+			assert.Equal(t, v.ToMap(), tt.id.ToMap())
+		})
+	}
+}
diff --git a/id/item.go b/id/item.go
new file mode 100644
index 0000000000000000000000000000000000000000..70c3e7be95b517a85e07e487eb00f94f9f73e14f
--- /dev/null
+++ b/id/item.go
@@ -0,0 +1,61 @@
+package id
+
+const (
+	Item        = "item"
+	ItemsPrefix = "items"
+)
+
+type ItemID struct {
+	CollectionID
+	ItemID string `json:"item_id,omitempty" bson:"item_id,omitempty"`
+}
+
+func (t *ItemID) Type() string { return Item }
+
+func (t *ItemID) String() string {
+	return Join(t.CollectionID.String(), ItemsPrefix, t.ItemID)
+
+}
+
+func (t *ItemID) ToMap() map[string]any {
+	m := t.CollectionID.ToMap()
+	m["item_id"] = t.ItemID
+	m["type"] = Item
+	return m
+}
+
+func (t *ItemID) FromMap(m map[string]any) error {
+	if err := t.CollectionID.FromMap(m); err != nil {
+		return err
+	}
+	t.ItemID = m["item_id"].(string)
+	return nil
+}
+
+func (t *ItemID) Validate() error {
+	if t.ItemID == "" {
+		return ErrInvalidID
+	}
+
+	return t.CollectionID.Validate()
+}
+
+func parseItemID(parts []string) (*ItemID, error) {
+	if len(parts) != 8 || parts[6] != ItemsPrefix {
+		return nil, ErrInvalidID
+	}
+
+	collID, err := parseCollectionID(parts[:6])
+	if err != nil {
+		return nil, err
+	}
+
+	var id ItemID
+	id.CollectionID = *collID
+	id.ItemID = parts[7]
+	return &id, nil
+}
+
+func NewItemID(spaceID, envID, collID, id string) *ID {
+	return &ID{Descriptor: &ItemID{CollectionID: CollectionID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: collID}, ItemID: id}}
+}
diff --git a/id/json.go b/id/json.go
new file mode 100644
index 0000000000000000000000000000000000000000..34b87589dfd81e6b793c5812fb2b90f43fb724f1
--- /dev/null
+++ b/id/json.go
@@ -0,0 +1,23 @@
+package id
+
+import (
+	jsoniter "github.com/json-iterator/go"
+)
+
+func (id *ID) MarshalJSON() ([]byte, error) {
+	return jsoniter.Marshal(id.String())
+}
+
+func (id *ID) UnmarshalJSON(b []byte) error {
+	var s string
+	var err error
+	if err = jsoniter.Unmarshal(b, &s); err != nil {
+		return err
+	}
+	t, err := Parse(s)
+	if err != nil {
+		return err
+	}
+	*id = *t
+	return nil
+}
diff --git a/id/json_test.go b/id/json_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..afe831d4ef6038eaa6bc8a565b0623334cdf6bc8
--- /dev/null
+++ b/id/json_test.go
@@ -0,0 +1,101 @@
+package id
+
+import (
+	"testing"
+
+	jsoniter "github.com/json-iterator/go"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestID_MarshalUnmarshalJSON(t *testing.T) {
+	tests := []struct {
+		name string
+		id   *ID
+	}{
+		{
+			name: "OrganizationID",
+			id:   &ID{Descriptor: &OrganizationID{OrganizationID: "1"}},
+		},
+		{
+			name: "UserID",
+			id:   &ID{Descriptor: &UserID{UserID: "1"}},
+		},
+		{
+			name: "ServiceID",
+			id:   &ID{Descriptor: &ServiceID{ServiceID: "1"}},
+		},
+		{
+			name: "SpaceID",
+			id:   &ID{Descriptor: &SpaceID{SpaceID: "1"}},
+		},
+		{
+			name: "EnvironmentID",
+			id:   &ID{Descriptor: &EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+		},
+		{
+			name: "ClientID",
+			id:   &ID{Descriptor: &ClientID{ClientID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+		},
+		{
+			name: "RoleID",
+			id:   &ID{Descriptor: &RoleID{RoleID: "1", SpaceID: SpaceID{SpaceID: "1"}}},
+		},
+		{
+			name: "CollectionID",
+			id:   &ID{Descriptor: &CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
+		},
+		{
+			name: "SchemaID",
+			id:   &ID{Descriptor: &SchemaID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}},
+		},
+		{
+			name: "ItemID",
+			id:   &ID{Descriptor: &ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}},
+		},
+		{
+			name: "RevisionID",
+			id:   &ID{Descriptor: &RevisionID{RevisionID: "1", ItemID: ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
+		},
+		{
+			name: "FieldID",
+			id:   &ID{Descriptor: &FieldID{FieldName: "1", ItemID: ItemID{ItemID: "1", CollectionID: CollectionID{CollectionID: "1", EnvironmentID: EnvironmentID{EnvironmentID: "1", SpaceID: SpaceID{SpaceID: "1"}}}}}},
+		},
+		{
+			name: "SystemID",
+			id:   &ID{Descriptor: &SystemID{}},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			b, err := jsoniter.Marshal(&tt.id)
+			require.NoError(t, err)
+
+			var i ID
+			require.NoError(t, jsoniter.Unmarshal(b, &i))
+			assert.Equal(t, tt.id, &i, "после Unmarshal объект должен быть идентичен исходному")
+		})
+	}
+}
+
+func TestID_ExampleJSON(t *testing.T) {
+	type data struct {
+		ID     *ID
+		Text   string
+		Number int
+	}
+
+	test := &data{
+		ID:     &ID{Descriptor: &SpaceID{SpaceID: Space}},
+		Text:   "text",
+		Number: 1,
+	}
+
+	b, err := jsoniter.Marshal(test)
+	require.NoError(t, err)
+
+	buf := new(data)
+	err = jsoniter.Unmarshal(b, &buf)
+	require.NoError(t, err)
+	assert.Equal(t, test, buf, "после Unmarshal объект должен совпадать с исходным")
+}
diff --git a/id/organization.go b/id/organization.go
new file mode 100644
index 0000000000000000000000000000000000000000..fe9d22837977b486e78689ebee5f426bdee80fcf
--- /dev/null
+++ b/id/organization.go
@@ -0,0 +1,49 @@
+package id
+
+const (
+	Organization        = "organization"
+	OrganizationsPrefix = "orgs"
+)
+
+type OrganizationID struct {
+	OrganizationID string `json:"organization_id,omitempty" bson:"organization_id,omitempty"`
+}
+
+func (t *OrganizationID) Type() string { return Organization }
+
+func (t *OrganizationID) String() string {
+	return Join(OrganizationsPrefix, t.OrganizationID)
+}
+
+func (t *OrganizationID) ToMap() map[string]any {
+	return map[string]any{
+		"organization_id": t.OrganizationID,
+		"type":            Organization,
+	}
+}
+
+func (t *OrganizationID) FromMap(m map[string]any) error {
+	t.OrganizationID = m["organization_id"].(string)
+	return nil
+}
+
+func (t *OrganizationID) Validate() error {
+	if t.OrganizationID == "" {
+		return ErrInvalidID
+	}
+	return nil
+}
+
+func parseOrganizationID(parts []string) (*OrganizationID, error) {
+	var id OrganizationID
+	if len(parts) != 2 || parts[0] != OrganizationsPrefix {
+		return nil, ErrInvalidID
+	}
+
+	id.OrganizationID = parts[1]
+	return &id, nil
+}
+
+func NewOrganizationID(id string) *ID {
+	return &ID{Descriptor: &OrganizationID{OrganizationID: id}}
+}
diff --git a/id/revision.go b/id/revision.go
new file mode 100644
index 0000000000000000000000000000000000000000..0cb417e132fe1683f1e53a081e7c6244820478df
--- /dev/null
+++ b/id/revision.go
@@ -0,0 +1,61 @@
+package id
+
+const (
+	Revision        = "revision"
+	RevisionsPrefix = "revs"
+)
+
+type RevisionID struct {
+	ItemID
+	RevisionID string `json:"rev_id" bson:"rev_id,omitempty"`
+}
+
+func (t *RevisionID) Type() string { return Revision }
+
+func (t *RevisionID) String() string {
+	return Join(t.ItemID.String(), RevisionsPrefix, t.RevisionID)
+
+}
+
+func (t *RevisionID) ToMap() map[string]any {
+	m := t.ItemID.ToMap()
+	m["rev_id"] = t.RevisionID
+	m["type"] = Revision
+	return m
+}
+
+func (t *RevisionID) FromMap(m map[string]any) error {
+	if err := t.ItemID.FromMap(m); err != nil {
+		return err
+	}
+	t.RevisionID = m["rev_id"].(string)
+	return nil
+}
+
+func (t *RevisionID) Validate() error {
+	if t.RevisionID == "" {
+		return ErrInvalidID
+	}
+
+	return t.ItemID.Validate()
+}
+
+func parseRevisionID(parts []string) (*RevisionID, error) {
+	if len(parts) != 10 || parts[8] != RevisionsPrefix {
+		return nil, ErrInvalidID
+	}
+
+	itemID, err := parseItemID(parts[:8])
+	if err != nil {
+		return nil, err
+	}
+
+	var id RevisionID
+	id.ItemID = *itemID
+	id.RevisionID = parts[9]
+	return &id, nil
+}
+
+func NewRevisionID(spaceID, envID, collID, itemID, id string) *ID {
+	return &ID{Descriptor: &RevisionID{ItemID: ItemID{CollectionID: CollectionID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: collID}, ItemID: itemID}, RevisionID: id}}
+}
diff --git a/id/role.go b/id/role.go
new file mode 100644
index 0000000000000000000000000000000000000000..abb6537fc605d3f01f644a67a724cc81e3a6b54b
--- /dev/null
+++ b/id/role.go
@@ -0,0 +1,61 @@
+package id
+
+const (
+	Role        = "role"
+	RolesPrefix = "roles"
+)
+
+type RoleID struct {
+	SpaceID
+	RoleID string `json:"role_id,omitempty" bson:"role_id,omitempty"`
+}
+
+func (t *RoleID) Type() string { return Role }
+
+func (t *RoleID) String() string {
+	return Join(t.SpaceID.String(), RolesPrefix, t.RoleID)
+
+}
+
+func (t *RoleID) ToMap() map[string]any {
+	m := t.SpaceID.ToMap()
+	m["role_id"] = t.RoleID
+	m["type"] = Role
+	return m
+}
+
+func (t *RoleID) FromMap(m map[string]any) error {
+	if err := t.SpaceID.FromMap(m); err != nil {
+		return err
+	}
+	t.RoleID = m["role_id"].(string)
+	return nil
+}
+
+func (t *RoleID) Validate() error {
+	if t.RoleID == "" {
+		return ErrInvalidID
+	}
+
+	return t.SpaceID.Validate()
+}
+
+func parseRoleID(parts []string) (*RoleID, error) {
+	if len(parts) != 4 || parts[2] != RolesPrefix {
+		return nil, ErrInvalidID
+	}
+
+	spaceID, err := parseSpaceID(parts[:2])
+	if err != nil {
+		return nil, err
+	}
+
+	var id RoleID
+	id.SpaceID = *spaceID
+	id.RoleID = parts[3]
+	return &id, nil
+}
+
+func NewRoleID(spaceID, id string) *ID {
+	return &ID{Descriptor: &RoleID{SpaceID: SpaceID{SpaceID: spaceID}, RoleID: id}}
+}
diff --git a/id/schema.go b/id/schema.go
new file mode 100644
index 0000000000000000000000000000000000000000..e3afee8c6cf29bca6cae1f1eedd72253c47362a5
--- /dev/null
+++ b/id/schema.go
@@ -0,0 +1,60 @@
+package id
+
+const (
+	Schema       = "schema"
+	SchemaPrefix = "schema"
+)
+
+type SchemaID struct {
+	EnvironmentID
+	CollectionID string `json:"col_id" bson:"col_id,omitempty"`
+}
+
+func (t *SchemaID) Type() string { return Schema }
+
+func (t *SchemaID) String() string {
+	return Join(t.EnvironmentID.String(), SchemaPrefix, t.CollectionID)
+}
+
+func (t *SchemaID) ToMap() map[string]any {
+	m := t.EnvironmentID.ToMap()
+	m["col_id"] = t.CollectionID
+	m["type"] = Schema
+	return m
+}
+
+func (t *SchemaID) FromMap(m map[string]any) error {
+	if err := t.EnvironmentID.FromMap(m); err != nil {
+		return err
+	}
+	t.CollectionID = m["col_id"].(string)
+	return nil
+}
+
+func (t *SchemaID) Validate() error {
+	if t.CollectionID == "" {
+		return ErrInvalidID
+	}
+
+	return t.EnvironmentID.Validate()
+}
+
+func parseSchemaID(parts []string) (*SchemaID, error) {
+	if len(parts) != 6 || parts[4] != SchemaPrefix {
+		return nil, ErrInvalidID
+	}
+
+	envID, err := parseEnvironmentID(parts[:4])
+	if err != nil {
+		return nil, err
+	}
+
+	var id SchemaID
+	id.EnvironmentID = *envID
+	id.CollectionID = parts[5]
+	return &id, nil
+}
+
+func NewSchemaID(spaceID, envID, id string) *ID {
+	return &ID{Descriptor: &SchemaID{EnvironmentID: EnvironmentID{SpaceID: SpaceID{SpaceID: spaceID}, EnvironmentID: envID}, CollectionID: id}}
+}
diff --git a/id/service.go b/id/service.go
new file mode 100644
index 0000000000000000000000000000000000000000..23bb23aa500fbba082e1aae578eae6da5624a5b9
--- /dev/null
+++ b/id/service.go
@@ -0,0 +1,49 @@
+package id
+
+const (
+	Service        = "service"
+	ServicesPrefix = "services"
+)
+
+type ServiceID struct {
+	ServiceID string `json:"service_id,omitempty" bson:"service_id,omitempty"`
+}
+
+func (t *ServiceID) Type() string { return Service }
+
+func (t *ServiceID) String() string {
+	return Join(ServicesPrefix, t.ServiceID)
+}
+
+func (t *ServiceID) ToMap() map[string]any {
+	return map[string]any{
+		"service_id": t.ServiceID,
+		"type":       Service,
+	}
+}
+
+func (t *ServiceID) FromMap(m map[string]any) error {
+	t.ServiceID = m["service_id"].(string)
+	return nil
+}
+
+func (t *ServiceID) Validate() error {
+	if t.ServiceID == "" {
+		return ErrInvalidID
+	}
+	return nil
+}
+
+func parseServiceID(parts []string) (*ServiceID, error) {
+	var id ServiceID
+	if len(parts) != 2 || parts[0] != ServicesPrefix {
+		return nil, ErrInvalidID
+	}
+
+	id.ServiceID = parts[1]
+	return &id, nil
+}
+
+func NewServiceID(id string) *ID {
+	return &ID{Descriptor: &ServiceID{ServiceID: id}}
+}
diff --git a/id/space.go b/id/space.go
new file mode 100644
index 0000000000000000000000000000000000000000..39096673456d74462ef203b10814396fe48b2bcf
--- /dev/null
+++ b/id/space.go
@@ -0,0 +1,48 @@
+package id
+
+const (
+	Space        = "space"
+	SpacesPrefix = "spaces"
+)
+
+type SpaceID struct {
+	SpaceID string `json:"space_id,omitempty" bson:"space_id,omitempty"`
+}
+
+func (t *SpaceID) Type() string { return Space }
+
+func (t *SpaceID) String() string {
+	return Join(SpacesPrefix, t.SpaceID)
+}
+
+func (t *SpaceID) ToMap() map[string]any {
+	return map[string]any{
+		"space_id": t.SpaceID,
+		"type":     Space,
+	}
+}
+
+func (t *SpaceID) FromMap(m map[string]any) error {
+	t.SpaceID = m["space_id"].(string)
+	return nil
+}
+
+func (t *SpaceID) Validate() error {
+	if t.SpaceID == "" {
+		return ErrInvalidID
+	}
+	return nil
+}
+
+func parseSpaceID(parts []string) (*SpaceID, error) {
+	var id SpaceID
+	if len(parts) != 2 || parts[0] != SpacesPrefix {
+		return nil, ErrInvalidID
+	}
+
+	id.SpaceID = parts[1]
+	return &id, nil
+}
+func NewSpaceID(id string) *ID {
+	return &ID{Descriptor: &SpaceID{SpaceID: id}}
+}
diff --git a/id/system.go b/id/system.go
new file mode 100644
index 0000000000000000000000000000000000000000..de2f3c2571896e448f8aad17be32df58b73a6ea4
--- /dev/null
+++ b/id/system.go
@@ -0,0 +1,22 @@
+package id
+
+const System = "system"
+
+type SystemID struct{}
+
+func (t *SystemID) Type() string                   { return Space }
+func (t *SystemID) String() string                 { return string(Separator) + System }
+func (t *SystemID) ToMap() map[string]any          { return map[string]any{"type": System} }
+func (t *SystemID) FromMap(m map[string]any) error { return nil }
+func (t *SystemID) Validate() error                { return nil }
+
+func parseSystemID(parts []string) (*SystemID, error) {
+	var id SystemID
+	if len(parts) != 1 || parts[0] != System {
+		return nil, ErrInvalidID
+	}
+	return &id, nil
+}
+func NewSystemID() *ID {
+	return &ID{Descriptor: &SystemID{}}
+}
diff --git a/id/user.go b/id/user.go
new file mode 100644
index 0000000000000000000000000000000000000000..c76f6c9fa0e8ce440a1ef15e36bbb82a73301be5
--- /dev/null
+++ b/id/user.go
@@ -0,0 +1,49 @@
+package id
+
+const (
+	User        = "user"
+	UsersPrefix = "users"
+)
+
+type UserID struct {
+	UserID string `json:"user_id,omitempty" bson:"user_id,omitempty"`
+}
+
+func (t *UserID) Type() string { return User }
+
+func (t *UserID) String() string {
+	return Join(UsersPrefix, t.UserID)
+}
+
+func (t *UserID) ToMap() map[string]any {
+	return map[string]any{
+		"user_id": t.UserID,
+		"type":    User,
+	}
+}
+
+func (t *UserID) FromMap(m map[string]any) error {
+	t.UserID = m["user_id"].(string)
+	return nil
+}
+
+func (t *UserID) Validate() error {
+	if t.UserID == "" {
+		return ErrInvalidID
+	}
+	return nil
+}
+
+func parseUserID(parts []string) (*UserID, error) {
+	var id UserID
+	if len(parts) != 2 || parts[0] != UsersPrefix {
+		return nil, ErrInvalidID
+	}
+
+	id.UserID = parts[1]
+	return &id, nil
+}
+
+func NewUserID(id string) *ID {
+	return &ID{Descriptor: &UserID{UserID: id}}
+}
diff --git a/perxis-proto b/perxis-proto
index ecd756865a06583ce52bff6265c0fd5e159d93b9..63410745d6008eaa9d9b00626b5f5b6891ac9189 160000
--- a/perxis-proto
+++ b/perxis-proto
@@ -1 +1 @@
-Subproject commit ecd756865a06583ce52bff6265c0fd5e159d93b9
+Subproject commit 63410745d6008eaa9d9b00626b5f5b6891ac9189