diff --git a/id/collection.go b/id/collection.go
new file mode 100644
index 0000000000000000000000000000000000000000..18e1008b078d9f4350ee367b85d0489e75bc6293
--- /dev/null
+++ b/id/collection.go
@@ -0,0 +1,55 @@
+package id
+
+const (
+	Collection        = "collection"
+	CollectionsPrefix = "cols"
+)
+
+type CollectionID struct {
+	EnvironmentID
+	CollectionID string `json:"col_id"`
+}
+
+func (t *CollectionID) Type() string { return Collection }
+
+func (t *CollectionID) String() string {
+	return Join(t.EnvironmentID.String(), t.CollectionID)
+}
+
+func (t *CollectionID) ToMap() map[string]any {
+	m := t.EnvironmentID.ToMap()
+	m["col_id"] = t.EnvironmentID
+	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) FromString(id string) error {
+	parts := Split(id)
+	return t.fromParts(parts)
+}
+
+func (t *CollectionID) fromParts(parts []string) error {
+	if err := t.EnvironmentID.fromParts(parts); err != nil {
+		return err
+	}
+	if len(parts) < 6 || parts[4] != CollectionsPrefix {
+		return ErrInvalidID
+	}
+	t.CollectionID = parts[5]
+	return nil
+}
+
+func (t *CollectionID) Validate() error {
+	if t.CollectionID == "" {
+		return ErrInvalidID
+	}
+
+	return t.EnvironmentID.Validate()
+}
diff --git a/id/environment.go b/id/environment.go
new file mode 100644
index 0000000000000000000000000000000000000000..f5ceab37fc8d8c7131ac923dd0ac8912ebe2e182
--- /dev/null
+++ b/id/environment.go
@@ -0,0 +1,55 @@
+package id
+
+const (
+	Environment        = "environment"
+	EnvironmentsPrefix = "envs"
+)
+
+type EnvironmentID struct {
+	SpaceID
+	EnvironmentID string `json:"env_id"`
+}
+
+func (t *EnvironmentID) Type() string { return Environment }
+
+func (t *EnvironmentID) String() string {
+	return Join(t.SpaceID.String(), t.EnvironmentID)
+}
+
+func (t *EnvironmentID) ToMap() map[string]any {
+	m := t.SpaceID.ToMap()
+	m["env_id"] = t.EnvironmentID
+	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) FromString(id string) error {
+	parts := Split(id)
+	return t.fromParts(parts)
+}
+
+func (t *EnvironmentID) fromParts(parts []string) error {
+	if err := t.SpaceID.fromParts(parts); err != nil {
+		return err
+	}
+	if len(parts) < 4 || parts[2] != EnvironmentsPrefix {
+		return ErrInvalidID
+	}
+	t.EnvironmentID = parts[3]
+	return nil
+}
+
+func (t *EnvironmentID) Validate() error {
+	if t.EnvironmentID == "" {
+		return ErrInvalidID
+	}
+
+	return t.SpaceID.Validate()
+}
diff --git a/id/id.go b/id/id.go
new file mode 100644
index 0000000000000000000000000000000000000000..8f9ee6b8971aeae1c635200ba4c616cc943af8ac
--- /dev/null
+++ b/id/id.go
@@ -0,0 +1,34 @@
+package id
+
+import (
+	"errors"
+	"strings"
+)
+
+const Separator = '/'
+
+var ErrInvalidID = errors.New("invalid id")
+
+type ID interface {
+	String() string
+	Type() string
+	ToMap() map[string]any
+	FromString(string) error
+	FromMap(map[string]any) error
+	Validate() error
+}
+
+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
+}
diff --git a/id/json.go b/id/json.go
new file mode 100644
index 0000000000000000000000000000000000000000..ec510485d9025ad6ce4828c7ca8eb720629a511b
--- /dev/null
+++ b/id/json.go
@@ -0,0 +1,3 @@
+package id
+
+// Сохранение/чтение в JSON (в строку)
diff --git a/id/space.go b/id/space.go
new file mode 100644
index 0000000000000000000000000000000000000000..465412db077d14387afc958d96bb3eb7d6dd18c4
--- /dev/null
+++ b/id/space.go
@@ -0,0 +1,47 @@
+package id
+
+const (
+	Space        = "space"
+	SpacesPrefix = "spaces"
+)
+
+type SpaceID struct {
+	SpaceID string `json:"space_id"`
+}
+
+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,
+	}
+}
+
+func (t *SpaceID) FromMap(m map[string]any) error {
+	t.SpaceID = m["space_id"].(string)
+	return nil
+}
+
+func (t *SpaceID) FromString(id string) error {
+	parts := Split(id)
+	return t.fromParts(parts)
+}
+
+func (t *SpaceID) fromParts(parts []string) error {
+	if len(parts) < 2 || parts[0] != SpacesPrefix {
+		return ErrInvalidID
+	}
+	t.SpaceID = parts[1]
+	return nil
+}
+
+func (t *SpaceID) Validate() error {
+	if t.SpaceID == "" {
+		return ErrInvalidID
+	}
+	return nil
+}