diff --git a/geomagio/api/ws/DataApiQuery.py b/geomagio/api/ws/DataApiQuery.py
index e7c224532ecafcb02f180ce8724be595d2cae830..ac88e6092a94967b8d2a3907936b88fd776c6545 100644
--- a/geomagio/api/ws/DataApiQuery.py
+++ b/geomagio/api/ws/DataApiQuery.py
@@ -73,6 +73,15 @@ class DataApiQuery(BaseModel):
     format: Union[OutputFormat, str] = OutputFormat.IAGA2002
     data_host: Union[DataHost, str] = DataHost.DEFAULT
 
+    @field_validator("starttime", mode="before")
+    def validate_starttime(
+        cls, starttime: CustomUTCDateTimeType
+    ) -> CustomUTCDateTimeType:
+        if not starttime:
+            return default_starttime()
+        else:
+            return starttime
+
     @field_validator("data_host", mode="before")
     def validate_data_host(cls, data_host: DataHost) -> DataHost:
         if data_host not in DataHost.values():
diff --git a/geomagio/pydantic_utcdatetime.py b/geomagio/pydantic_utcdatetime.py
index 862cd138446571bf1e8ec9efe2da62b50a5d0b56..7653f6ad6ff610f50f899b526db1eed73ebf4268 100644
--- a/geomagio/pydantic_utcdatetime.py
+++ b/geomagio/pydantic_utcdatetime.py
@@ -32,7 +32,7 @@ class CustomUTCDateTimeValidator:
                 time = UTCDateTime(value)
             except:
                 raise ValueError(
-                    f"Invalid time type. Expected format is '%Y-%m-%dT%H:%M:%S.%fZ'"
+                    f"Invalid time type. See obspy UTCDateTime for more information."
                 )
             return time
 
diff --git a/test/DataApiQuery_test.py b/test/DataApiQuery_test.py
index c03e88f29c231858b4c722b6a69f7cc4cb204bd5..117f1758c1be5f4d9668dc25208ec8fcee5a3596 100644
--- a/test/DataApiQuery_test.py
+++ b/test/DataApiQuery_test.py
@@ -30,6 +30,24 @@ def test_DataApiQuery_defaults():
     assert_equal(query.data_host.value, "edgecwb.usgs.gov")
 
 
+def test_DataApiQuery_starttime_is_none():
+    query = DataApiQuery(id="BOU", starttime=None)
+
+    now = datetime.datetime.now(tz=datetime.timezone.utc)
+    expected_start_time = UTCDateTime(year=now.year, month=now.month, day=now.day)
+    expected_endtime = expected_start_time + (86400 - 0.001)
+
+    assert_equal(query.id, "BOU")
+    assert_equal(query.starttime, expected_start_time)
+    assert_equal(query.endtime, expected_endtime)
+    assert_equal(query.elements, ["X", "Y", "Z", "F"])
+    assert_equal(query.sampling_period, SamplingPeriod.MINUTE)
+    assert_equal(query.data_type, DataType.VARIATION)
+    assert_equal(query.format, OutputFormat.IAGA2002)
+    # assumes the env var DATA_HOST is not set
+    assert_equal(query.data_host.value, "edgecwb.usgs.gov")
+
+
 def test_DataApiQuery_valid():
     query = DataApiQuery(
         id="DED",
@@ -88,6 +106,40 @@ def test_DataApiQuery_default_endtime():
     assert_equal(query.data_host, DataHost.DEFAULT)
 
 
+def test_DataApiQuery_default_only_endtime():
+    # test a valid endtime that is after default starttime
+    hour_later = datetime.datetime.now() + datetime.timedelta(hours=1)
+
+    query = DataApiQuery(id="BOU", endtime=hour_later)
+
+    assert_equal(query.id, "BOU")
+
+    now = datetime.datetime.now(tz=datetime.timezone.utc)
+    expected_start_time = UTCDateTime(year=now.year, month=now.month, day=now.day)
+    # starttime is at the beginning of today
+    assert_equal(query.starttime, expected_start_time)
+    # endtime is still what the user set
+    assert_equal(query.endtime, hour_later)
+
+    assert_equal(query.elements, ["X", "Y", "Z", "F"])
+    assert_equal(query.sampling_period, SamplingPeriod.MINUTE)
+    assert_equal(query.data_type, DataType.VARIATION)
+    assert_equal(query.format, OutputFormat.IAGA2002)
+    assert_equal(query.data_host, DataHost.DEFAULT)
+
+
+def test_DataApiQuery_default_only_invalid_endtime():
+    # test an invalid endtime that is before default starttime
+    query = None
+    try:
+        query = DataApiQuery(id="BOU", endtime="2024-09-01T00:00:01")
+    except Exception as e:
+        err = e.errors()
+        assert "Value error, Starttime must be before endtime." == err[0]["msg"]
+
+    assert_equal(query, None)
+
+
 def test_DataApiQuery_starttime_after_endtime():
     query = None
     try:
diff --git a/test/api_test/ws_test/data_test.py b/test/api_test/ws_test/data_test.py
index 2cf8d5d7699542e95f37ec6842c83993f1c05184..3376e31100056e7186ce47543fad8f81d352e404 100644
--- a/test/api_test/ws_test/data_test.py
+++ b/test/api_test/ws_test/data_test.py
@@ -1,3 +1,4 @@
+import datetime
 from fastapi import Depends
 from fastapi.testclient import TestClient
 from numpy.testing import assert_equal
@@ -39,6 +40,21 @@ def test_get_data_query(test_client):
     assert_equal(query.data_type, "R1")
 
 
+def test_get_data_query_no_starttime(test_client):
+    """test.api_test.ws_test.data_test.test_get_data_query_no_starttime()"""
+    response = test_client.get("/query/?id=BOU&elements=X,Y,Z,F")
+    query = DataApiQuery(**response.json())
+    assert_equal(query.id, "BOU")
+
+    # expect starttime to default to start of current day
+    now = datetime.datetime.now(tz=datetime.timezone.utc)
+    expected = UTCDateTime(year=now.year, month=now.month, day=now.day)
+
+    assert_equal(query.starttime, expected)
+    # expect endtime to default to 1 day after starttiime
+    assert_equal(query.endtime, expected + (86400 - 0.001))
+
+
 async def test_get_data_query_extra_params(test_client):
     with pytest.raises(ValueError) as error:
         response = await test_client.get(
diff --git a/test/api_test/ws_test/filter_test.py b/test/api_test/ws_test/filter_test.py
index 20fb73cf92124d9510a665b7e4c574737ddad9a1..dbd2964ea2072d8dd678ae745fe60a5f37c54f64 100644
--- a/test/api_test/ws_test/filter_test.py
+++ b/test/api_test/ws_test/filter_test.py
@@ -1,3 +1,4 @@
+import datetime
 from fastapi import Depends
 from fastapi.testclient import TestClient
 from numpy.testing import assert_equal
@@ -35,3 +36,18 @@ def test_get_filter_data_query(test_client):
     assert_equal(query.data_type, "variation")
     assert_equal(query.input_sampling_period, SamplingPeriod.MINUTE)
     assert_equal(query.output_sampling_period, SamplingPeriod.HOUR)
+
+
+def test_get_filter_data_query_no_starttime(test_client):
+    """test.api_test.ws_test.data_test.test_get_filter_data_query_no_starttime()"""
+    response = test_client.get("/filter/?id=BOU&elements=X,Y,Z,F")
+    query = FilterApiQuery(**response.json())
+    assert_equal(query.id, "BOU")
+
+    # expect starttime to default to start of current day
+    now = datetime.datetime.now(tz=datetime.timezone.utc)
+    expected = UTCDateTime(year=now.year, month=now.month, day=now.day)
+
+    assert_equal(query.starttime, expected)
+    # expect endtime to default to 1 day after starttiime
+    assert_equal(query.endtime, expected + (86400 - 0.001))
diff --git a/test/pydantic_utcdatetime_test.py b/test/pydantic_utcdatetime_test.py
index ef81615a4ede899880b017224b0586b8eae257bd..232cd7b1cd6e58ecbe5b91e4eeb53efc87f900b7 100644
--- a/test/pydantic_utcdatetime_test.py
+++ b/test/pydantic_utcdatetime_test.py
@@ -17,6 +17,13 @@ def test_UTCDateTime_string():
     assert_equal(t.starttime, UTCDateTime(2024, 11, 5, 0, 0))
 
 
+def test_UTCDateTime_ISO860_string():
+    # `ISO8601:2004`_ string from obspy docs
+    t = TimeClass(starttime="2024-11-05T00:00:00.00")
+
+    assert_equal(t.starttime, UTCDateTime(2024, 11, 5, 0, 0))
+
+
 def test_UTCDateTime_datetime():
     t = TimeClass(starttime=datetime.datetime(2024, 11, 5, tzinfo=tz.tzutc()))
 
@@ -42,5 +49,9 @@ def test_invalid():
     except Exception as e:
         err = e.errors()
         assert "Input should be an instance of UTCDateTime" == err[0]["msg"]
+        assert (
+            "Value error, Invalid time type. See obspy UTCDateTime for more information."
+            == err[1]["msg"]
+        )
 
     assert_equal(t, None)