diff --git a/api.php b/api.php
index 8dfb2a23c9ce684ad6461ba704deccd61ef6b212..e6343d97e319e85b778b8e0574805743a339c2a7 100755
--- a/api.php
+++ b/api.php
@@ -35,6 +35,7 @@ use local_asystgrade\utils;
 
 try {
     require_login();
+    require_capability('mod/assign:grade', context_system::instance());
 } catch (coding_exception | moodle_exception $e) {
     debugging($e->getMessage());
     redirect(
@@ -43,53 +44,74 @@ try {
     );
 }
 
-if ($_SERVER['REQUEST_METHOD'] === 'POST') {
-    $data = json_decode(file_get_contents('php://input'), true);
+if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
+    throw new moodle_exception('invalidmethod', 'local_asystgrade');
+}
 
-    if ($data) {
-        // Preparing Flask API.
-        try {
-            $apiendpoint = utils::get_api_endpoint();
-        } catch (dml_exception $e) {
-            debugging('Failed to get API endpoint setting: ' . $e->getMessage());
-        }
+$data = json_decode(file_get_contents('php://input'), true);
 
-        $httpclient = new http_client();
-        $apiclient = client::getInstance($apiendpoint, $httpclient);
+if ($data) {
+    // Preparing Flask API.
+    try {
+        $apiendpoint = utils::get_api_endpoint();
+    } catch (dml_exception $e) {
+        debugging('Failed to get API endpoint setting: ' . $e->getMessage());
+    }
 
-        $maxretries = 3;
-        $attempts = 0;
-        $success = false;
+    $httpclient = new http_client();
+    $apiclient = client::getInstance($apiendpoint, $httpclient);
 
-        while ($attempts < $maxretries && !$success) {
-            try {
-                // Sending data to Flask and obtaining an answer.
-                $response = $apiclient->send_data($data);
-                $success = true;
-            } catch (Exception $e) {
-                $attempts++;
-                debugging('API request error: ' . $e->getMessage());
-                if ($attempts >= $maxretries) {
-                    echo json_encode(['error' => 'A server error occurred. Please try again later.']);
-                    exit; // Ensure to stop further processing.
-                }
-            }
-        }
+    $response = retry_api_request($apiclient, $data);
+    $grades = json_decode($response, true);
+
+    // Check JSON validity.
+    if (json_last_error() !== JSON_ERROR_NONE) {
+        debugging('JSON decode error: ' . json_last_error_msg());
+        throw new moodle_exception('invalidjson', 'local_asystgrade', '', json_last_error_msg());
+    } else {
+        echo json_encode(['success' => true, 'grades' => $grades]);
+    }
+} else {
+    echo json_encode(['error' => 'No data received']);
+}
 
-        if ($success) {
-            $grades = json_decode($response, true);
 
-            // Check JSON validity.
-            if (json_last_error() === JSON_ERROR_NONE) {
-                echo json_encode(['success' => true, 'grades' => $grades]);
-            } else {
-                debugging('JSON decode error: ' . json_last_error_msg());
-                echo json_encode(['error' => 'Invalid JSON from Flask API']);
+/**
+ * Validates the provided request payload data array.
+ *
+ * @param  array $data The data to validate.
+ * @return array The cleaned data.
+ * @throws moodle_exception If the data is invalid.
+ */
+function validate_data($data): array {
+    if (!isset($data['referenceAnswer'], $data['studentAnswers']) || !is_array($data['studentAnswers'])) {
+        throw new moodle_exception('invalidrequest', 'local_asystgrade');
+    }
+    return [
+        'referenceAnswer' => clean_param($data['referenceAnswer'], PARAM_TEXT),
+        'studentAnswers' => array_map(fn($answer) => clean_param($answer, PARAM_TEXT), $data['studentAnswers']),
+    ];
+}
+
+/**
+ * Retries an API request a specified number of times.
+ *
+ * @param  object $apiclient The API client to use for the request.
+ * @param  array $payload The data to send in the request.
+ * @param  int $maxretries The maximum number of retry attempts.
+ * @return mixed The response from the API client.
+ * @throws moodle_exception If the API request fails after the maximum retries.
+ */
+function retry_api_request($apiclient, $payload, $maxretries = 3): mixed
+{
+    for ($attempts = 0; $attempts < $maxretries; $attempts++) {
+        try {
+            return $apiclient->send_data(validate_data($payload));
+        } catch (Exception $e) {
+            debugging('API request error: ' . $e->getMessage());
+            if ($attempts + 1 === $maxretries) {
+                throw new moodle_exception('apifailure', 'local_asystgrade');
             }
         }
-    } else {
-        echo json_encode(['error' => 'No data received']);
     }
-} else {
-    echo json_encode(['error' => 'Invalid request method']);
 }
diff --git a/classes/api/http_client.php b/classes/api/http_client.php
index fdc8e755e39c007ae9e0213dd8e2ef9607c4bb5d..47ffb83905f542b3b069fbeda77d100fddce37b8 100755
--- a/classes/api/http_client.php
+++ b/classes/api/http_client.php
@@ -43,6 +43,8 @@ class http_client implements http_client_interface {
      * @throws Exception
      */
     public function post(string $url, array $data): bool|string {
+        global $CFG;
+        require_once($CFG->libdir . '/filelib.php');
         $curl = new curl();
         $options = [
             'CURLOPT_HTTPHEADER' => ['Content-Type: application/json'],
diff --git a/lang/en/local_asystgrade.php b/lang/en/local_asystgrade.php
index 5bf056b4b3a153eafc5398fe469659223f1e46b7..a7aab5061cafc3143a0a582d02702f22039790eb 100755
--- a/lang/en/local_asystgrade.php
+++ b/lang/en/local_asystgrade.php
@@ -28,3 +28,13 @@ $string['apiendpoint']      = 'API Endpoint';
 $string['apiendpoint_desc'] = 'The endpoint of the AsystGrade API should be changed if you set ML Backend at remote server.';
 $string['pluginname']       = 'ASYST API Moodle integration plugin';
 $string['privacy:metadata'] = 'The AsystGrade plugin does not store any personal data.';
+
+// Error messages.
+$string['loginerror']       = 'You must log in with sufficient permissions to access this page.';
+$string['invalidmethod']    = 'Invalid request method. Only POST requests are allowed.';
+$string['invalidrequest']   = 'Invalid request payload. Required fields are missing or improperly formatted.';
+$string['invalidanswers']   = 'Invalid student answers provided.';
+$string['invalidjson']      = 'Failed to parse JSON response from the server: {$a}';
+$string['apifailure']       = 'The grading API failed after multiple attempts. Please try again later.';
+$string['norequestdata']    = 'No data received from the client.';
+
diff --git a/readme.md b/readme.md
index a6787629710875328aa23e76649445db94038e8b..f9362c3690c35b938ffb2c6a60f1aabd70d578b8 100755
--- a/readme.md
+++ b/readme.md
@@ -172,6 +172,15 @@ In this case it is possible to change an API address from http://127.0.0.1:5001/
 If ASYST ML microservice is running, the grade will appear at every student's answer.
 ![Grading result](https://transfer.hft-stuttgart.de/gitlab/ulrike.pado/asyst-moodle-plugin/-/raw/asyst-moodle-plugin/images/Grading%20result.png)
 
+### Other important settings
+Since Moodle's Curl wrapper is used, it is also necessary to set a few HTTP security properties at the page /admin/settings.php?section=httpsecurity: 
+- remove from cURL blocked hosts list 127.0.0.0/8 and localhost.
+- add to cURL allowed ports list 5001 (or other one that you use for an external custom flask server).
+
+If you are using default flask local server, you could also just run update_http_settings script for that:
+~~~php
+php ./local/asystgrade/update_http_settings.php
+~~~
 
 The structure of request to ASYST ML Backend: 
 ~~~JSON
diff --git a/update_http_settings.php b/update_http_settings.php
new file mode 100755
index 0000000000000000000000000000000000000000..bb3d17ffacb595e1b66e51f07100480a9de7f07e
--- /dev/null
+++ b/update_http_settings.php
@@ -0,0 +1,47 @@
+<?php
+// This file is part of Moodle - https://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <https://www.gnu.org/licenses/>.
+
+/**
+ * CLI Script for the local_asystgrade plugin to set HTTP Curl port and domain permissions.
+ *
+ * @package   local_asystgrade
+ * @copyright 2024 Artem Baranovskyi <artem.baranovsky1980@gmail.com>
+ * @copyright based on work by 2023 Ulrike Pado <ulrike.pado@hft-stuttgart.de>,
+ * @copyright Yunus Eryilmaz & Larissa Kirschner <https://link.springer.com/article/10.1007/s40593-023-00383-w>
+ * @license   https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Define CLI_SCRIPT to indicate this script is being run from the command line.
+ */
+const CLI_SCRIPT = true;
+require('config.php');
+
+global $CFG, $DB;
+
+// Remove 127.0.0.0/8 and localhost from blocked hosts.
+$blockedhosts = get_config('core', 'curlsecurityblockedhosts');
+$newblockedhosts = str_replace(['127.0.0.0/8', 'localhost'], '', $blockedhosts);
+set_config('curlsecurityblockedhosts', trim($newblockedhosts, ','));
+
+// Add 5001 to allowed ports.
+$allowedports = get_config('core', 'curlsecurityallowedport');
+$newallowedports = $allowedports ? $allowedports . "\r\n5001" : '5001';
+set_config('curlsecurityallowedport', $newallowedports);
+
+echo "Settings updated.\n";