diff --git a/digitalarchive/exceptions.py b/digitalarchive/exceptions.py
index 8f0f3da..94f222d 100644
--- a/digitalarchive/exceptions.py
+++ b/digitalarchive/exceptions.py
@@ -14,3 +14,6 @@ class NoSuchResourceError(Exception):
 
 class APIServerError(Exception):
     """DA API returned a non-200 code for attempted operation."""
+
+class MalformedLanguageSearch(InvalidSearchFieldError):
+    """User passed a langauge search term that was not an instance of models.Language or an ISO 639-2/B string."""
diff --git a/digitalarchive/models.py b/digitalarchive/models.py
index d887150..bb8a4b7 100644
--- a/digitalarchive/models.py
+++ b/digitalarchive/models.py
@@ -663,7 +663,8 @@ class Document(_MatchableResource, _HydrateableResource, _TimestampedResource):
                 authors include all of the passed contributors.
             donors (list(:class:`digitalarchive.models.Donor`)) Restrict results to Documents who were obtained or
                 translated with support from all of the passed donors.
-            language (:class:`digitalarchive.models.Language`) Restrict results to Documents by original language.
+            languages (:class:`digitalarchive.models.Language` or str) Restrict results to Documents by language of
+                original document. If passing a string, you must pass an ISO 639-2/B language code.
             translation (:class:`digitalarchive.models.Translation`) Restrict results to Documents for which there
                 is a translation available in the passed Language.
             theme (:class:`digitalarchive.models.Theme`) Restrict results to Documents belonging to the passed Theme.
@@ -693,6 +694,10 @@ class Document(_MatchableResource, _HydrateableResource, _TimestampedResource):
         if any(key in kwargs.keys() for key in ["start_date", "end_date"]):
             kwargs = Document._process_date_searches(kwargs)
 
+        # Process language searches if they are present.
+        if "languages" in kwargs.keys():
+            kwargs = Document._process_language_search(kwargs)
+
         # Process any related model searches.
         if any(
             key in kwargs.keys()
@@ -896,6 +901,35 @@ class Document(_MatchableResource, _HydrateableResource, _TimestampedResource):
         # Return the reformatted query.
         return query
 
+    @staticmethod
+    def _process_language_search(query: dict) -> dict:
+        """
+        Process a language search
+
+        Looks up the DA's language ID# for user provided ISO 639-2/B language codes and updates the query.
+
+        Args:
+            query (dict): A ResourceMatcher query.
+
+        Returns:
+            dict: A query dict with a ISO 639-2/B string replaced with appropriate Language object.
+        """
+        parsed_languages = []
+        for language in query["languages"]:
+            # Check if ID# is instance of language, bail on yes.
+            if isinstance(language, Language):
+                parsed_languages.append(language)
+
+            # If str, lookup ID# of language
+            elif isinstance(language, str) and len(language) == 3:
+                parsed_languages.append(Language(id=language))
+
+            else:
+                raise exceptions.MalformedLanguageSearch
+
+            # Replace kwarg with Langauge object.
+            query["languages"] = parsed_languages
+            return query
 
 @dataclass(eq=False)
 class Theme(_HydrateableResource):

diff --git a/tests/integration/test_models.py b/tests/integration/test_models.py
index 144d889..8ae297a 100644
--- a/tests/integration/test_models.py
+++ b/tests/integration/test_models.py
@@ -260,6 +260,12 @@ class TestDocument:
         for doc in docs.all():
             assert language in doc.languages
 
+    def test_search_by_language_by_iso_code(self):
+        all_docs = digitalarchive.Document.match()
+        german_docs = digitalarchive.Document.match(languages=["ger"])
+        assert german_docs.count > 0
+        assert german_docs.count < all_docs.count
+
     def test_search_by_translation(self):
         language = digitalarchive.models.Language(id="chi")
         docs = digitalarchive.Document.match(translations=[language])
diff --git a/tests/unit/test_models.py b/tests/unit/test_models.py
index c943d72..fb84a01 100644
--- a/tests/unit/test_models.py
+++ b/tests/unit/test_models.py
@@ -192,6 +192,10 @@ class TestDocument:
         # mock_matching.assert_called_with(models.Document, collection[]= mock_collections, model="Record"})
         assert "collection[]" in mock_matching.call_args[1].keys()
 
+    def test_invalid_language_search(self):
+        with pytest.raises(exceptions.MalformedLanguageSearch):
+            models.Document.match(languages=["invalid"])
+
     @unittest.mock.patch("digitalarchive.models.api")
     def test_process_date_search_only_end_date(self, mock_api):
         test_date = date(1989, 4, 15)

Accept ISO 639-3 language codes when searching for documents by language
Currently, users must pass an entire language object to limit searches by language. Adding the ability to simply pass a 639-3 language string would make searches easier.


tests/unit/test_models.py::TestDocument::test_invalid_language_search
tests/unit/test_models.py::TestMatchableResource::test_match_name tests/unit/test_models.py::TestMatchableResource::test_match_value tests/unit/test_models.py::TestMatchableResource::test_match_handle_term_and_name tests/unit/test_models.py::TestHydrateableResource::test_pull tests/unit/test_models.py::TestHydrateableResource::test_hydrate tests/unit/test_models.py::TestCollection::test_match tests/unit/test_models.py::TestCollection::test_datetime_parsing tests/unit/test_models.py::TestDocument::test_match tests/unit/test_models.py::TestDocument::test_match_date_search_handling tests/unit/test_models.py::TestDocument::test_match_q_field_construction tests/unit/test_models.py::TestDocument::test_related_model_searches tests/unit/test_models.py::TestDocument::test_process_date_search_only_end_date tests/unit/test_models.py::TestDocument::test_valid_eq tests/unit/test_models.py::TestDocument::test_invalid_eq tests/unit/test_models.py::TestDocument::test_invalid_eq_class tests/unit/test_models.py::TestDocument::test_hash tests/unit/test_models.py::TestDocument::test_date_parsing tests/unit/test_models.py::TestDocument::test_hydrate tests/unit/test_models.py::TestDocument::test_hydrate_recursive tests/unit/test_models.py::TestDocument::test_parse_child_records tests/unit/test_models.py::TestDocument::test_parse_child_records_empty tests/unit/test_models.py::TestDocument::test_process_related_model_searches_languages tests/unit/test_models.py::TestDocument::test_match_invalid_field tests/unit/test_models.py::TestDocument::test_process_date_search_invalid_date_str tests/unit/test_models.py::TestDocument::test_process_date_search_invalid_date_obj tests/unit/test_models.py::TestDocument::test_process_related_model_searches_too_many_params tests/unit/test_models.py::TestAsset::test_init tests/unit/test_models.py::TestTranscript::test_hydrate tests/unit/test_models.py::TestTranscript::test_hydrate_html tests/unit/test_models.py::TestTranscript::test_hydrate_pdf tests/unit/test_models.py::TestTranscript::test_hydrate_bad_extension tests/unit/test_models.py::TestTranscript::test_hydrate_server_error tests/unit/test_models.py::TestTranslation::test_init tests/unit/test_models.py::TestMediaFile::test_init tests/unit/test_models.py::TestCoverage::test_init_no_parent tests/unit/test_models.py::TestCoverage::test_init_parent_parsing tests/unit/test_models.py::TestCoverage::test_init_children_parsing tests/unit/test_models.py::TestTheme::test_init_collections tests/unit/test_models.py::TestTheme::test_pull