diff --git a/administrator/components/com_installer/src/Model/LanguagesModel.php b/administrator/components/com_installer/src/Model/LanguagesModel.php
index 39f381c03fe31..7aba317158d01 100644
--- a/administrator/components/com_installer/src/Model/LanguagesModel.php
+++ b/administrator/components/com_installer/src/Model/LanguagesModel.php
@@ -155,7 +155,7 @@ protected function getLanguages()
         }
 
         $languages     = [];
-        $search        = strtolower($this->getState('filter.search'));
+        $search        = strtolower($this->getState('filter.search', ''));
 
         foreach ($updateSiteXML->extension as $extension) {
             $language = new \stdClass();
@@ -183,9 +183,9 @@ protected function getLanguages()
         usort(
             $languages,
             function ($a, $b) use ($that) {
-                $ordering = $that->getState('list.ordering');
+                $ordering = $that->getState('list.ordering', 'name');
 
-                if (strtolower($that->getState('list.direction')) === 'asc') {
+                if (strtolower($that->getState('list.direction', 'asc')) === 'asc') {
                     return StringHelper::strcmp($a->$ordering, $b->$ordering);
                 }
 
@@ -195,9 +195,9 @@ function ($a, $b) use ($that) {
 
         // Count the non-paginated list
         $this->languageCount = \count($languages);
-        $limit               = ($this->getState('list.limit') > 0) ? $this->getState('list.limit') : $this->languageCount;
+        $limit               = ($this->getState('list.limit', 20) > 0) ? $this->getState('list.limit', 20) : $this->languageCount;
 
-        return \array_slice($languages, $this->getStart(), $limit);
+        return \array_slice($languages, $this->getStart() ?? 0, $limit);
     }
 
     /**
diff --git a/api/components/com_installer/src/Controller/LanguagesController.php b/api/components/com_installer/src/Controller/LanguagesController.php
new file mode 100644
index 0000000000000..2095cdad299f6
--- /dev/null
+++ b/api/components/com_installer/src/Controller/LanguagesController.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @package     Joomla.API
+ * @subpackage  com_installer
+ *
+ * @copyright   (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
+ * @license     GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Component\Installer\Api\Controller;
+
+use Joomla\CMS\MVC\Controller\ApiController;
+
+// phpcs:disable PSR1.Files.SideEffects
+\defined('_JEXEC') or die;
+// phpcs:enable PSR1.Files.SideEffects
+
+/**
+ * The manage controller
+ *
+ * @since  __DEPLOY_VERSION__
+ */
+class LanguagesController extends ApiController
+{
+    /**
+     * The content type of the item.
+     *
+     * @var    string
+     * @since  __DEPLOY_VERSION__
+     */
+    protected $contentType = 'languages';
+
+    /**
+     * The default view for the display method.
+     *
+     * @var    string
+     * @since  __DEPLOY_VERSION__
+     */
+    protected $default_view = 'languages';
+}
diff --git a/api/components/com_installer/src/View/Languages/JsonapiView.php b/api/components/com_installer/src/View/Languages/JsonapiView.php
new file mode 100644
index 0000000000000..10e40f9ff7158
--- /dev/null
+++ b/api/components/com_installer/src/View/Languages/JsonapiView.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @package     Joomla.API
+ * @subpackage  com_installer
+ *
+ * @copyright   (C) 2023 Open Source Matters, Inc. <https://www.joomla.org>
+ * @license     GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Component\Installer\Api\View\Languages;
+
+use Joomla\CMS\MVC\View\JsonApiView as BaseApiView;
+
+// phpcs:disable PSR1.Files.SideEffects
+\defined('_JEXEC') or die;
+// phpcs:enable PSR1.Files.SideEffects
+
+/**
+ * The languages view
+ *
+ * @since  __DEPLOY_VERSION__
+ */
+class JsonapiView extends BaseApiView
+{
+    /**
+     * The fields to render item in the documents
+     *
+     * @var  array
+     * @since  __DEPLOY_VERSION__
+     */
+    protected $fieldsToRenderList = [
+        'name',
+        'element',
+        'version',
+        'type',
+        'detailsurl',
+    ];
+
+    protected $i = 0;
+
+    /**
+     * Prepare item before render.
+     *
+     * @param   object  $item  The model item
+     *
+     * @return  object
+     *
+     * @since   __DEPLOY_VERSION__
+     */
+    protected function prepareItem($item)
+    {
+        $item->id = ++$this->i;
+
+        return parent::prepareItem($item);
+    }
+}
diff --git a/tests/System/integration/api/com_languages/Languages.cy.js b/tests/System/integration/api/com_languages/Languages.cy.js
new file mode 100644
index 0000000000000..0d942ff683fa5
--- /dev/null
+++ b/tests/System/integration/api/com_languages/Languages.cy.js
@@ -0,0 +1,7 @@
+describe('Test that languages API endpoint', () => {
+  it('can deliver a list of languages', () => {
+    cy.api_get('/languages')
+      .then((response) => cy.wrap(response).its('body').its('data.0').its('type')
+        .should('include', 'languages'));
+  });
+});