diff --git a/gso/products/product_blocks/site.py b/gso/products/product_blocks/site.py
index 1dae1b582505ec0824bb9da67d9f3eef6d6507cc..4cd0f6874f97562dcf66504dc0f499d88260e1d3 100644
--- a/gso/products/product_blocks/site.py
+++ b/gso/products/product_blocks/site.py
@@ -1,10 +1,10 @@
 """The product block that describes a site subscription."""
-import re
 from typing import Optional
 
 from orchestrator.domain.base import ProductBlockModel
 from orchestrator.types import SubscriptionLifecycle, strEnum
-from pydantic import ConstrainedStr
+
+from gso.schemas.types import LatitudeCoordinate, LongitudeCoordinate
 
 
 class SiteTier(strEnum):
@@ -20,15 +20,6 @@ class SiteTier(strEnum):
     TIER4 = 4
 
 
-class SnmpCoordinate(ConstrainedStr):
-    """An {term}`SNMP` coordinate, modeled as a constrained string.
-
-    The coordinate must match the format of `1.35`, `-123.456`, etc.
-    """
-
-    regex = re.compile(r"^-?\d{1,3}\.\d+$")
-
-
 class SiteBlockInactive(ProductBlockModel, lifecycle=[SubscriptionLifecycle.INITIAL], product_block_name="SiteBlock"):
     """A site that's currently inactive, see {class}`SiteBlock`."""
 
@@ -36,8 +27,8 @@ class SiteBlockInactive(ProductBlockModel, lifecycle=[SubscriptionLifecycle.INIT
     site_city: Optional[str] = None
     site_country: Optional[str] = None
     site_country_code: Optional[str] = None
-    site_latitude: Optional[SnmpCoordinate] = None
-    site_longitude: Optional[SnmpCoordinate] = None
+    site_latitude: Optional[LatitudeCoordinate] = None
+    site_longitude: Optional[LongitudeCoordinate] = None
     site_internal_id: Optional[int] = None
     site_bgp_community_id: Optional[int] = None
     site_tier: Optional[SiteTier] = None
@@ -51,8 +42,8 @@ class SiteBlockProvisioning(SiteBlockInactive, lifecycle=[SubscriptionLifecycle.
     site_city: Optional[str] = None
     site_country: Optional[str] = None
     site_country_code: Optional[str] = None
-    site_latitude: Optional[SnmpCoordinate] = None
-    site_longitude: Optional[SnmpCoordinate] = None
+    site_latitude: Optional[LatitudeCoordinate] = None
+    site_longitude: Optional[LongitudeCoordinate] = None
     site_internal_id: Optional[int] = None
     site_bgp_community_id: Optional[int] = None
     site_tier: Optional[SiteTier] = None
@@ -72,9 +63,9 @@ class SiteBlock(SiteBlockProvisioning, lifecycle=[SubscriptionLifecycle.ACTIVE])
     site_country_code: str
     """The code of the corresponding country. This is also used for the {term}`FQDN`, following the example given for
     the site name, the country code would end up in the Y position."""
-    site_latitude: SnmpCoordinate
+    site_latitude: LatitudeCoordinate
     """The latitude of the site, used for {term}`SNMP` purposes."""
-    site_longitude: SnmpCoordinate
+    site_longitude: LongitudeCoordinate
     """Similar to the latitude, the longitude of a site."""
     site_internal_id: int
     """The internal ID used within GÉANT to denote a site."""
diff --git a/gso/schemas/types.py b/gso/schemas/types.py
new file mode 100644
index 0000000000000000000000000000000000000000..94aced74e3c6ba4b6703b7afee01d962603cfbd4
--- /dev/null
+++ b/gso/schemas/types.py
@@ -0,0 +1,40 @@
+import re
+from typing import Union
+
+from pydantic import ConstrainedStr
+
+
+class LatitudeCoordinate(ConstrainedStr):
+    """A latitude coordinate, modeled as a constrained string.
+
+    The coordinate must match the format conforming to the latitude
+    range of `-`90 to +90 degrees. It can be a floating-point number or an integer.
+    Valid examples: 40.7128, -74.0060, 90, -90, 0
+    """
+
+    regex = re.compile(r"^-?([1-8]?\d(\.\d+)?|90(\.0+)?)$")
+
+    @classmethod
+    def validate(cls, value: Union[str]) -> Union[str]:
+        if not cls.regex.match(value):
+            raise ValueError("Invalid latitude coordinate. Valid examples: '40.7128', '-74.0060', '90', '-90', '0'.")
+
+        return value
+
+
+class LongitudeCoordinate(ConstrainedStr):
+    """A longitude coordinate, modeled as a constrained string.
+
+    The coordinate must match the format conforming to the longitude
+    range of `-`180 to 180 degrees. It can be a floating point number or an integer.
+    Valid examples: 40.7128, -74.0060, 180, -180, 0
+    """
+
+    regex = re.compile(r"^-?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$")
+
+    @classmethod
+    def validate(cls, value: Union[str]) -> Union[str]:
+        if not cls.regex.match(value):
+            raise ValueError("Invalid longitude coordinate. Valid examples: '40.7128', '-74.0060', '180', '-180'")
+
+        return value
diff --git a/gso/workflows/site/create_site.py b/gso/workflows/site/create_site.py
index b2e7406aaa9ffa868ffd201fe37c7d8e06f6b917..e495049ec17ba041d4aa7248e308a8e60c3939f2 100644
--- a/gso/workflows/site/create_site.py
+++ b/gso/workflows/site/create_site.py
@@ -11,8 +11,8 @@ from orchestrator.workflows.utils import wrap_create_initial_input_form
 from pydantic import validator
 
 from gso.products.product_blocks import site as site_pb
-from gso.products.product_blocks.site import SnmpCoordinate
 from gso.products.product_types import site
+from gso.schemas.types import LatitudeCoordinate, LongitudeCoordinate
 from gso.workflows.utils import customer_selector
 
 
@@ -26,8 +26,8 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:  # noqa: C
         site_city: str
         site_country: str
         site_country_code: str
-        site_latitude: str
-        site_longitude: str
+        site_latitude: LatitudeCoordinate
+        site_longitude: LongitudeCoordinate
         site_bgp_community_id: int
         site_internal_id: int
         site_tier: site_pb.SiteTier
@@ -41,26 +41,6 @@ def initial_input_form_generator(product_name: str) -> FormGenerator:  # noqa: C
             except LookupError:
                 raise ValueError("Invalid or non-existent country code, it must be in ISO 3166-1 alpha-2 format.")
 
-        @validator("site_latitude", allow_reuse=True)
-        def latitude_must_be_valid(cls, latitude: str) -> str | NoReturn:
-            def _is_valid_latitude(degree: float) -> bool:
-                return -90 <= degree <= 90
-
-            if _is_valid_latitude(float(latitude)):
-                return latitude
-
-            raise ValueError("Entered latitude is not a valid value, must be between -90.0° and 90.0°.")
-
-        @validator("site_longitude", allow_reuse=True)
-        def longitude_must_be_valid(cls, longitude: str) -> str | NoReturn:
-            def _is_valid_longitude(degree: float) -> bool:
-                return -180 <= degree <= 180
-
-            if _is_valid_longitude(float(longitude)):
-                return longitude
-
-            raise ValueError("Entered longitude is not a valid value, must be between -180.0° and 180.0°.")
-
         @validator("site_ts_address", allow_reuse=True)
         def ts_address_must_be_valid(cls, ts_address: str) -> str | NoReturn:
             try:
@@ -91,8 +71,8 @@ def initialize_subscription(
     site_city: str,
     site_country: str,
     site_country_code: str,
-    site_latitude: SnmpCoordinate,
-    site_longitude: SnmpCoordinate,
+    site_latitude: LatitudeCoordinate,
+    site_longitude: LongitudeCoordinate,
     site_bgp_community_id: int,
     site_internal_id: int,
     site_ts_address: str,
@@ -102,8 +82,8 @@ def initialize_subscription(
     subscription.site.site_city = site_city
     subscription.site.site_country = site_country
     subscription.site.site_country_code = site_country_code
-    subscription.site.site_latitude = site_longitude
-    subscription.site.site_longitude = site_latitude
+    subscription.site.site_latitude = site_latitude
+    subscription.site.site_longitude = site_longitude
     subscription.site.site_bgp_community_id = site_bgp_community_id
     subscription.site.site_internal_id = site_internal_id
     subscription.site.site_tier = site_tier
diff --git a/test/schemas/__init__.py b/test/schemas/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/test/schemas/test_types.py b/test/schemas/test_types.py
new file mode 100644
index 0000000000000000000000000000000000000000..dcfb515dbd315a92b205f4faa66a615f81ea0232
--- /dev/null
+++ b/test/schemas/test_types.py
@@ -0,0 +1,55 @@
+import pytest
+
+from gso.schemas.types import LatitudeCoordinate, LongitudeCoordinate
+
+
+@pytest.mark.parametrize(
+    "input_value, is_valid",
+    [
+        ("40.7128", True),
+        ("-74.0060", True),
+        ("90", True),
+        ("-90", True),
+        ("0", True),
+        ("45.6", True),
+        ("91", False),
+        ("-91", False),
+        ("180", False),
+        ("-180", False),
+        ("abc", False),
+        ("90.1", False),
+    ],
+)
+def test_latitude(input_value, is_valid):
+    if is_valid:
+        assert LatitudeCoordinate.validate(input_value) == input_value
+    else:
+        with pytest.raises(ValueError) as excinfo:
+            LatitudeCoordinate.validate(input_value)
+        assert "Invalid latitude coordinate" in str(excinfo.value)
+
+
+@pytest.mark.parametrize(
+    "input_value, is_valid",
+    [
+        ("40.7128", True),
+        ("-74.0060", True),
+        ("180", True),
+        ("-180", True),
+        ("0", True),
+        ("90.1", True),
+        ("181", False),
+        ("-181", False),
+        ("200", False),
+        ("-200", False),
+        ("abc", False),
+        ("90a", False),
+    ],
+)
+def test_longitude(input_value, is_valid):
+    if is_valid:
+        assert LongitudeCoordinate.validate(input_value) == input_value
+    else:
+        with pytest.raises(ValueError) as excinfo:
+            LongitudeCoordinate.validate(input_value)
+        assert "Invalid longitude coordinate" in str(excinfo.value)