diff --git a/client/.eslintrc.json b/client/.eslintrc.json
index 0098e76a..e7dd1e89 100644
--- a/client/.eslintrc.json
+++ b/client/.eslintrc.json
@@ -81,6 +81,19 @@
             }
         ],
         "import/prefer-default-export": "off",
-        "no-alert": "off"
+        "no-alert": "off",
+        "arrow-body-style": "off",
+        "max-len": [
+            "error",
+            120,
+            2,
+            {
+                "ignoreUrls": true,
+                "ignoreComments": false,
+                "ignoreRegExpLiterals": true,
+                "ignoreStrings": true,
+                "ignoreTemplateLiterals": true
+            }
+        ]
     }
 }
diff --git a/client/dev.eslintrc b/client/dev.eslintrc
index 27341caf..9d7e5493 100644
--- a/client/dev.eslintrc
+++ b/client/dev.eslintrc
@@ -1,6 +1,6 @@
 {
     "extends": ".eslintrc",
     "rules": {
-        "no-debugger":"warn",
+        "no-debugger":"warn"
     }
 }
diff --git a/client/package-lock.json b/client/package-lock.json
index ba1d3772..cc6c55ea 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -15094,6 +15094,11 @@
         "setimmediate": "^1.0.4"
       }
     },
+    "timezones-list": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/timezones-list/-/timezones-list-3.0.2.tgz",
+      "integrity": "sha512-I698hm6Jp/xxkwyTSOr39pZkYKETL8LDJeSIhjxXBfPUAHM5oZNuQ4o9UK3PSkDBOkjATecSOBb3pR1IkIBUsg=="
+    },
     "tiny-invariant": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.1.0.tgz",
diff --git a/client/package.json b/client/package.json
index 22b83010..134b98b5 100644
--- a/client/package.json
+++ b/client/package.json
@@ -43,6 +43,7 @@
         "redux-form": "^8.3.5",
         "redux-thunk": "^2.3.0",
         "string-length": "^5.0.1",
+        "timezones-list": "^3.0.2",
         "url-polyfill": "^1.1.9"
     },
     "devDependencies": {
diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json
index 85313bb3..b8e72b3f 100644
--- a/client/src/__locales/en.json
+++ b/client/src/__locales/en.json
@@ -680,5 +680,37 @@
     "protection_section_label": "Protection",
     "log_and_stats_section_label": "Query log and statistics",
     "ignore_query_log": "Ignore this client in query log",
-    "ignore_statistics": "Ignore this client in statistics"
+    "ignore_statistics": "Ignore this client in statistics",
+    "schedule_services": "Pause service blocking",
+    "schedule_services_desc": "Configure the pause schedule of the service-blocking filter",
+    "schedule_services_desc_client": "Configure the pause schedule of the service-blocking filter for this client",
+    "schedule_desc": "Set inactivity periods for blocked services",
+    "schedule_invalid_select": "Start time must be before end time",
+    "schedule_select_days": "Select days",
+    "schedule_timezone": "Select a time zone",
+    "schedule_current_timezone": "Current time zone: {{value}}",
+    "schedule_time_all_day": "All day",
+    "schedule_modal_description": "This schedule will replace any existing schedules for the same day of the week. Each day of the week can have only one inactivity period.",
+    "schedule_modal_time_off": "No service blocking:",
+    "schedule_new": "New schedule",
+    "schedule_edit": "Edit schedule",
+    "schedule_save": "Save schedule",
+    "schedule_add": "Add schedule",
+    "schedule_remove": "Remove schedule",
+    "schedule_from": "From",
+    "schedule_to": "To",
+    "sunday": "Sunday",
+    "monday": "Monday",
+    "tuesday": "Tuesday",
+    "wednesday": "Wednesday",
+    "thursday": "Thursday",
+    "friday": "Friday",
+    "saturday": "Saturday",
+    "sunday_short": "Sun",
+    "monday_short": "Mon",
+    "tuesday_short": "Tue",
+    "wednesday_short": "Wed",
+    "thursday_short": "Thu",
+    "friday_short": "Fri",
+    "saturday_short": "Sat"
 }
diff --git a/client/src/actions/services.js b/client/src/actions/services.js
index 2f5f20db..28ae8837 100644
--- a/client/src/actions/services.js
+++ b/client/src/actions/services.js
@@ -32,19 +32,19 @@ export const getAllBlockedServices = () => async (dispatch) => {
     }
 };
 
-export const setBlockedServicesRequest = createAction('SET_BLOCKED_SERVICES_REQUEST');
-export const setBlockedServicesFailure = createAction('SET_BLOCKED_SERVICES_FAILURE');
-export const setBlockedServicesSuccess = createAction('SET_BLOCKED_SERVICES_SUCCESS');
+export const updateBlockedServicesRequest = createAction('UPDATE_BLOCKED_SERVICES_REQUEST');
+export const updateBlockedServicesFailure = createAction('UPDATE_BLOCKED_SERVICES_FAILURE');
+export const updateBlockedServicesSuccess = createAction('UPDATE_BLOCKED_SERVICES_SUCCESS');
 
-export const setBlockedServices = (values) => async (dispatch) => {
-    dispatch(setBlockedServicesRequest());
+export const updateBlockedServices = (values) => async (dispatch) => {
+    dispatch(updateBlockedServicesRequest());
     try {
-        await apiClient.setBlockedServices(values);
-        dispatch(setBlockedServicesSuccess());
+        await apiClient.updateBlockedServices(values);
+        dispatch(updateBlockedServicesSuccess());
         dispatch(getBlockedServices());
         dispatch(addSuccessToast('blocked_services_saved'));
     } catch (error) {
         dispatch(addErrorToast({ error }));
-        dispatch(setBlockedServicesFailure());
+        dispatch(updateBlockedServicesFailure());
     }
 };
diff --git a/client/src/api/Api.js b/client/src/api/Api.js
index a01c9d04..077c794e 100644
--- a/client/src/api/Api.js
+++ b/client/src/api/Api.js
@@ -489,9 +489,9 @@ class Api {
     }
 
     // Blocked services
-    BLOCKED_SERVICES_LIST = { path: 'blocked_services/list', method: 'GET' };
+    BLOCKED_SERVICES_GET = { path: 'blocked_services/get', method: 'GET' };
 
-    BLOCKED_SERVICES_SET = { path: 'blocked_services/set', method: 'POST' };
+    BLOCKED_SERVICES_UPDATE = { path: 'blocked_services/update', method: 'PUT' };
 
     BLOCKED_SERVICES_ALL = { path: 'blocked_services/all', method: 'GET' };
 
@@ -501,12 +501,12 @@ class Api {
     }
 
     getBlockedServices() {
-        const { path, method } = this.BLOCKED_SERVICES_LIST;
+        const { path, method } = this.BLOCKED_SERVICES_GET;
         return this.makeRequest(path, method);
     }
 
-    setBlockedServices(config) {
-        const { path, method } = this.BLOCKED_SERVICES_SET;
+    updateBlockedServices(config) {
+        const { path, method } = this.BLOCKED_SERVICES_UPDATE;
         const parameters = {
             data: config,
         };
diff --git a/client/src/components/Filters/Services/ScheduleForm/Modal.js b/client/src/components/Filters/Services/ScheduleForm/Modal.js
new file mode 100644
index 00000000..429db9be
--- /dev/null
+++ b/client/src/components/Filters/Services/ScheduleForm/Modal.js
@@ -0,0 +1,220 @@
+import React, { useState, useEffect } from 'react';
+import PropTypes from 'prop-types';
+import { useTranslation } from 'react-i18next';
+import ReactModal from 'react-modal';
+
+import { Timezone } from './Timezone';
+import { TimeSelect } from './TimeSelect';
+import { TimePeriod } from './TimePeriod';
+import { getFullDayName, getShortDayName } from './helpers';
+import { LOCAL_TIMEZONE_VALUE } from '../../../../helpers/constants';
+
+export const DAYS_OF_WEEK = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
+
+const INITIAL_START_TIME_MS = 0;
+const INITIAL_END_TIME_MS = 86340000;
+
+export const Modal = ({
+    isOpen,
+    currentDay,
+    schedule,
+    onClose,
+    onSubmit,
+}) => {
+    const [t] = useTranslation();
+
+    const intialTimezone = schedule.time_zone === LOCAL_TIMEZONE_VALUE
+        ? Intl.DateTimeFormat().resolvedOptions().timeZone
+        : schedule.time_zone;
+
+    const [timezone, setTimezone] = useState(intialTimezone);
+    const [days, setDays] = useState(new Set());
+
+    const [startTime, setStartTime] = useState(INITIAL_START_TIME_MS);
+    const [endTime, setEndTime] = useState(INITIAL_END_TIME_MS);
+
+    const [wrongPeriod, setWrongPeriod] = useState(true);
+
+    useEffect(() => {
+        if (currentDay) {
+            const newDays = new Set([currentDay]);
+            setDays(newDays);
+
+            setStartTime(schedule[currentDay].start);
+            setEndTime(schedule[currentDay].end);
+        }
+    }, [currentDay]);
+
+    useEffect(() => {
+        if (startTime >= endTime) {
+            setWrongPeriod(true);
+        } else {
+            setWrongPeriod(false);
+        }
+    }, [startTime, endTime]);
+
+    const addDays = (day) => {
+        const newDays = new Set(days);
+
+        if (newDays.has(day)) {
+            newDays.delete(day);
+        } else {
+            newDays.add(day);
+        }
+
+        setDays(newDays);
+    };
+
+    const activeDay = (day) => {
+        return days.has(day);
+    };
+
+    const onFormSubmit = (e) => {
+        e.preventDefault();
+
+        if (currentDay) {
+            const newSchedule = schedule;
+
+            Array.from(days).forEach((day) => {
+                newSchedule[day] = {
+                    start: startTime,
+                    end: endTime,
+                };
+            });
+
+            onSubmit(newSchedule);
+        } else {
+            const newSchedule = {
+                time_zone: timezone,
+            };
+
+            Array.from(days).forEach((day) => {
+                newSchedule[day] = {
+                    start: startTime,
+                    end: endTime,
+                };
+            });
+
+            onSubmit(newSchedule);
+        }
+    };
+
+    return (
+        <ReactModal
+            className="Modal__Bootstrap modal-dialog modal-dialog-centered modal-dialog--schedule"
+            closeTimeoutMS={0}
+            isOpen={isOpen}
+            onRequestClose={onClose}
+        >
+            <div className="modal-content">
+                <div className="modal-header">
+                    <h4 className="modal-title">
+                        {currentDay ? t('schedule_edit') : t('schedule_new')}
+                    </h4>
+                    <button type="button" className="close" onClick={onClose}>
+                        <span className="sr-only">Close</span>
+                    </button>
+                </div>
+                <form onSubmit={onFormSubmit}>
+                    <div className="modal-body">
+                        <Timezone
+                            timezone={timezone}
+                            setTimezone={setTimezone}
+                        />
+
+                        <div className="schedule__days">
+                            {DAYS_OF_WEEK.map((day) => (
+                                <button
+                                    type="button"
+                                    key={day}
+                                    className="btn schedule__button-day"
+                                    data-active={activeDay(day)}
+                                    onClick={() => addDays(day)}
+                                >
+                                    {getShortDayName(t, day)}
+                                </button>
+                            )) }
+                        </div>
+
+                        <div className="schedule__time-wrap">
+                            <div className="schedule__time-row">
+                                <TimeSelect
+                                    value={startTime}
+                                    onChange={(v) => setStartTime(v)}
+                                />
+
+                                <TimeSelect
+                                    value={endTime}
+                                    onChange={(v) => setEndTime(v)}
+                                />
+                            </div>
+
+                            {wrongPeriod && (
+                                <div className="schedule__error">
+                                    {t('schedule_invalid_select')}
+                                </div>
+                            )}
+                        </div>
+
+                        <div className="schedule__info">
+                            <div className="schedule__info-title">
+                                {t('schedule_modal_time_off')}
+                            </div>
+                            <div className="schedule__info-row">
+                                <svg className="icons schedule__info-icon">
+                                    <use xlinkHref="#calendar" />
+                                </svg>
+                                {days.size ? (
+                                    Array.from(days).map((day) => getFullDayName(t, day)).join(', ')
+                                ) : (
+                                    <span>
+                                        —
+                                    </span>
+                                )}
+                            </div>
+                            <div className="schedule__info-row">
+                                <svg className="icons schedule__info-icon">
+                                    <use xlinkHref="#watch" />
+                                </svg>
+                                {wrongPeriod ? (
+                                    <span>
+                                        —
+                                    </span>
+                                ) : (
+                                    <TimePeriod
+                                        startTimeMs={startTime}
+                                        endTimeMs={endTime}
+                                    />
+                                )}
+                            </div>
+                        </div>
+
+                        <div className="schedule__notice">
+                            {t('schedule_modal_description')}
+                        </div>
+                    </div>
+                    <div className="modal-footer">
+                        <div className="btn-list">
+                            <button
+                                type="button"
+                                className="btn btn-success btn-standard"
+                                disabled={days.size === 0 || wrongPeriod}
+                                onClick={onFormSubmit}
+                            >
+                                {currentDay ? t('schedule_save') : t('schedule_add')}
+                            </button>
+                        </div>
+                    </div>
+                </form>
+            </div>
+        </ReactModal>
+    );
+};
+
+Modal.propTypes = {
+    schedule: PropTypes.object.isRequired,
+    currentDay: PropTypes.string,
+    isOpen: PropTypes.bool.isRequired,
+    onClose: PropTypes.func.isRequired,
+    onSubmit: PropTypes.func.isRequired,
+};
diff --git a/client/src/components/Filters/Services/ScheduleForm/TimePeriod.js b/client/src/components/Filters/Services/ScheduleForm/TimePeriod.js
new file mode 100644
index 00000000..69ef2293
--- /dev/null
+++ b/client/src/components/Filters/Services/ScheduleForm/TimePeriod.js
@@ -0,0 +1,25 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { getTimeFromMs } from './helpers';
+
+export const TimePeriod = ({
+    startTimeMs,
+    endTimeMs,
+}) => {
+    const startTime = getTimeFromMs(startTimeMs);
+    const endTime = getTimeFromMs(endTimeMs);
+
+    return (
+        <div className="schedule__time">
+            <time>{startTime.hours}:{startTime.minutes}</time>
+            &nbsp;–&nbsp;
+            <time>{endTime.hours}:{endTime.minutes}</time>
+        </div>
+    );
+};
+
+TimePeriod.propTypes = {
+    startTimeMs: PropTypes.number.isRequired,
+    endTimeMs: PropTypes.number.isRequired,
+};
diff --git a/client/src/components/Filters/Services/ScheduleForm/TimeSelect.js b/client/src/components/Filters/Services/ScheduleForm/TimeSelect.js
new file mode 100644
index 00000000..35998437
--- /dev/null
+++ b/client/src/components/Filters/Services/ScheduleForm/TimeSelect.js
@@ -0,0 +1,60 @@
+import React, { useState } from 'react';
+import PropTypes from 'prop-types';
+
+import { getTimeFromMs, convertTimeToMs } from './helpers';
+
+export const TimeSelect = ({
+    value,
+    onChange,
+}) => {
+    const { hours: initialHours, minutes: initialMinutes } = getTimeFromMs(value);
+
+    const [hours, setHours] = useState(initialHours);
+    const [minutes, setMinutes] = useState(initialMinutes);
+
+    const hourOptions = Array.from({ length: 24 }, (_, i) => i.toString().padStart(2, '0'));
+    const minuteOptions = Array.from({ length: 60 }, (_, i) => i.toString().padStart(2, '0'));
+
+    const onHourChange = (event) => {
+        setHours(event.target.value);
+        onChange(convertTimeToMs(event.target.value, minutes));
+    };
+
+    const onMinuteChange = (event) => {
+        setMinutes(event.target.value);
+        onChange(convertTimeToMs(hours, event.target.value));
+    };
+
+    return (
+        <div className="schedule__time-select">
+            <select
+                value={hours}
+                onChange={onHourChange}
+                className="form-control custom-select"
+            >
+                {hourOptions.map((hour) => (
+                    <option key={hour} value={hour}>
+                        {hour}
+                    </option>
+                ))}
+            </select>
+            &nbsp;:&nbsp;
+            <select
+                value={minutes}
+                onChange={onMinuteChange}
+                className="form-control custom-select"
+            >
+                {minuteOptions.map((minute) => (
+                    <option key={minute} value={minute}>
+                        {minute}
+                    </option>
+                ))}
+            </select>
+        </div>
+    );
+};
+
+TimeSelect.propTypes = {
+    value: PropTypes.number.isRequired,
+    onChange: PropTypes.func.isRequired,
+};
diff --git a/client/src/components/Filters/Services/ScheduleForm/Timezone.js b/client/src/components/Filters/Services/ScheduleForm/Timezone.js
new file mode 100644
index 00000000..71d017d6
--- /dev/null
+++ b/client/src/components/Filters/Services/ScheduleForm/Timezone.js
@@ -0,0 +1,46 @@
+import React from 'react';
+import timezones from 'timezones-list';
+import { useTranslation } from 'react-i18next';
+import PropTypes from 'prop-types';
+
+import { LOCAL_TIMEZONE_VALUE } from '../../../../helpers/constants';
+
+export const Timezone = ({
+    timezone,
+    setTimezone,
+}) => {
+    const [t] = useTranslation();
+
+    const onTimeZoneChange = (event) => {
+        setTimezone(event.target.value);
+    };
+
+    return (
+        <div className="schedule__timezone">
+            <label className="form__label form__label--with-desc mb-2">
+                {t('schedule_timezone')}
+            </label>
+
+            <select
+                className="form-control custom-select"
+                value={timezone}
+                onChange={onTimeZoneChange}
+            >
+                <option value={LOCAL_TIMEZONE_VALUE}>
+                    {t('schedule_timezone')}
+                </option>
+                {/* TODO: get timezones from backend method when the method is ready */}
+                {timezones.map((zone) => (
+                    <option key={zone.name} value={zone.tzCode}>
+                        {zone.label}
+                    </option>
+                ))}
+            </select>
+        </div>
+    );
+};
+
+Timezone.propTypes = {
+    timezone: PropTypes.string.isRequired,
+    setTimezone: PropTypes.func.isRequired,
+};
diff --git a/client/src/components/Filters/Services/ScheduleForm/helpers.js b/client/src/components/Filters/Services/ScheduleForm/helpers.js
new file mode 100644
index 00000000..c3986ed4
--- /dev/null
+++ b/client/src/components/Filters/Services/ScheduleForm/helpers.js
@@ -0,0 +1,46 @@
+export const getFullDayName = (t, abbreviation) => {
+    const dayMap = {
+        sun: t('sunday'),
+        mon: t('monday'),
+        tue: t('tuesday'),
+        wed: t('wednesday'),
+        thu: t('thursday'),
+        fri: t('friday'),
+        sat: t('saturday'),
+    };
+
+    return dayMap[abbreviation] || '';
+};
+
+export const getShortDayName = (t, abbreviation) => {
+    const dayMap = {
+        sun: t('sunday_short'),
+        mon: t('monday_short'),
+        tue: t('tuesday_short'),
+        wed: t('wednesday_short'),
+        thu: t('thursday_short'),
+        fri: t('friday_short'),
+        sat: t('saturday_short'),
+    };
+
+    return dayMap[abbreviation] || '';
+};
+
+export const getTimeFromMs = (value) => {
+    const selectedTime = new Date(value);
+    const hours = selectedTime.getUTCHours();
+    const minutes = selectedTime.getUTCMinutes();
+
+    return {
+        hours: hours.toString().padStart(2, '0'),
+        minutes: minutes.toString().padStart(2, '0'),
+    };
+};
+
+export const convertTimeToMs = (hours, minutes) => {
+    const selectedTime = new Date(0);
+    selectedTime.setUTCHours(parseInt(hours, 10));
+    selectedTime.setUTCMinutes(parseInt(minutes, 10));
+
+    return selectedTime.getTime();
+};
diff --git a/client/src/components/Filters/Services/ScheduleForm/index.js b/client/src/components/Filters/Services/ScheduleForm/index.js
new file mode 100644
index 00000000..f7bf605b
--- /dev/null
+++ b/client/src/components/Filters/Services/ScheduleForm/index.js
@@ -0,0 +1,140 @@
+import React, { useState, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import PropTypes from 'prop-types';
+import cn from 'classnames';
+
+import { Modal } from './Modal';
+import { getFullDayName, getShortDayName } from './helpers';
+import { LOCAL_TIMEZONE_VALUE } from '../../../../helpers/constants';
+import { TimePeriod } from './TimePeriod';
+import './styles.css';
+
+export const ScheduleForm = ({
+    schedule,
+    onScheduleSubmit,
+    clientForm,
+}) => {
+    const [t] = useTranslation();
+    const [modalOpen, setModalOpen] = useState(false);
+    const [currentDay, setCurrentDay] = useState();
+
+    const onModalOpen = () => setModalOpen(true);
+    const onModalClose = () => setModalOpen(false);
+
+    const filteredScheduleKeys = useMemo(() => (
+        schedule ? Object.keys(schedule).filter((v) => v !== 'time_zone') : []
+    ), [schedule]);
+
+    const scheduleMap = new Map();
+    filteredScheduleKeys.forEach((day) => scheduleMap.set(day, schedule[day]));
+
+    const onSubmit = (values) => {
+        onScheduleSubmit(values);
+        onModalClose();
+    };
+
+    const onDelete = (day) => {
+        scheduleMap.delete(day);
+
+        const scheduleWeek = Object.fromEntries(Array.from(scheduleMap.entries()));
+
+        onScheduleSubmit({
+            time_zone: schedule.time_zone,
+            ...scheduleWeek,
+        });
+    };
+
+    const onEdit = (day) => {
+        setCurrentDay(day);
+        onModalOpen();
+    };
+
+    const onAdd = () => {
+        setCurrentDay(undefined);
+        onModalOpen();
+    };
+
+    return (
+        <div>
+            <div className="schedule__current-timezone">
+                {t('schedule_current_timezone', { value: schedule?.time_zone || LOCAL_TIMEZONE_VALUE })}
+            </div>
+
+            <div className="schedule__rows">
+                {filteredScheduleKeys.map((day) => {
+                    const data = schedule[day];
+
+                    if (!data) {
+                        return undefined;
+                    }
+
+                    return (
+                        <div key={day} className="schedule__row">
+                            <div className="schedule__day">
+                                {getFullDayName(t, day)}
+                            </div>
+                            <div className="schedule__day schedule__day--mobile">
+                                {getShortDayName(t, day)}
+                            </div>
+                            <TimePeriod
+                                startTimeMs={data.start}
+                                endTimeMs={data.end}
+                            />
+                            <div className="schedule__actions">
+                                <button
+                                    type="button"
+                                    className="btn btn-icon btn-outline-primary btn-sm schedule__button"
+                                    title={t('edit_table_action')}
+                                    onClick={() => onEdit(day)}
+                                >
+                                    <svg className="icons icon12">
+                                        <use xlinkHref="#edit" />
+                                    </svg>
+                                </button>
+
+                                <button
+                                    type="button"
+                                    className="btn btn-icon btn-outline-secondary btn-sm schedule__button"
+                                    title={t('delete_table_action')}
+                                    onClick={() => onDelete(day)}
+                                >
+                                    <svg className="icons">
+                                        <use xlinkHref="#delete" />
+                                    </svg>
+                                </button>
+                            </div>
+                        </div>
+                    );
+                })}
+            </div>
+
+            <button
+                type="button"
+                className={cn(
+                    'btn',
+                    { 'btn-outline-success btn-sm': clientForm },
+                    { 'btn-success btn-standard': !clientForm },
+                )}
+                onClick={onAdd}
+            >
+                {t('schedule_new')}
+            </button>
+
+            {modalOpen && (
+                <Modal
+                    isOpen={modalOpen}
+                    onClose={onModalClose}
+                    onSubmit={onSubmit}
+                    schedule={schedule}
+                    currentDay={currentDay}
+                />
+            )}
+        </div>
+    );
+};
+
+ScheduleForm.propTypes = {
+    schedule: PropTypes.object,
+    onScheduleSubmit: PropTypes.func.isRequired,
+    clientForm: PropTypes.bool,
+};
diff --git a/client/src/components/Filters/Services/ScheduleForm/styles.css b/client/src/components/Filters/Services/ScheduleForm/styles.css
new file mode 100644
index 00000000..f839f92a
--- /dev/null
+++ b/client/src/components/Filters/Services/ScheduleForm/styles.css
@@ -0,0 +1,134 @@
+.schedule__row {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    margin-bottom: 8px;
+}
+
+.schedule__row:last-child {
+    margin-bottom: 0;
+}
+
+.schedule__rows {
+    margin-bottom: 24px;
+}
+
+.schedule__day {
+    display: none;
+    min-width: 110px;
+}
+
+.schedule__day--mobile {
+    display: block;
+    min-width: 50px;
+}
+
+@media screen and (min-width: 767px) {
+    .schedule__row {
+        justify-content: flex-start;
+    }
+
+    .schedule__day {
+        display: block;
+    }
+
+    .schedule__day--mobile {
+        display: none;
+    }
+
+    .schedule__actions {
+        margin-left: 32px;
+        white-space: nowrap;
+    }
+}
+
+.schedule__time {
+    min-width: 110px;
+}
+
+.schedule__button {
+    border: 0;
+}
+
+.schedule__days {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+    margin-bottom: 24px;
+}
+
+.schedule__button-day {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    min-width: 60px;
+    height: 32px;
+    font-size: 14px;
+    line-height: 14px;
+    color: #495057;
+    background-color: transparent;
+    border: 1px solid var(--card-border-color);
+    border-radius: 4px;
+    cursor: pointer;
+    outline: 0;
+}
+
+.schedule__button-day[data-active="true"] {
+    color: var(--btn-success-bgcolor);
+    border-color: var(--btn-success-bgcolor);
+}
+
+.schedule__time-wrap {
+    margin-bottom: 24px;
+}
+
+.schedule__time-row {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+}
+
+.schedule__time-select {
+    display: flex;
+    align-items: center;
+}
+
+.schedule__error {
+    margin-top: 4px;
+    font-size: 13px;
+    color: #cd201f;
+}
+
+.schedule__timezone {
+    margin-bottom: 24px;
+}
+
+.schedule__current-timezone {
+    margin-bottom: 16px;
+}
+
+.schedule__info {
+    margin-bottom: 24px;
+}
+
+.schedule__notice {
+    font-size: 13px;
+}
+
+.schedule__info-title {
+    margin-bottom: 8px;
+}
+
+.schedule__info-row {
+    display: flex;
+    align-items: center;
+    margin-bottom: 4px;
+}
+
+.schedule__info-icon {
+    width: 24px;
+    height: 24px;
+    margin-right: 8px;
+    color: #495057;
+    flex-shrink: 0;
+}
diff --git a/client/src/components/Filters/Services/index.js b/client/src/components/Filters/Services/index.js
index 09cdd7c8..3a0a12fd 100644
--- a/client/src/components/Filters/Services/index.js
+++ b/client/src/components/Filters/Services/index.js
@@ -4,9 +4,11 @@ import { useTranslation } from 'react-i18next';
 import { useDispatch, useSelector } from 'react-redux';
 import Form from './Form';
 import Card from '../../ui/Card';
-import { getBlockedServices, getAllBlockedServices, setBlockedServices } from '../../../actions/services';
+import { getBlockedServices, getAllBlockedServices, updateBlockedServices } from '../../../actions/services';
 import PageTitle from '../../ui/PageTitle';
 
+import { ScheduleForm } from './ScheduleForm';
+
 const getInitialDataForServices = (initial) => (initial ? initial.reduce(
     (acc, service) => {
         acc.blocked_services[service] = true;
@@ -33,10 +35,24 @@ const Services = () => {
             .keys(values.blocked_services)
             .filter((service) => values.blocked_services[service]);
 
-        dispatch(setBlockedServices(blocked_services));
+        dispatch(updateBlockedServices({
+            ids: blocked_services,
+            schedule: services.list.schedule,
+        }));
     };
 
-    const initialValues = getInitialDataForServices(services.list);
+    const handleScheduleSubmit = (values) => {
+        dispatch(updateBlockedServices({
+            ids: services.list.ids,
+            schedule: values,
+        }));
+    };
+
+    const initialValues = getInitialDataForServices(services.list.ids);
+
+    if (!initialValues) {
+        return null;
+    }
 
     return (
         <>
@@ -57,6 +73,17 @@ const Services = () => {
                     />
                 </div>
             </Card>
+
+            <Card
+                title={t('schedule_services')}
+                subtitle={t('schedule_services_desc')}
+                bodyType="card-body box-body--settings"
+            >
+                <ScheduleForm
+                    schedule={services.list.schedule}
+                    onScheduleSubmit={handleScheduleSubmit}
+                />
+            </Card>
         </>
     );
 };
diff --git a/client/src/components/Settings/Clients/ClientsTable/ClientsTable.js b/client/src/components/Settings/Clients/ClientsTable/ClientsTable.js
index dab8994c..96f835c3 100644
--- a/client/src/components/Settings/Clients/ClientsTable/ClientsTable.js
+++ b/client/src/components/Settings/Clients/ClientsTable/ClientsTable.js
@@ -6,7 +6,7 @@ import { Trans, useTranslation } from 'react-i18next';
 import { useDispatch, useSelector } from 'react-redux';
 import ReactTable from 'react-table';
 
-import { getAllBlockedServices } from '../../../../actions/services';
+import { getAllBlockedServices, getBlockedServices } from '../../../../actions/services';
 import { initSettings } from '../../../../actions';
 import {
     splitByNewLine,
@@ -14,7 +14,7 @@ import {
     sortIp,
     getService,
 } from '../../../../helpers/helpers';
-import { MODAL_TYPE } from '../../../../helpers/constants';
+import { MODAL_TYPE, LOCAL_TIMEZONE_VALUE } from '../../../../helpers/constants';
 import Card from '../../../ui/Card';
 import CellWrap from '../../../ui/CellWrap';
 import LogsSearchLink from '../../../ui/LogsSearchLink';
@@ -45,6 +45,7 @@ const ClientsTable = ({
 
     useEffect(() => {
         dispatch(getAllBlockedServices());
+        dispatch(getBlockedServices());
         dispatch(initSettings());
     }, []);
 
@@ -112,6 +113,9 @@ const ClientsTable = ({
             tags: [],
             use_global_settings: true,
             use_global_blocked_services: true,
+            blocked_services_schedule: {
+                time_zone: LOCAL_TIMEZONE_VALUE,
+            },
             safe_search: { ...(safesearch || {}) },
         };
     };
diff --git a/client/src/components/Settings/Clients/Form.js b/client/src/components/Settings/Clients/Form.js
index 190996e4..652957d0 100644
--- a/client/src/components/Settings/Clients/Form.js
+++ b/client/src/components/Settings/Clients/Form.js
@@ -11,6 +11,7 @@ import Select from 'react-select';
 import i18n from '../../../i18n';
 import Tabs from '../../ui/Tabs';
 import Examples from '../Dns/Upstream/Examples';
+import { ScheduleForm } from '../../Filters/Services/ScheduleForm';
 import { toggleAllServices, trimLinesAndRemoveEmpty, captitalizeWords } from '../../../helpers/helpers';
 import {
     renderInputField,
@@ -137,10 +138,10 @@ let Form = (props) => {
         handleSubmit,
         reset,
         change,
-        pristine,
         submitting,
         useGlobalSettings,
         useGlobalServices,
+        blockedServicesSchedule,
         toggleClientModal,
         processingAdding,
         processingUpdating,
@@ -155,6 +156,10 @@ let Form = (props) => {
 
     const [activeTabLabel, setActiveTabLabel] = useState('settings');
 
+    const handleScheduleSubmit = (values) => {
+        change('blocked_services_schedule', values);
+    };
+
     const tabs = {
         settings: {
             title: 'settings',
@@ -269,6 +274,21 @@ let Form = (props) => {
                 </div>
             </div>,
         },
+        schedule_services: {
+            title: 'schedule_services',
+            component: (
+                <>
+                    <div className="form__desc mb-4">
+                        <Trans>schedule_services_desc_client</Trans>
+                    </div>
+                    <ScheduleForm
+                        schedule={blockedServicesSchedule}
+                        onScheduleSubmit={handleScheduleSubmit}
+                        clientForm
+                    />
+                </>
+            ),
+        },
         upstream_dns: {
             title: 'upstream_dns',
             component: <div label="upstream" title={props.t('upstream_dns')}>
@@ -355,8 +375,12 @@ let Form = (props) => {
                     </div>
                 </div>
 
-                <Tabs controlClass="form" tabs={tabs} activeTabLabel={activeTabLabel}
-                      setActiveTabLabel={setActiveTabLabel}>
+                <Tabs
+                    controlClass="form"
+                    tabs={tabs}
+                    activeTabLabel={activeTabLabel}
+                    setActiveTabLabel={setActiveTabLabel}
+                >
                     {activeTab}
                 </Tabs>
             </div>
@@ -380,7 +404,6 @@ let Form = (props) => {
                         disabled={
                             submitting
                             || invalid
-                            || pristine
                             || processingAdding
                             || processingUpdating
                         }
@@ -402,6 +425,7 @@ Form.propTypes = {
     toggleClientModal: PropTypes.func.isRequired,
     useGlobalSettings: PropTypes.bool,
     useGlobalServices: PropTypes.bool,
+    blockedServicesSchedule: PropTypes.object,
     t: PropTypes.func.isRequired,
     processingAdding: PropTypes.bool.isRequired,
     processingUpdating: PropTypes.bool.isRequired,
@@ -415,9 +439,11 @@ const selector = formValueSelector(FORM_NAME.CLIENT);
 Form = connect((state) => {
     const useGlobalSettings = selector(state, 'use_global_settings');
     const useGlobalServices = selector(state, 'use_global_blocked_services');
+    const blockedServicesSchedule = selector(state, 'blocked_services_schedule');
     return {
         useGlobalSettings,
         useGlobalServices,
+        blockedServicesSchedule,
     };
 })(Form);
 
diff --git a/client/src/components/ui/Icons.js b/client/src/components/ui/Icons.js
index b38da489..288eadfc 100644
--- a/client/src/components/ui/Icons.js
+++ b/client/src/components/ui/Icons.js
@@ -225,6 +225,20 @@ const Icons = () => (
                 <path stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" d="M8.036 10.93l3.93 4.07 4.068-3.93" />
             </g>
         </symbol>
+
+        <symbol id="calendar" fill="none" height="24" viewBox="0 0 24 24" width="24" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
+            <rect x="4" y="5.5" width="16" height="14" rx="3" />
+            <path d="M12 4V7" />
+            <path d="M8 4L8 7" />
+            <path d="M16 4V7" />
+            <path d="M9.7397 15.5V11L8 13" />
+            <path d="M14.7397 15.5V11L13 13" />
+        </symbol>
+
+        <symbol id="watch" fill="none" height="24" viewBox="0 0 24 24" width="24" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
+            <circle cx="12" cy="12" r="9" />
+            <path d="M16.1215 12.1213H11.8789V7.87866" />
+        </symbol>
     </svg>
 );
 
diff --git a/client/src/components/ui/Modal.css b/client/src/components/ui/Modal.css
index b22b9709..13adb794 100644
--- a/client/src/components/ui/Modal.css
+++ b/client/src/components/ui/Modal.css
@@ -41,7 +41,8 @@
 }
 
 @media (min-width: 576px) {
-    .modal-dialog--clients {
+    .modal-dialog--clients,
+    .modal-dialog--schedule {
         max-width: 650px;
     }
 }
diff --git a/client/src/components/ui/Tabler.css b/client/src/components/ui/Tabler.css
index 69b0e1c4..d49810e2 100644
--- a/client/src/components/ui/Tabler.css
+++ b/client/src/components/ui/Tabler.css
@@ -470,6 +470,10 @@ hr {
     border-top: 1px solid rgba(0, 40, 100, 0.12);
 }
 
+[data-theme=dark] hr {
+    border-color: var(--card-border-color);
+}
+
 small,
 .small {
     font-size: 87.5%;
diff --git a/client/src/containers/Settings.js b/client/src/containers/Settings.js
index 866765de..6222f8eb 100644
--- a/client/src/containers/Settings.js
+++ b/client/src/containers/Settings.js
@@ -1,6 +1,6 @@
 import { connect } from 'react-redux';
 import { initSettings, toggleSetting } from '../actions';
-import { getBlockedServices, setBlockedServices } from '../actions/services';
+import { getBlockedServices, updateBlockedServices } from '../actions/services';
 import { getStatsConfig, setStatsConfig, resetStats } from '../actions/stats';
 import { clearLogs, getLogsConfig, setLogsConfig } from '../actions/queryLogs';
 import { getFilteringStatus, setFiltersConfig } from '../actions/filtering';
@@ -24,7 +24,7 @@ const mapDispatchToProps = {
     initSettings,
     toggleSetting,
     getBlockedServices,
-    setBlockedServices,
+    updateBlockedServices,
     getStatsConfig,
     setStatsConfig,
     resetStats,
diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js
index ec1e437e..e436d77f 100644
--- a/client/src/helpers/constants.js
+++ b/client/src/helpers/constants.js
@@ -552,3 +552,5 @@ export const DISABLE_PROTECTION_TIMINGS = {
 };
 
 export const LOCAL_STORAGE_THEME_KEY = 'account_theme';
+
+export const LOCAL_TIMEZONE_VALUE = 'Local';
diff --git a/client/src/reducers/services.js b/client/src/reducers/services.js
index c0663c8e..07b9947c 100644
--- a/client/src/reducers/services.js
+++ b/client/src/reducers/services.js
@@ -20,9 +20,9 @@ const services = handleActions(
             processingAll: false,
         }),
 
-        [actions.setBlockedServicesRequest]: (state) => ({ ...state, processingSet: true }),
-        [actions.setBlockedServicesFailure]: (state) => ({ ...state, processingSet: false }),
-        [actions.setBlockedServicesSuccess]: (state) => ({
+        [actions.updateBlockedServicesRequest]: (state) => ({ ...state, processingSet: true }),
+        [actions.updateBlockedServicesFailure]: (state) => ({ ...state, processingSet: false }),
+        [actions.updateBlockedServicesSuccess]: (state) => ({
             ...state,
             processingSet: false,
         }),
@@ -31,7 +31,7 @@ const services = handleActions(
         processing: true,
         processingAll: true,
         processingSet: false,
-        list: [],
+        list: {},
         allServices: [],
     },
 );
diff --git a/internal/aghhttp/aghhttp.go b/internal/aghhttp/aghhttp.go
index 6cb2c670..88dfe7a7 100644
--- a/internal/aghhttp/aghhttp.go
+++ b/internal/aghhttp/aghhttp.go
@@ -2,7 +2,6 @@
 package aghhttp
 
 import (
-	"encoding/json"
 	"fmt"
 	"io"
 	"net/http"
@@ -61,23 +60,3 @@ func WriteTextPlainDeprecated(w http.ResponseWriter, r *http.Request) (isPlainTe
 
 	return true
 }
-
-// WriteJSONResponse sets the content-type header in w.Header() to
-// "application/json", writes a header with a "200 OK" status, encodes resp to
-// w, calls [Error] on any returned error, and returns it as well.
-func WriteJSONResponse(w http.ResponseWriter, r *http.Request, resp any) (err error) {
-	return WriteJSONResponseCode(w, r, http.StatusOK, resp)
-}
-
-// WriteJSONResponseCode is like [WriteJSONResponse] but adds the ability to
-// redefine the status code.
-func WriteJSONResponseCode(w http.ResponseWriter, r *http.Request, code int, resp any) (err error) {
-	w.Header().Set(httphdr.ContentType, HdrValApplicationJSON)
-	w.WriteHeader(code)
-	err = json.NewEncoder(w).Encode(resp)
-	if err != nil {
-		Error(r, w, http.StatusInternalServerError, "encoding resp: %s", err)
-	}
-
-	return err
-}
diff --git a/internal/next/websvc/json.go b/internal/aghhttp/json.go
similarity index 75%
rename from internal/next/websvc/json.go
rename to internal/aghhttp/json.go
index f7622b63..b7eca767 100644
--- a/internal/next/websvc/json.go
+++ b/internal/aghhttp/json.go
@@ -1,4 +1,4 @@
-package websvc
+package aghhttp
 
 import (
 	"encoding/json"
@@ -7,7 +7,6 @@ import (
 	"strconv"
 	"time"
 
-	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/golibs/httphdr"
 	"github.com/AdguardTeam/golibs/log"
 )
@@ -87,30 +86,29 @@ func (t *JSONTime) UnmarshalJSON(b []byte) (err error) {
 	return nil
 }
 
-// writeJSONOKResponse writes headers with the code 200 OK, encodes v into w,
-// and logs any errors it encounters.  r is used to get additional information
-// from the request.
-func writeJSONOKResponse(w http.ResponseWriter, r *http.Request, v any) {
-	writeJSONResponse(w, r, v, http.StatusOK)
-}
-
-// writeJSONResponse writes headers with code, encodes v into w, and logs any
-// errors it encounters.  r is used to get additional information from the
+// WriteJSONResponse writes headers with the code, encodes resp into w, and logs
+// any errors it encounters.  r is used to get additional information from the
 // request.
-func writeJSONResponse(w http.ResponseWriter, r *http.Request, v any, code int) {
-	// TODO(a.garipov): Put some of these to a middleware.
+func WriteJSONResponse(w http.ResponseWriter, r *http.Request, code int, resp any) {
 	h := w.Header()
-	h.Set(httphdr.ContentType, aghhttp.HdrValApplicationJSON)
-	h.Set(httphdr.Server, aghhttp.UserAgent())
+	h.Set(httphdr.ContentType, HdrValApplicationJSON)
+	h.Set(httphdr.Server, UserAgent())
 
 	w.WriteHeader(code)
 
-	err := json.NewEncoder(w).Encode(v)
+	err := json.NewEncoder(w).Encode(resp)
 	if err != nil {
-		log.Error("websvc: writing resp to %s %s: %s", r.Method, r.URL.Path, err)
+		log.Error("aghhttp: writing json resp to %s %s: %s", r.Method, r.URL.Path, err)
 	}
 }
 
+// WriteJSONResponseOK writes headers with the code 200 OK, encodes v into w,
+// and logs any errors it encounters.  r is used to get additional information
+// from the request.
+func WriteJSONResponseOK(w http.ResponseWriter, r *http.Request, v any) {
+	WriteJSONResponse(w, r, http.StatusOK, v)
+}
+
 // ErrorCode is the error code as used by the HTTP API.  See the ErrorCode
 // definition in the OpenAPI specification.
 type ErrorCode string
@@ -131,14 +129,14 @@ type HTTPAPIErrorResp struct {
 	Msg  string    `json:"msg"`
 }
 
-// writeJSONErrorResponse encodes err as a JSON error into w, and logs any
+// WriteJSONResponseError encodes err as a JSON error into w, and logs any
 // errors it encounters.  r is used to get additional information from the
 // request.
-func writeJSONErrorResponse(w http.ResponseWriter, r *http.Request, err error) {
-	log.Error("websvc: %s %s: %s", r.Method, r.URL.Path, err)
+func WriteJSONResponseError(w http.ResponseWriter, r *http.Request, err error) {
+	log.Error("aghhttp: writing json error to %s %s: %s", r.Method, r.URL.Path, err)
 
-	writeJSONResponse(w, r, &HTTPAPIErrorResp{
+	WriteJSONResponse(w, r, http.StatusUnprocessableEntity, &HTTPAPIErrorResp{
 		Code: ErrorCodeTMP000,
 		Msg:  err.Error(),
-	}, http.StatusUnprocessableEntity)
+	})
 }
diff --git a/internal/next/websvc/json_test.go b/internal/aghhttp/json_test.go
similarity index 81%
rename from internal/next/websvc/json_test.go
rename to internal/aghhttp/json_test.go
index 90874958..9a2d33f1 100644
--- a/internal/next/websvc/json_test.go
+++ b/internal/aghhttp/json_test.go
@@ -1,18 +1,18 @@
-package websvc_test
+package aghhttp_test
 
 import (
 	"encoding/json"
 	"testing"
 	"time"
 
-	"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
+	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/golibs/testutil"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
 // testJSONTime is the JSON time for tests.
-var testJSONTime = websvc.JSONTime(time.Unix(1_234_567_890, 123_456_000).UTC())
+var testJSONTime = aghhttp.JSONTime(time.Unix(1_234_567_890, 123_456_000).UTC())
 
 // testJSONTimeStr is the string with the JSON encoding of testJSONTime.
 const testJSONTimeStr = "1234567890123.456"
@@ -21,17 +21,17 @@ func TestJSONTime_MarshalJSON(t *testing.T) {
 	testCases := []struct {
 		name       string
 		wantErrMsg string
-		in         websvc.JSONTime
+		in         aghhttp.JSONTime
 		want       []byte
 	}{{
 		name:       "unix_zero",
 		wantErrMsg: "",
-		in:         websvc.JSONTime(time.Unix(0, 0)),
+		in:         aghhttp.JSONTime(time.Unix(0, 0)),
 		want:       []byte("0"),
 	}, {
 		name:       "empty",
 		wantErrMsg: "",
-		in:         websvc.JSONTime{},
+		in:         aghhttp.JSONTime{},
 		want:       []byte("-6795364578871.345"),
 	}, {
 		name:       "time",
@@ -51,7 +51,7 @@ func TestJSONTime_MarshalJSON(t *testing.T) {
 
 	t.Run("json", func(t *testing.T) {
 		in := &struct {
-			A websvc.JSONTime
+			A aghhttp.JSONTime
 		}{
 			A: testJSONTime,
 		}
@@ -67,7 +67,7 @@ func TestJSONTime_UnmarshalJSON(t *testing.T) {
 	testCases := []struct {
 		name       string
 		wantErrMsg string
-		want       websvc.JSONTime
+		want       aghhttp.JSONTime
 		data       []byte
 	}{{
 		name:       "time",
@@ -78,13 +78,13 @@ func TestJSONTime_UnmarshalJSON(t *testing.T) {
 		name: "bad",
 		wantErrMsg: `parsing json time: strconv.ParseFloat: parsing "{}": ` +
 			`invalid syntax`,
-		want: websvc.JSONTime{},
+		want: aghhttp.JSONTime{},
 		data: []byte(`{}`),
 	}}
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			var got websvc.JSONTime
+			var got aghhttp.JSONTime
 			err := got.UnmarshalJSON(tc.data)
 			testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
 
@@ -93,7 +93,7 @@ func TestJSONTime_UnmarshalJSON(t *testing.T) {
 	}
 
 	t.Run("nil", func(t *testing.T) {
-		err := (*websvc.JSONTime)(nil).UnmarshalJSON([]byte("0"))
+		err := (*aghhttp.JSONTime)(nil).UnmarshalJSON([]byte("0"))
 		require.Error(t, err)
 
 		msg := err.Error()
@@ -103,7 +103,7 @@ func TestJSONTime_UnmarshalJSON(t *testing.T) {
 	t.Run("json", func(t *testing.T) {
 		want := testJSONTime
 		var got struct {
-			A websvc.JSONTime
+			A aghhttp.JSONTime
 		}
 
 		err := json.Unmarshal([]byte(`{"A":`+testJSONTimeStr+`}`), &got)
diff --git a/internal/dhcpd/http_unix.go b/internal/dhcpd/http_unix.go
index b07f9543..2ba693cc 100644
--- a/internal/dhcpd/http_unix.go
+++ b/internal/dhcpd/http_unix.go
@@ -146,7 +146,7 @@ func (s *server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) {
 	status.Leases = leasesToDynamic(s.Leases(LeasesDynamic))
 	status.StaticLeases = leasesToStatic(s.Leases(LeasesStatic))
 
-	_ = aghhttp.WriteJSONResponse(w, r, status)
+	aghhttp.WriteJSONResponseOK(w, r, status)
 }
 
 func (s *server) enableDHCP(ifaceName string) (code int, err error) {
@@ -395,7 +395,7 @@ func (s *server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	_ = aghhttp.WriteJSONResponse(w, r, resp)
+	aghhttp.WriteJSONResponseOK(w, r, resp)
 }
 
 // newNetInterfaceJSON creates a JSON object from a [net.Interface] iface.
@@ -547,7 +547,7 @@ func (s *server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Reque
 
 	setOtherDHCPResult(ifaceName, result)
 
-	_ = aghhttp.WriteJSONResponse(w, r, result)
+	aghhttp.WriteJSONResponseOK(w, r, result)
 }
 
 // setOtherDHCPResult sets the results of the check for another DHCP server in
diff --git a/internal/dhcpd/http_windows.go b/internal/dhcpd/http_windows.go
index fda72d48..eb82b861 100644
--- a/internal/dhcpd/http_windows.go
+++ b/internal/dhcpd/http_windows.go
@@ -24,7 +24,7 @@ type jsonError struct {
 // TODO(a.garipov): Either take the logger from the server after we've
 // refactored logging or make this not a method of *Server.
 func (s *server) notImplemented(w http.ResponseWriter, r *http.Request) {
-	_ = aghhttp.WriteJSONResponseCode(w, r, http.StatusNotImplemented, &jsonError{
+	aghhttp.WriteJSONResponse(w, r, http.StatusNotImplemented, &jsonError{
 		Message: aghos.Unsupported("dhcp").Error(),
 	})
 }
diff --git a/internal/dnsforward/access.go b/internal/dnsforward/access.go
index d29afbde..ba9300c2 100644
--- a/internal/dnsforward/access.go
+++ b/internal/dnsforward/access.go
@@ -182,7 +182,7 @@ func (s *Server) accessListJSON() (j accessListJSON) {
 }
 
 func (s *Server) handleAccessList(w http.ResponseWriter, r *http.Request) {
-	_ = aghhttp.WriteJSONResponse(w, r, s.accessListJSON())
+	aghhttp.WriteJSONResponseOK(w, r, s.accessListJSON())
 }
 
 // validateAccessSet checks the internal accessListJSON lists.  To search for
diff --git a/internal/dnsforward/http.go b/internal/dnsforward/http.go
index 184e5e7c..3be25372 100644
--- a/internal/dnsforward/http.go
+++ b/internal/dnsforward/http.go
@@ -169,7 +169,7 @@ func (s *Server) getDNSConfig() (c *jsonDNSConfig) {
 // handleGetConfig handles requests to the GET /control/dns_info endpoint.
 func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
 	resp := s.getDNSConfig()
-	_ = aghhttp.WriteJSONResponse(w, r, resp)
+	aghhttp.WriteJSONResponseOK(w, r, resp)
 }
 
 func (req *jsonDNSConfig) checkBlockingMode() (err error) {
@@ -758,7 +758,7 @@ func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	_ = aghhttp.WriteJSONResponse(w, r, result)
+	aghhttp.WriteJSONResponseOK(w, r, result)
 }
 
 // handleCacheClear is the handler for the POST /control/cache_clear HTTP API.
diff --git a/internal/filtering/blocked.go b/internal/filtering/blocked.go
index f403d0ab..2dba12da 100644
--- a/internal/filtering/blocked.go
+++ b/internal/filtering/blocked.go
@@ -50,10 +50,10 @@ func initBlockedServices() {
 // BlockedServices is the configuration of blocked services.
 type BlockedServices struct {
 	// Schedule is blocked services schedule for every day of the week.
-	Schedule *schedule.Weekly `yaml:"schedule"`
+	Schedule *schedule.Weekly `json:"schedule" yaml:"schedule"`
 
 	// IDs is the names of blocked services.
-	IDs []string `yaml:"ids"`
+	IDs []string `json:"ids" yaml:"ids"`
 }
 
 // Clone returns a deep copy of blocked services.
@@ -114,25 +114,33 @@ func (d *DNSFilter) ApplyBlockedServicesList(setts *Settings, list []string) {
 }
 
 func (d *DNSFilter) handleBlockedServicesIDs(w http.ResponseWriter, r *http.Request) {
-	_ = aghhttp.WriteJSONResponse(w, r, serviceIDs)
+	aghhttp.WriteJSONResponseOK(w, r, serviceIDs)
 }
 
 func (d *DNSFilter) handleBlockedServicesAll(w http.ResponseWriter, r *http.Request) {
-	_ = aghhttp.WriteJSONResponse(w, r, struct {
+	aghhttp.WriteJSONResponseOK(w, r, struct {
 		BlockedServices []blockedService `json:"blocked_services"`
 	}{
 		BlockedServices: blockedServices,
 	})
 }
 
+// handleBlockedServicesList is the handler for the GET
+// /control/blocked_services/list HTTP API.
+//
+// Deprecated:  Use handleBlockedServicesGet.
 func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) {
 	d.confLock.RLock()
 	list := d.Config.BlockedServices.IDs
 	d.confLock.RUnlock()
 
-	_ = aghhttp.WriteJSONResponse(w, r, list)
+	aghhttp.WriteJSONResponseOK(w, r, list)
 }
 
+// handleBlockedServicesSet is the handler for the POST
+// /control/blocked_services/set HTTP API.
+//
+// Deprecated:  Use handleBlockedServicesUpdate.
 func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) {
 	list := []string{}
 	err := json.NewDecoder(r.Body).Decode(&list)
@@ -150,3 +158,51 @@ func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Requ
 
 	d.Config.ConfigModified()
 }
+
+// handleBlockedServicesGet is the handler for the GET
+// /control/blocked_services/get HTTP API.
+func (d *DNSFilter) handleBlockedServicesGet(w http.ResponseWriter, r *http.Request) {
+	var bsvc *BlockedServices
+	func() {
+		d.confLock.RLock()
+		defer d.confLock.RUnlock()
+
+		bsvc = d.Config.BlockedServices.Clone()
+	}()
+
+	aghhttp.WriteJSONResponseOK(w, r, bsvc)
+}
+
+// handleBlockedServicesUpdate is the handler for the PUT
+// /control/blocked_services/update HTTP API.
+func (d *DNSFilter) handleBlockedServicesUpdate(w http.ResponseWriter, r *http.Request) {
+	bsvc := &BlockedServices{}
+	err := json.NewDecoder(r.Body).Decode(bsvc)
+	if err != nil {
+		aghhttp.Error(r, w, http.StatusBadRequest, "json.Decode: %s", err)
+
+		return
+	}
+
+	err = bsvc.Validate()
+	if err != nil {
+		aghhttp.Error(r, w, http.StatusUnprocessableEntity, "validating: %s", err)
+
+		return
+	}
+
+	if bsvc.Schedule == nil {
+		bsvc.Schedule = schedule.EmptyWeekly()
+	}
+
+	func() {
+		d.confLock.Lock()
+		defer d.confLock.Unlock()
+
+		d.Config.BlockedServices = bsvc
+	}()
+
+	log.Debug("updated blocked services schedule: %d", len(bsvc.IDs))
+
+	d.Config.ConfigModified()
+}
diff --git a/internal/filtering/http.go b/internal/filtering/http.go
index 43191ca6..df3fe95e 100644
--- a/internal/filtering/http.go
+++ b/internal/filtering/http.go
@@ -301,7 +301,7 @@ func (d *DNSFilter) handleFilteringRefresh(w http.ResponseWriter, r *http.Reques
 		return
 	}
 
-	_ = aghhttp.WriteJSONResponse(w, r, resp)
+	aghhttp.WriteJSONResponseOK(w, r, resp)
 }
 
 type filterJSON struct {
@@ -354,7 +354,7 @@ func (d *DNSFilter) handleFilteringStatus(w http.ResponseWriter, r *http.Request
 	resp.UserRules = d.UserRules
 	d.filtersMu.RUnlock()
 
-	_ = aghhttp.WriteJSONResponse(w, r, resp)
+	aghhttp.WriteJSONResponseOK(w, r, resp)
 }
 
 // Set filtering configuration
@@ -456,7 +456,7 @@ func (d *DNSFilter) handleCheckHost(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	_ = aghhttp.WriteJSONResponse(w, r, resp)
+	aghhttp.WriteJSONResponseOK(w, r, resp)
 }
 
 // setProtectedBool sets the value of a boolean pointer under a lock.  l must
@@ -504,7 +504,7 @@ func (d *DNSFilter) handleSafeBrowsingStatus(w http.ResponseWriter, r *http.Requ
 		Enabled: protectedBool(&d.confLock, &d.Config.SafeBrowsingEnabled),
 	}
 
-	_ = aghhttp.WriteJSONResponse(w, r, resp)
+	aghhttp.WriteJSONResponseOK(w, r, resp)
 }
 
 // handleParentalEnable is the handler for the POST /control/parental/enable
@@ -530,7 +530,7 @@ func (d *DNSFilter) handleParentalStatus(w http.ResponseWriter, r *http.Request)
 		Enabled: protectedBool(&d.confLock, &d.Config.ParentalEnabled),
 	}
 
-	_ = aghhttp.WriteJSONResponse(w, r, resp)
+	aghhttp.WriteJSONResponseOK(w, r, resp)
 }
 
 // RegisterFilteringHandlers - register handlers
@@ -560,9 +560,14 @@ func (d *DNSFilter) RegisterFilteringHandlers() {
 
 	registerHTTP(http.MethodGet, "/control/blocked_services/services", d.handleBlockedServicesIDs)
 	registerHTTP(http.MethodGet, "/control/blocked_services/all", d.handleBlockedServicesAll)
+
+	// Deprecated handlers.
 	registerHTTP(http.MethodGet, "/control/blocked_services/list", d.handleBlockedServicesList)
 	registerHTTP(http.MethodPost, "/control/blocked_services/set", d.handleBlockedServicesSet)
 
+	registerHTTP(http.MethodGet, "/control/blocked_services/get", d.handleBlockedServicesGet)
+	registerHTTP(http.MethodPut, "/control/blocked_services/update", d.handleBlockedServicesUpdate)
+
 	registerHTTP(http.MethodGet, "/control/filtering/status", d.handleFilteringStatus)
 	registerHTTP(http.MethodPost, "/control/filtering/config", d.handleFilteringConfig)
 	registerHTTP(http.MethodPost, "/control/filtering/add_url", d.handleFilteringAddURL)
diff --git a/internal/filtering/rewritehttp.go b/internal/filtering/rewritehttp.go
index fea5a650..b3dc275a 100644
--- a/internal/filtering/rewritehttp.go
+++ b/internal/filtering/rewritehttp.go
@@ -28,7 +28,7 @@ func (d *DNSFilter) handleRewriteList(w http.ResponseWriter, r *http.Request) {
 	}
 	d.confLock.Unlock()
 
-	_ = aghhttp.WriteJSONResponse(w, r, arr)
+	aghhttp.WriteJSONResponseOK(w, r, arr)
 }
 
 func (d *DNSFilter) handleRewriteAdd(w http.ResponseWriter, r *http.Request) {
diff --git a/internal/filtering/safesearchhttp.go b/internal/filtering/safesearchhttp.go
index 6048cfea..d1358a2f 100644
--- a/internal/filtering/safesearchhttp.go
+++ b/internal/filtering/safesearchhttp.go
@@ -36,7 +36,7 @@ func (d *DNSFilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Reques
 		resp = d.Config.SafeSearchConf
 	}()
 
-	_ = aghhttp.WriteJSONResponse(w, r, resp)
+	aghhttp.WriteJSONResponseOK(w, r, resp)
 }
 
 // handleSafeSearchSettings is the handler for PUT /control/safesearch/settings
diff --git a/internal/home/clientshttp.go b/internal/home/clientshttp.go
index 38ea666d..240ef4de 100644
--- a/internal/home/clientshttp.go
+++ b/internal/home/clientshttp.go
@@ -34,8 +34,12 @@ type clientJSON struct {
 	WHOIS          *whois.Info                 `json:"whois_info,omitempty"`
 	SafeSearchConf *filtering.SafeSearchConfig `json:"safe_search"`
 
+	// Schedule is blocked services schedule for every day of the week.
+	Schedule *schedule.Weekly `json:"blocked_services_schedule"`
+
 	Name string `json:"name"`
 
+	// BlockedServices is the names of blocked services.
 	BlockedServices []string `json:"blocked_services"`
 	IDs             []string `json:"ids"`
 	Tags            []string `json:"tags"`
@@ -53,6 +57,34 @@ type clientJSON struct {
 	IgnoreStatistics aghalg.NullBool `json:"ignore_statistics"`
 }
 
+// copySettings returns a copy of specific settings from JSON or a previous
+// client.
+func (j *clientJSON) copySettings(
+	prev *Client,
+) (weekly *schedule.Weekly, ignoreQueryLog, ignoreStatistics bool) {
+	if j.Schedule != nil {
+		weekly = j.Schedule.Clone()
+	} else if prev != nil && prev.BlockedServices != nil {
+		weekly = prev.BlockedServices.Schedule.Clone()
+	} else {
+		weekly = schedule.EmptyWeekly()
+	}
+
+	if j.IgnoreQueryLog != aghalg.NBNull {
+		ignoreQueryLog = j.IgnoreQueryLog == aghalg.NBTrue
+	} else if prev != nil {
+		ignoreQueryLog = prev.IgnoreQueryLog
+	}
+
+	if j.IgnoreStatistics != aghalg.NBNull {
+		ignoreStatistics = j.IgnoreStatistics == aghalg.NBTrue
+	} else if prev != nil {
+		ignoreStatistics = prev.IgnoreStatistics
+	}
+
+	return weekly, ignoreQueryLog, ignoreStatistics
+}
+
 type runtimeClientJSON struct {
 	WHOIS *whois.Info `json:"whois_info"`
 
@@ -93,7 +125,7 @@ func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http
 
 	data.Tags = clientTags
 
-	_ = aghhttp.WriteJSONResponse(w, r, data)
+	aghhttp.WriteJSONResponseOK(w, r, data)
 }
 
 // jsonToClient converts JSON object to Client object.
@@ -119,9 +151,15 @@ func (clients *clientsContainer) jsonToClient(cj clientJSON, prev *Client) (c *C
 		}
 	}
 
-	weekly := schedule.EmptyWeekly()
-	if prev != nil {
-		weekly = prev.BlockedServices.Schedule.Clone()
+	weekly, ignoreQueryLog, ignoreStatistics := cj.copySettings(prev)
+
+	bs := &filtering.BlockedServices{
+		Schedule: weekly,
+		IDs:      cj.BlockedServices,
+	}
+	err = bs.Validate()
+	if err != nil {
+		return nil, fmt.Errorf("validating blocked services: %w", err)
 	}
 
 	c = &Client{
@@ -129,10 +167,7 @@ func (clients *clientsContainer) jsonToClient(cj clientJSON, prev *Client) (c *C
 
 		Name: cj.Name,
 
-		BlockedServices: &filtering.BlockedServices{
-			Schedule: weekly,
-			IDs:      cj.BlockedServices,
-		},
+		BlockedServices: bs,
 
 		IDs:       cj.IDs,
 		Tags:      cj.Tags,
@@ -143,18 +178,8 @@ func (clients *clientsContainer) jsonToClient(cj clientJSON, prev *Client) (c *C
 		ParentalEnabled:       cj.ParentalEnabled,
 		SafeBrowsingEnabled:   cj.SafeBrowsingEnabled,
 		UseOwnBlockedServices: !cj.UseGlobalBlockedServices,
-	}
-
-	if cj.IgnoreQueryLog != aghalg.NBNull {
-		c.IgnoreQueryLog = cj.IgnoreQueryLog == aghalg.NBTrue
-	} else if prev != nil {
-		c.IgnoreQueryLog = prev.IgnoreQueryLog
-	}
-
-	if cj.IgnoreStatistics != aghalg.NBNull {
-		c.IgnoreStatistics = cj.IgnoreStatistics == aghalg.NBTrue
-	} else if prev != nil {
-		c.IgnoreStatistics = prev.IgnoreStatistics
+		IgnoreQueryLog:        ignoreQueryLog,
+		IgnoreStatistics:      ignoreStatistics,
 	}
 
 	if safeSearchConf.Enabled {
@@ -191,6 +216,7 @@ func clientToJSON(c *Client) (cj *clientJSON) {
 
 		UseGlobalBlockedServices: !c.UseOwnBlockedServices,
 
+		Schedule:        c.BlockedServices.Schedule,
 		BlockedServices: c.BlockedServices.IDs,
 
 		Upstreams: c.Upstreams,
@@ -338,7 +364,7 @@ func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http
 		})
 	}
 
-	_ = aghhttp.WriteJSONResponse(w, r, data)
+	aghhttp.WriteJSONResponseOK(w, r, data)
 }
 
 // findRuntime looks up the IP in runtime and temporary storages, like
diff --git a/internal/home/control.go b/internal/home/control.go
index a2414b35..f7286aed 100644
--- a/internal/home/control.go
+++ b/internal/home/control.go
@@ -170,7 +170,7 @@ func handleStatus(w http.ResponseWriter, r *http.Request) {
 		resp.IsDHCPAvailable = Context.dhcpServer != nil
 	}
 
-	_ = aghhttp.WriteJSONResponse(w, r, resp)
+	aghhttp.WriteJSONResponseOK(w, r, resp)
 }
 
 // ------------------------
diff --git a/internal/home/controlinstall.go b/internal/home/controlinstall.go
index a5be3354..d6985eab 100644
--- a/internal/home/controlinstall.go
+++ b/internal/home/controlinstall.go
@@ -59,7 +59,7 @@ func (web *webAPI) handleInstallGetAddresses(w http.ResponseWriter, r *http.Requ
 		data.Interfaces[iface.Name] = iface
 	}
 
-	_ = aghhttp.WriteJSONResponse(w, r, data)
+	aghhttp.WriteJSONResponseOK(w, r, data)
 }
 
 type checkConfReqEnt struct {
@@ -190,7 +190,7 @@ func (web *webAPI) handleInstallCheckConfig(w http.ResponseWriter, r *http.Reque
 		resp.StaticIP = handleStaticIP(req.DNS.IP, req.SetStaticIP)
 	}
 
-	_ = aghhttp.WriteJSONResponse(w, r, resp)
+	aghhttp.WriteJSONResponseOK(w, r, resp)
 }
 
 // handleStaticIP - handles static IP request
diff --git a/internal/home/controlupdate.go b/internal/home/controlupdate.go
index 5238134c..50a1a6f3 100644
--- a/internal/home/controlupdate.go
+++ b/internal/home/controlupdate.go
@@ -33,7 +33,7 @@ func (web *webAPI) handleVersionJSON(w http.ResponseWriter, r *http.Request) {
 	resp := &versionResponse{}
 	if web.conf.disableUpdate {
 		resp.Disabled = true
-		_ = aghhttp.WriteJSONResponse(w, r, resp)
+		aghhttp.WriteJSONResponseOK(w, r, resp)
 
 		return
 	}
@@ -68,7 +68,7 @@ func (web *webAPI) handleVersionJSON(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	_ = aghhttp.WriteJSONResponse(w, r, resp)
+	aghhttp.WriteJSONResponseOK(w, r, resp)
 }
 
 // requestVersionInfo sets the VersionInfo field of resp if it can reach the
diff --git a/internal/home/i18n.go b/internal/home/i18n.go
index d9e3435c..267205d3 100644
--- a/internal/home/i18n.go
+++ b/internal/home/i18n.go
@@ -58,7 +58,7 @@ type languageJSON struct {
 func handleI18nCurrentLanguage(w http.ResponseWriter, r *http.Request) {
 	log.Printf("home: language is %s", config.Language)
 
-	_ = aghhttp.WriteJSONResponse(w, r, &languageJSON{
+	aghhttp.WriteJSONResponseOK(w, r, &languageJSON{
 		Language: config.Language,
 	})
 }
diff --git a/internal/home/profilehttp.go b/internal/home/profilehttp.go
index 12e036c3..91e36f28 100644
--- a/internal/home/profilehttp.go
+++ b/internal/home/profilehttp.go
@@ -61,7 +61,7 @@ func handleGetProfile(w http.ResponseWriter, r *http.Request) {
 		}
 	}()
 
-	_ = aghhttp.WriteJSONResponse(w, r, resp)
+	aghhttp.WriteJSONResponseOK(w, r, resp)
 }
 
 // handlePutProfile is the handler for PUT /control/profile/update endpoint.
diff --git a/internal/home/tls.go b/internal/home/tls.go
index c42d5175..004e9412 100644
--- a/internal/home/tls.go
+++ b/internal/home/tls.go
@@ -770,7 +770,7 @@ func marshalTLS(w http.ResponseWriter, r *http.Request, data tlsConfig) {
 		data.PrivateKey = ""
 	}
 
-	_ = aghhttp.WriteJSONResponse(w, r, data)
+	aghhttp.WriteJSONResponseOK(w, r, data)
 }
 
 // registerWebHandlers registers HTTP handlers for TLS configuration.
diff --git a/internal/next/websvc/dns.go b/internal/next/websvc/dns.go
index 34cf9a15..39f05d22 100644
--- a/internal/next/websvc/dns.go
+++ b/internal/next/websvc/dns.go
@@ -7,6 +7,7 @@ import (
 	"net/netip"
 	"time"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
 )
 
@@ -17,13 +18,13 @@ import (
 type ReqPatchSettingsDNS struct {
 	// TODO(a.garipov): Add more as we go.
 
-	Addresses           []netip.AddrPort `json:"addresses"`
-	BootstrapServers    []string         `json:"bootstrap_servers"`
-	UpstreamServers     []string         `json:"upstream_servers"`
-	DNS64Prefixes       []netip.Prefix   `json:"dns64_prefixes"`
-	UpstreamTimeout     JSONDuration     `json:"upstream_timeout"`
-	BootstrapPreferIPv6 bool             `json:"bootstrap_prefer_ipv6"`
-	UseDNS64            bool             `json:"use_dns64"`
+	Addresses           []netip.AddrPort     `json:"addresses"`
+	BootstrapServers    []string             `json:"bootstrap_servers"`
+	UpstreamServers     []string             `json:"upstream_servers"`
+	DNS64Prefixes       []netip.Prefix       `json:"dns64_prefixes"`
+	UpstreamTimeout     aghhttp.JSONDuration `json:"upstream_timeout"`
+	BootstrapPreferIPv6 bool                 `json:"bootstrap_prefer_ipv6"`
+	UseDNS64            bool                 `json:"use_dns64"`
 }
 
 // HTTPAPIDNSSettings are the DNS settings as used by the HTTP API.  See the
@@ -31,13 +32,13 @@ type ReqPatchSettingsDNS struct {
 type HTTPAPIDNSSettings struct {
 	// TODO(a.garipov): Add more as we go.
 
-	Addresses           []netip.AddrPort `json:"addresses"`
-	BootstrapServers    []string         `json:"bootstrap_servers"`
-	UpstreamServers     []string         `json:"upstream_servers"`
-	DNS64Prefixes       []netip.Prefix   `json:"dns64_prefixes"`
-	UpstreamTimeout     JSONDuration     `json:"upstream_timeout"`
-	BootstrapPreferIPv6 bool             `json:"bootstrap_prefer_ipv6"`
-	UseDNS64            bool             `json:"use_dns64"`
+	Addresses           []netip.AddrPort     `json:"addresses"`
+	BootstrapServers    []string             `json:"bootstrap_servers"`
+	UpstreamServers     []string             `json:"upstream_servers"`
+	DNS64Prefixes       []netip.Prefix       `json:"dns64_prefixes"`
+	UpstreamTimeout     aghhttp.JSONDuration `json:"upstream_timeout"`
+	BootstrapPreferIPv6 bool                 `json:"bootstrap_prefer_ipv6"`
+	UseDNS64            bool                 `json:"use_dns64"`
 }
 
 // handlePatchSettingsDNS is the handler for the PATCH /api/v1/settings/dns HTTP
@@ -53,7 +54,7 @@ func (svc *Service) handlePatchSettingsDNS(w http.ResponseWriter, r *http.Reques
 
 	err := json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		writeJSONErrorResponse(w, r, fmt.Errorf("decoding: %w", err))
+		aghhttp.WriteJSONResponseError(w, r, fmt.Errorf("decoding: %w", err))
 
 		return
 	}
@@ -71,7 +72,7 @@ func (svc *Service) handlePatchSettingsDNS(w http.ResponseWriter, r *http.Reques
 	ctx := r.Context()
 	err = svc.confMgr.UpdateDNS(ctx, newConf)
 	if err != nil {
-		writeJSONErrorResponse(w, r, fmt.Errorf("updating: %w", err))
+		aghhttp.WriteJSONResponseError(w, r, fmt.Errorf("updating: %w", err))
 
 		return
 	}
@@ -79,17 +80,17 @@ func (svc *Service) handlePatchSettingsDNS(w http.ResponseWriter, r *http.Reques
 	newSvc := svc.confMgr.DNS()
 	err = newSvc.Start()
 	if err != nil {
-		writeJSONErrorResponse(w, r, fmt.Errorf("starting new service: %w", err))
+		aghhttp.WriteJSONResponseError(w, r, fmt.Errorf("starting new service: %w", err))
 
 		return
 	}
 
-	writeJSONOKResponse(w, r, &HTTPAPIDNSSettings{
+	aghhttp.WriteJSONResponseOK(w, r, &HTTPAPIDNSSettings{
 		Addresses:           newConf.Addresses,
 		BootstrapServers:    newConf.BootstrapServers,
 		UpstreamServers:     newConf.UpstreamServers,
 		DNS64Prefixes:       newConf.DNS64Prefixes,
-		UpstreamTimeout:     JSONDuration(newConf.UpstreamTimeout),
+		UpstreamTimeout:     aghhttp.JSONDuration(newConf.UpstreamTimeout),
 		BootstrapPreferIPv6: newConf.BootstrapPreferIPv6,
 		UseDNS64:            newConf.UseDNS64,
 	})
diff --git a/internal/next/websvc/dns_test.go b/internal/next/websvc/dns_test.go
index c39ba1ab..5f6e3d44 100644
--- a/internal/next/websvc/dns_test.go
+++ b/internal/next/websvc/dns_test.go
@@ -10,6 +10,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/AdGuardHome/internal/aghtest"
 	"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
 	"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
@@ -24,7 +25,7 @@ func TestService_HandlePatchSettingsDNS(t *testing.T) {
 		BootstrapServers:    []string{"1.0.0.1"},
 		UpstreamServers:     []string{"1.1.1.1"},
 		DNS64Prefixes:       []netip.Prefix{netip.MustParsePrefix("1234::/64")},
-		UpstreamTimeout:     websvc.JSONDuration(2 * time.Second),
+		UpstreamTimeout:     aghhttp.JSONDuration(2 * time.Second),
 		BootstrapPreferIPv6: true,
 		UseDNS64:            true,
 	}
diff --git a/internal/next/websvc/http.go b/internal/next/websvc/http.go
index 7e4785cd..db32372d 100644
--- a/internal/next/websvc/http.go
+++ b/internal/next/websvc/http.go
@@ -8,6 +8,7 @@ import (
 	"net/netip"
 	"time"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
 	"github.com/AdguardTeam/golibs/log"
 )
@@ -21,9 +22,9 @@ type ReqPatchSettingsHTTP struct {
 	//
 	// TODO(a.garipov): Add wait time.
 
-	Addresses       []netip.AddrPort `json:"addresses"`
-	SecureAddresses []netip.AddrPort `json:"secure_addresses"`
-	Timeout         JSONDuration     `json:"timeout"`
+	Addresses       []netip.AddrPort     `json:"addresses"`
+	SecureAddresses []netip.AddrPort     `json:"secure_addresses"`
+	Timeout         aghhttp.JSONDuration `json:"timeout"`
 }
 
 // HTTPAPIHTTPSettings are the HTTP settings as used by the HTTP API.  See the
@@ -31,10 +32,10 @@ type ReqPatchSettingsHTTP struct {
 type HTTPAPIHTTPSettings struct {
 	// TODO(a.garipov): Add more as we go.
 
-	Addresses       []netip.AddrPort `json:"addresses"`
-	SecureAddresses []netip.AddrPort `json:"secure_addresses"`
-	Timeout         JSONDuration     `json:"timeout"`
-	ForceHTTPS      bool             `json:"force_https"`
+	Addresses       []netip.AddrPort     `json:"addresses"`
+	SecureAddresses []netip.AddrPort     `json:"secure_addresses"`
+	Timeout         aghhttp.JSONDuration `json:"timeout"`
+	ForceHTTPS      bool                 `json:"force_https"`
 }
 
 // handlePatchSettingsHTTP is the handler for the PATCH /api/v1/settings/http
@@ -46,7 +47,7 @@ func (svc *Service) handlePatchSettingsHTTP(w http.ResponseWriter, r *http.Reque
 
 	err := json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		writeJSONErrorResponse(w, r, fmt.Errorf("decoding: %w", err))
+		aghhttp.WriteJSONResponseError(w, r, fmt.Errorf("decoding: %w", err))
 
 		return
 	}
@@ -65,10 +66,10 @@ func (svc *Service) handlePatchSettingsHTTP(w http.ResponseWriter, r *http.Reque
 		ForceHTTPS:      svc.forceHTTPS,
 	}
 
-	writeJSONOKResponse(w, r, &HTTPAPIHTTPSettings{
+	aghhttp.WriteJSONResponseOK(w, r, &HTTPAPIHTTPSettings{
 		Addresses:       newConf.Addresses,
 		SecureAddresses: newConf.SecureAddresses,
-		Timeout:         JSONDuration(newConf.Timeout),
+		Timeout:         aghhttp.JSONDuration(newConf.Timeout),
 		ForceHTTPS:      newConf.ForceHTTPS,
 	})
 
diff --git a/internal/next/websvc/http_test.go b/internal/next/websvc/http_test.go
index c04921f6..3168ec03 100644
--- a/internal/next/websvc/http_test.go
+++ b/internal/next/websvc/http_test.go
@@ -10,6 +10,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
 	"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
 	"github.com/stretchr/testify/assert"
@@ -20,7 +21,7 @@ func TestService_HandlePatchSettingsHTTP(t *testing.T) {
 	wantWeb := &websvc.HTTPAPIHTTPSettings{
 		Addresses:       []netip.AddrPort{netip.MustParseAddrPort("127.0.1.1:80")},
 		SecureAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.1.1:443")},
-		Timeout:         websvc.JSONDuration(10 * time.Second),
+		Timeout:         aghhttp.JSONDuration(10 * time.Second),
 		ForceHTTPS:      false,
 	}
 
diff --git a/internal/next/websvc/settings.go b/internal/next/websvc/settings.go
index ab308836..44364ca3 100644
--- a/internal/next/websvc/settings.go
+++ b/internal/next/websvc/settings.go
@@ -2,6 +2,8 @@ package websvc
 
 import (
 	"net/http"
+
+	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 )
 
 // All Settings Handlers
@@ -25,20 +27,20 @@ func (svc *Service) handleGetSettingsAll(w http.ResponseWriter, r *http.Request)
 	httpConf := webSvc.Config()
 
 	// TODO(a.garipov): Add all currently supported parameters.
-	writeJSONOKResponse(w, r, &RespGetV1SettingsAll{
+	aghhttp.WriteJSONResponseOK(w, r, &RespGetV1SettingsAll{
 		DNS: &HTTPAPIDNSSettings{
 			Addresses:           dnsConf.Addresses,
 			BootstrapServers:    dnsConf.BootstrapServers,
 			UpstreamServers:     dnsConf.UpstreamServers,
 			DNS64Prefixes:       dnsConf.DNS64Prefixes,
-			UpstreamTimeout:     JSONDuration(dnsConf.UpstreamTimeout),
+			UpstreamTimeout:     aghhttp.JSONDuration(dnsConf.UpstreamTimeout),
 			BootstrapPreferIPv6: dnsConf.BootstrapPreferIPv6,
 			UseDNS64:            dnsConf.UseDNS64,
 		},
 		HTTP: &HTTPAPIHTTPSettings{
 			Addresses:       httpConf.Addresses,
 			SecureAddresses: httpConf.SecureAddresses,
-			Timeout:         JSONDuration(httpConf.Timeout),
+			Timeout:         aghhttp.JSONDuration(httpConf.Timeout),
 			ForceHTTPS:      httpConf.ForceHTTPS,
 		},
 	})
diff --git a/internal/next/websvc/settings_test.go b/internal/next/websvc/settings_test.go
index cacf28a9..f07d8b3e 100644
--- a/internal/next/websvc/settings_test.go
+++ b/internal/next/websvc/settings_test.go
@@ -9,6 +9,7 @@ import (
 	"testing"
 	"time"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/AdGuardHome/internal/next/agh"
 	"github.com/AdguardTeam/AdGuardHome/internal/next/dnssvc"
 	"github.com/AdguardTeam/AdGuardHome/internal/next/websvc"
@@ -23,14 +24,14 @@ func TestService_HandleGetSettingsAll(t *testing.T) {
 		Addresses:           []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:53")},
 		BootstrapServers:    []string{"94.140.14.140", "94.140.14.141"},
 		UpstreamServers:     []string{"94.140.14.14", "1.1.1.1"},
-		UpstreamTimeout:     websvc.JSONDuration(1 * time.Second),
+		UpstreamTimeout:     aghhttp.JSONDuration(1 * time.Second),
 		BootstrapPreferIPv6: true,
 	}
 
 	wantWeb := &websvc.HTTPAPIHTTPSettings{
 		Addresses:       []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:80")},
 		SecureAddresses: []netip.AddrPort{netip.MustParseAddrPort("127.0.0.1:443")},
-		Timeout:         websvc.JSONDuration(5 * time.Second),
+		Timeout:         aghhttp.JSONDuration(5 * time.Second),
 		ForceHTTPS:      true,
 	}
 
diff --git a/internal/next/websvc/system.go b/internal/next/websvc/system.go
index fbf60fe4..d4ca34ad 100644
--- a/internal/next/websvc/system.go
+++ b/internal/next/websvc/system.go
@@ -4,6 +4,7 @@ import (
 	"net/http"
 	"runtime"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/AdGuardHome/internal/version"
 )
 
@@ -12,24 +13,24 @@ import (
 // RespGetV1SystemInfo describes the response of the GET /api/v1/system/info
 // HTTP API.
 type RespGetV1SystemInfo struct {
-	Arch       string   `json:"arch"`
-	Channel    string   `json:"channel"`
-	OS         string   `json:"os"`
-	NewVersion string   `json:"new_version,omitempty"`
-	Start      JSONTime `json:"start"`
-	Version    string   `json:"version"`
+	Arch       string           `json:"arch"`
+	Channel    string           `json:"channel"`
+	OS         string           `json:"os"`
+	NewVersion string           `json:"new_version,omitempty"`
+	Start      aghhttp.JSONTime `json:"start"`
+	Version    string           `json:"version"`
 }
 
 // handleGetV1SystemInfo is the handler for the GET /api/v1/system/info HTTP
 // API.
 func (svc *Service) handleGetV1SystemInfo(w http.ResponseWriter, r *http.Request) {
-	writeJSONOKResponse(w, r, &RespGetV1SystemInfo{
+	aghhttp.WriteJSONResponseOK(w, r, &RespGetV1SystemInfo{
 		Arch:    runtime.GOARCH,
 		Channel: version.Channel(),
 		OS:      runtime.GOOS,
 		// TODO(a.garipov): Fill this when we have an updater.
 		NewVersion: "",
-		Start:      JSONTime(svc.start),
+		Start:      aghhttp.JSONTime(svc.start),
 		Version:    version.Version(),
 	})
 }
diff --git a/internal/querylog/http.go b/internal/querylog/http.go
index 1a29e23d..7a5c24a9 100644
--- a/internal/querylog/http.go
+++ b/internal/querylog/http.go
@@ -93,7 +93,7 @@ func (l *queryLog) handleQueryLog(w http.ResponseWriter, r *http.Request) {
 
 	resp := entriesToJSON(entries, oldest, l.anonymizer.Load())
 
-	_ = aghhttp.WriteJSONResponse(w, r, resp)
+	aghhttp.WriteJSONResponseOK(w, r, resp)
 }
 
 // handleQueryLogClear is the handler for the POST /control/querylog/clear HTTP
@@ -118,7 +118,7 @@ func (l *queryLog) handleQueryLogInfo(w http.ResponseWriter, r *http.Request) {
 		ivl = timeutil.Day * 90
 	}
 
-	_ = aghhttp.WriteJSONResponse(w, r, configJSON{
+	aghhttp.WriteJSONResponseOK(w, r, configJSON{
 		Enabled:           aghalg.BoolToNullBool(l.conf.Enabled),
 		Interval:          ivl.Hours() / 24,
 		AnonymizeClientIP: aghalg.BoolToNullBool(l.conf.AnonymizeClientIP),
@@ -143,7 +143,7 @@ func (l *queryLog) handleGetQueryLogConfig(w http.ResponseWriter, r *http.Reques
 
 	slices.Sort(resp.Ignored)
 
-	_ = aghhttp.WriteJSONResponse(w, r, resp)
+	aghhttp.WriteJSONResponseOK(w, r, resp)
 }
 
 // AnonymizeIP masks ip to anonymize the client if the ip is a valid one.
diff --git a/internal/schedule/schedule.go b/internal/schedule/schedule.go
index 1bf96016..fe5aeb82 100644
--- a/internal/schedule/schedule.go
+++ b/internal/schedule/schedule.go
@@ -2,9 +2,11 @@
 package schedule
 
 import (
+	"encoding/json"
 	"fmt"
 	"time"
 
+	"github.com/AdguardTeam/AdGuardHome/internal/aghhttp"
 	"github.com/AdguardTeam/golibs/errors"
 	"github.com/AdguardTeam/golibs/timeutil"
 	"gopkg.in/yaml.v3"
@@ -50,6 +52,10 @@ func FullWeekly() (w *Weekly) {
 
 // Clone returns a deep copy of a weekly.
 func (w *Weekly) Clone() (c *Weekly) {
+	if w == nil {
+		return nil
+	}
+
 	// NOTE:  Do not use time.LoadLocation, because the results will be
 	// different on time zone database update.
 	return &Weekly{
@@ -75,12 +81,62 @@ func (w *Weekly) Contains(t time.Time) (ok bool) {
 	return dr.contains(offset)
 }
 
+// type check
+var _ json.Unmarshaler = (*Weekly)(nil)
+
+// UnmarshalJSON implements the [json.Unmarshaler] interface for *Weekly.
+func (w *Weekly) UnmarshalJSON(data []byte) (err error) {
+	conf := &weeklyConfigJSON{}
+	err = json.Unmarshal(data, conf)
+	if err != nil {
+		return err
+	}
+
+	weekly := Weekly{}
+
+	weekly.location, err = time.LoadLocation(conf.TimeZone)
+	if err != nil {
+		return err
+	}
+
+	days := []*dayConfigJSON{
+		time.Sunday:    conf.Sunday,
+		time.Monday:    conf.Monday,
+		time.Tuesday:   conf.Tuesday,
+		time.Wednesday: conf.Wednesday,
+		time.Thursday:  conf.Thursday,
+		time.Friday:    conf.Friday,
+		time.Saturday:  conf.Saturday,
+	}
+	for i, d := range days {
+		var r dayRange
+
+		if d != nil {
+			r = dayRange{
+				start: time.Duration(d.Start),
+				end:   time.Duration(d.End),
+			}
+		}
+
+		err = w.validate(r)
+		if err != nil {
+			return fmt.Errorf("weekday %s: %w", time.Weekday(i), err)
+		}
+
+		weekly.days[i] = r
+	}
+
+	*w = weekly
+
+	return nil
+}
+
 // type check
 var _ yaml.Unmarshaler = (*Weekly)(nil)
 
 // UnmarshalYAML implements the [yaml.Unmarshaler] interface for *Weekly.
 func (w *Weekly) UnmarshalYAML(value *yaml.Node) (err error) {
-	conf := &weeklyConfig{}
+	conf := &weeklyConfigYAML{}
 
 	err = value.Decode(conf)
 	if err != nil {
@@ -96,7 +152,7 @@ func (w *Weekly) UnmarshalYAML(value *yaml.Node) (err error) {
 		return err
 	}
 
-	days := []dayConfig{
+	days := []dayConfigYAML{
 		time.Sunday:    conf.Sunday,
 		time.Monday:    conf.Monday,
 		time.Tuesday:   conf.Tuesday,
@@ -124,24 +180,24 @@ func (w *Weekly) UnmarshalYAML(value *yaml.Node) (err error) {
 	return nil
 }
 
-// weeklyConfig is the YAML configuration structure of Weekly.
-type weeklyConfig struct {
+// weeklyConfigYAML is the YAML configuration structure of Weekly.
+type weeklyConfigYAML struct {
 	// TimeZone is the local time zone.
 	TimeZone string `yaml:"time_zone"`
 
 	// Days of the week.
 
-	Sunday    dayConfig `yaml:"sun,omitempty"`
-	Monday    dayConfig `yaml:"mon,omitempty"`
-	Tuesday   dayConfig `yaml:"tue,omitempty"`
-	Wednesday dayConfig `yaml:"wed,omitempty"`
-	Thursday  dayConfig `yaml:"thu,omitempty"`
-	Friday    dayConfig `yaml:"fri,omitempty"`
-	Saturday  dayConfig `yaml:"sat,omitempty"`
+	Sunday    dayConfigYAML `yaml:"sun,omitempty"`
+	Monday    dayConfigYAML `yaml:"mon,omitempty"`
+	Tuesday   dayConfigYAML `yaml:"tue,omitempty"`
+	Wednesday dayConfigYAML `yaml:"wed,omitempty"`
+	Thursday  dayConfigYAML `yaml:"thu,omitempty"`
+	Friday    dayConfigYAML `yaml:"fri,omitempty"`
+	Saturday  dayConfigYAML `yaml:"sat,omitempty"`
 }
 
-// dayConfig is the YAML configuration structure of dayRange.
-type dayConfig struct {
+// dayConfigYAML is the YAML configuration structure of dayRange.
+type dayConfigYAML struct {
 	Start timeutil.Duration `yaml:"start"`
 	End   timeutil.Duration `yaml:"end"`
 }
@@ -172,38 +228,57 @@ func (w *Weekly) validate(r dayRange) (err error) {
 	}
 }
 
+// type check
+var _ json.Marshaler = (*Weekly)(nil)
+
+// MarshalJSON implements the [json.Marshaler] interface for *Weekly.
+func (w *Weekly) MarshalJSON() (data []byte, err error) {
+	c := &weeklyConfigJSON{
+		TimeZone:  w.location.String(),
+		Sunday:    w.days[time.Sunday].toDayConfigJSON(),
+		Monday:    w.days[time.Monday].toDayConfigJSON(),
+		Tuesday:   w.days[time.Tuesday].toDayConfigJSON(),
+		Wednesday: w.days[time.Wednesday].toDayConfigJSON(),
+		Thursday:  w.days[time.Thursday].toDayConfigJSON(),
+		Friday:    w.days[time.Friday].toDayConfigJSON(),
+		Saturday:  w.days[time.Saturday].toDayConfigJSON(),
+	}
+
+	return json.Marshal(c)
+}
+
 // type check
 var _ yaml.Marshaler = (*Weekly)(nil)
 
 // MarshalYAML implements the [yaml.Marshaler] interface for *Weekly.
 func (w *Weekly) MarshalYAML() (v any, err error) {
-	return weeklyConfig{
+	return weeklyConfigYAML{
 		TimeZone: w.location.String(),
-		Sunday: dayConfig{
+		Sunday: dayConfigYAML{
 			Start: timeutil.Duration{Duration: w.days[time.Sunday].start},
 			End:   timeutil.Duration{Duration: w.days[time.Sunday].end},
 		},
-		Monday: dayConfig{
+		Monday: dayConfigYAML{
 			Start: timeutil.Duration{Duration: w.days[time.Monday].start},
 			End:   timeutil.Duration{Duration: w.days[time.Monday].end},
 		},
-		Tuesday: dayConfig{
+		Tuesday: dayConfigYAML{
 			Start: timeutil.Duration{Duration: w.days[time.Tuesday].start},
 			End:   timeutil.Duration{Duration: w.days[time.Tuesday].end},
 		},
-		Wednesday: dayConfig{
+		Wednesday: dayConfigYAML{
 			Start: timeutil.Duration{Duration: w.days[time.Wednesday].start},
 			End:   timeutil.Duration{Duration: w.days[time.Wednesday].end},
 		},
-		Thursday: dayConfig{
+		Thursday: dayConfigYAML{
 			Start: timeutil.Duration{Duration: w.days[time.Thursday].start},
 			End:   timeutil.Duration{Duration: w.days[time.Thursday].end},
 		},
-		Friday: dayConfig{
+		Friday: dayConfigYAML{
 			Start: timeutil.Duration{Duration: w.days[time.Friday].start},
 			End:   timeutil.Duration{Duration: w.days[time.Friday].end},
 		},
-		Saturday: dayConfig{
+		Saturday: dayConfigYAML{
 			Start: timeutil.Duration{Duration: w.days[time.Saturday].start},
 			End:   timeutil.Duration{Duration: w.days[time.Saturday].end},
 		},
@@ -248,3 +323,38 @@ func (r dayRange) validate() (err error) {
 func (r *dayRange) contains(offset time.Duration) (ok bool) {
 	return r.start <= offset && offset < r.end
 }
+
+// toDayConfigJSON returns nil if the day range is empty, otherwise returns
+// initialized JSON configuration of the day range.
+func (r dayRange) toDayConfigJSON() (j *dayConfigJSON) {
+	if (r == dayRange{}) {
+		return nil
+	}
+
+	return &dayConfigJSON{
+		Start: aghhttp.JSONDuration(r.start),
+		End:   aghhttp.JSONDuration(r.end),
+	}
+}
+
+// weeklyConfigJSON is the JSON configuration structure of Weekly.
+type weeklyConfigJSON struct {
+	// TimeZone is the local time zone.
+	TimeZone string `json:"time_zone"`
+
+	// Days of the week.
+
+	Sunday    *dayConfigJSON `json:"sun,omitempty"`
+	Monday    *dayConfigJSON `json:"mon,omitempty"`
+	Tuesday   *dayConfigJSON `json:"tue,omitempty"`
+	Wednesday *dayConfigJSON `json:"wed,omitempty"`
+	Thursday  *dayConfigJSON `json:"thu,omitempty"`
+	Friday    *dayConfigJSON `json:"fri,omitempty"`
+	Saturday  *dayConfigJSON `json:"sat,omitempty"`
+}
+
+// dayConfigJSON is the JSON configuration structure of dayRange.
+type dayConfigJSON struct {
+	Start aghhttp.JSONDuration `json:"start"`
+	End   aghhttp.JSONDuration `json:"end"`
+}
diff --git a/internal/schedule/schedule_internal_test.go b/internal/schedule/schedule_internal_test.go
index f500524e..0d9aa705 100644
--- a/internal/schedule/schedule_internal_test.go
+++ b/internal/schedule/schedule_internal_test.go
@@ -1,6 +1,7 @@
 package schedule
 
 import (
+	"encoding/json"
 	"testing"
 	"time"
 
@@ -122,7 +123,7 @@ func TestWeekly_Contains(t *testing.T) {
 	}
 }
 
-const brusselsSunday = `
+const brusselsSundayYAML = `
 sun:
     start: 12h
     end: 14h
@@ -179,7 +180,7 @@ yaml: "bad"
 	}, {
 		name:       "brussels_sunday",
 		wantErrMsg: "",
-		data:       []byte(brusselsSunday),
+		data:       []byte(brusselsSundayYAML),
 		want:       brusselsWeekly,
 	}, {
 		name:       "start_equal_end",
@@ -240,7 +241,7 @@ func TestWeekly_MarshalYAML(t *testing.T) {
 		want: &Weekly{},
 	}, {
 		name: "brussels_sunday",
-		data: []byte(brusselsSunday),
+		data: []byte(brusselsSundayYAML),
 		want: brusselsWeekly,
 	}}
 
@@ -369,3 +370,142 @@ func TestDayRange_Validate(t *testing.T) {
 		})
 	}
 }
+
+const brusselsSundayJSON = `{
+  "sun": {
+    "end": 50400000,
+    "start": 43200000
+  },
+  "time_zone": "Europe/Brussels"
+}`
+
+func TestWeekly_UnmarshalJSON(t *testing.T) {
+	const (
+		sameTime = `{
+  "sun": {
+    "end": 32400000,
+    "start": 32400000
+  }
+}`
+		negativeStart = `{
+  "sun": {
+    "end": 3600000,
+    "start": -3600000
+  }
+}`
+		badTZ = `{
+  "time_zone": "bad_timezone"
+}`
+		badJSON = `{
+  "bad": "json",
+}`
+	)
+
+	brusseltsTZ, err := time.LoadLocation("Europe/Brussels")
+	require.NoError(t, err)
+
+	brusselsWeekly := &Weekly{
+		days: [7]dayRange{{
+			start: time.Hour * 12,
+			end:   time.Hour * 14,
+		}},
+		location: brusseltsTZ,
+	}
+
+	testCases := []struct {
+		name       string
+		wantErrMsg string
+		data       []byte
+		want       *Weekly
+	}{{
+		name:       "empty",
+		wantErrMsg: "unexpected end of JSON input",
+		data:       []byte(""),
+		want:       &Weekly{},
+	}, {
+		name:       "null",
+		wantErrMsg: "",
+		data:       []byte("null"),
+		want:       &Weekly{location: time.UTC},
+	}, {
+		name:       "brussels_sunday",
+		wantErrMsg: "",
+		data:       []byte(brusselsSundayJSON),
+		want:       brusselsWeekly,
+	}, {
+		name:       "start_equal_end",
+		wantErrMsg: "weekday Sunday: bad day range: start 9h0m0s is greater or equal to end 9h0m0s",
+		data:       []byte(sameTime),
+		want:       &Weekly{},
+	}, {
+		name:       "start_negative",
+		wantErrMsg: "weekday Sunday: bad day range: start -1h0m0s is negative",
+		data:       []byte(negativeStart),
+		want:       &Weekly{},
+	}, {
+		name:       "bad_time_zone",
+		wantErrMsg: "unknown time zone bad_timezone",
+		data:       []byte(badTZ),
+		want:       &Weekly{},
+	}, {
+		name:       "bad_json",
+		wantErrMsg: "invalid character '}' looking for beginning of object key string",
+		data:       []byte(badJSON),
+		want:       &Weekly{},
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			w := &Weekly{}
+			err = json.Unmarshal(tc.data, w)
+			testutil.AssertErrorMsg(t, tc.wantErrMsg, err)
+
+			assert.Equal(t, tc.want, w)
+		})
+	}
+}
+
+func TestWeekly_MarshalJSON(t *testing.T) {
+	brusselsTZ, err := time.LoadLocation("Europe/Brussels")
+	require.NoError(t, err)
+
+	brusselsWeekly := &Weekly{
+		days: [7]dayRange{time.Sunday: {
+			start: time.Hour * 12,
+			end:   time.Hour * 14,
+		}},
+		location: brusselsTZ,
+	}
+
+	testCases := []struct {
+		name string
+		data []byte
+		want *Weekly
+	}{{
+		name: "empty",
+		data: []byte(""),
+		want: &Weekly{},
+	}, {
+		name: "null",
+		data: []byte("null"),
+		want: &Weekly{},
+	}, {
+		name: "brussels_sunday",
+		data: []byte(brusselsSundayJSON),
+		want: brusselsWeekly,
+	}}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			var data []byte
+			data, err = json.Marshal(brusselsWeekly)
+			require.NoError(t, err)
+
+			w := &Weekly{}
+			err = json.Unmarshal(data, w)
+			require.NoError(t, err)
+
+			assert.Equal(t, brusselsWeekly, w)
+		})
+	}
+}
diff --git a/internal/stats/http.go b/internal/stats/http.go
index 764579b1..3eee5bbb 100644
--- a/internal/stats/http.go
+++ b/internal/stats/http.go
@@ -73,7 +73,7 @@ func (s *StatsCtx) handleStats(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	_ = aghhttp.WriteJSONResponse(w, r, resp)
+	aghhttp.WriteJSONResponseOK(w, r, resp)
 }
 
 // configResp is the response to the GET /control/stats_info.
@@ -122,7 +122,7 @@ func (s *StatsCtx) handleStatsInfo(w http.ResponseWriter, r *http.Request) {
 		resp.IntervalDays = 0
 	}
 
-	_ = aghhttp.WriteJSONResponse(w, r, resp)
+	aghhttp.WriteJSONResponseOK(w, r, resp)
 }
 
 // handleGetStatsConfig is the handler for the GET /control/stats/config HTTP
@@ -142,7 +142,7 @@ func (s *StatsCtx) handleGetStatsConfig(w http.ResponseWriter, r *http.Request)
 
 	slices.Sort(resp.Ignored)
 
-	_ = aghhttp.WriteJSONResponse(w, r, resp)
+	aghhttp.WriteJSONResponseOK(w, r, resp)
 }
 
 // handleStatsConfig is the handler for the POST /control/stats_config HTTP API.
diff --git a/openapi/CHANGELOG.md b/openapi/CHANGELOG.md
index fe35e4c9..667dff8c 100644
--- a/openapi/CHANGELOG.md
+++ b/openapi/CHANGELOG.md
@@ -4,6 +4,86 @@
 
 ## v0.108.0: API changes
 
+## v0.107.37: API changes
+
+### Deprecated blocked services APIs
+
+* The `GET /control/blocked_services/list` HTTP API; use the new `GET
+  /control/blocked_services/get` API instead.
+
+* The `POST /control/blocked_services/set` HTTP API; use the new `PUT
+  /control/blocked_services/update` API instead.
+
+### New blocked services APIs
+
+* The new `GET /control/blocked_services/get` HTTP API.
+
+* The new `PUT /control/blocked_services/update` HTTP API allows config
+  updates.
+
+These APIs accept and return a JSON object with the following format:
+
+```json
+{
+  "schedule": {
+    "time_zone": "Local",
+    "sun": {
+      "start": 46800000,
+      "end": 82800000
+    }
+  },
+  "ids": [
+    "vk"
+  ]
+}
+```
+
+### `/control/clients` HTTP APIs
+
+The following HTTP APIs have been changed:
+
+*  `GET /control/clients`;
+*  `GET /control/clients/find?ip0=...&ip1=...&ip2=...`;
+*  `POST /control/clients/add`;
+*  `POST /control/clients/update`;
+
+The new field `blocked_services_schedule` has been added to JSON objects.  It
+has the following format:
+
+```json
+{
+  "time_zone": "Local",
+  "sun": {
+    "start": 0,
+    "end": 86400000
+  },
+  "mon": {
+    "start": 60000,
+    "end": 82800000
+  },
+  "thu": {
+    "start": 120000,
+    "end": 79200000
+  },
+  "tue": {
+    "start": 180000,
+    "end": 75600000
+  },
+  "wed": {
+    "start": 240000,
+    "end": 72000000
+  },
+  "fri": {
+    "start": 300000,
+    "end": 68400000
+  },
+  "sat": {
+    "start": 360000,
+    "end": 64800000
+  }
+}
+```
+
 ## v0.107.36: API changes
 
 ### The new fields `"top_upstreams_responses"` and `"top_upstreams_avg_time"` in `Stats` object
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index 64f376e4..89a1a1c1 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -1001,6 +1001,9 @@
                 '$ref': '#/components/schemas/BlockedServicesAll'
   '/blocked_services/list':
     'get':
+      'deprecated': true
+      'description': >
+        Deprecated: Use `GET /blocked_services/get` instead.
       'tags':
       - 'blocked_services'
       'operationId': 'blockedServicesList'
@@ -1014,6 +1017,9 @@
                 '$ref': '#/components/schemas/BlockedServicesArray'
   '/blocked_services/set':
     'post':
+      'deprecated': true
+      'description': >
+        Deprecated: Use `PUT /blocked_services/update` instead.
       'tags':
       - 'blocked_services'
       'operationId': 'blockedServicesSet'
@@ -1026,6 +1032,34 @@
       'responses':
         '200':
           'description': 'OK.'
+  '/blocked_services/get':
+    'get':
+      'tags':
+      - 'blocked_services'
+      'operationId': 'blockedServicesSchedule'
+      'summary': 'Get blocked services'
+      'responses':
+        '200':
+          'description': 'OK.'
+          'content':
+            'application/json':
+              'schema':
+                '$ref': '#/components/schemas/BlockedServicesSchedule'
+  '/blocked_services/update':
+    'put':
+      'tags':
+      - 'blocked_services'
+      'operationId': 'blockedServicesScheduleUpdate'
+      'summary': 'Update blocked services'
+      'requestBody':
+        'content':
+          'application/json':
+            'schema':
+              '$ref': '#/components/schemas/BlockedServicesSchedule'
+        'required': true
+      'responses':
+        '200':
+          'description': 'OK.'
   '/rewrite/list':
     'get':
       'tags':
@@ -2485,6 +2519,54 @@
           'type': 'boolean'
         'youtube':
           'type': 'boolean'
+    'Schedule':
+      'type': 'object'
+      'description': >
+        Sets periods of inactivity for filtering blocked services.  The
+        schedule contains 7 days (Sunday to Saturday) and a time zone.
+      'properties':
+        'time_zone':
+          'description': >
+            Time zone name according to IANA time zone database.  For example
+            `Europe/Brussels`.  `Local` represents the system's local time
+            zone.
+          'type': 'string'
+        'sun':
+          '$ref': '#/components/schemas/DayRange'
+        'mon':
+          '$ref': '#/components/schemas/DayRange'
+        'tue':
+          '$ref': '#/components/schemas/DayRange'
+        'wed':
+          '$ref': '#/components/schemas/DayRange'
+        'thu':
+          '$ref': '#/components/schemas/DayRange'
+        'fri':
+          '$ref': '#/components/schemas/DayRange'
+        'sat':
+          '$ref': '#/components/schemas/DayRange'
+    'DayRange':
+      'type': 'object'
+      'description': >
+        The single interval within a day.  It begins at the `start` and ends
+        before the `end`.
+      'properties':
+        'start':
+          'type': 'number'
+          'description': >
+            The number of milliseconds elapsed from the start of a day.  It
+            must be less than `end` and is expected to be rounded to minutes.
+            So the maximum value is `86340000` (23 hours and 59 minutes).
+          'minimum': 0
+          'maximum': 86340000
+        'end':
+          'type': 'number'
+          'description': >
+            The number of milliseconds elapsed from the start of a day.  It is
+            expected to be rounded to minutes.  The maximum value is `86400000`
+            (24 hours).
+          'minimum': 0
+          'maximum': 86400000
     'Client':
       'type': 'object'
       'description': 'Client information.'
@@ -2513,6 +2595,8 @@
           '$ref': '#/components/schemas/SafeSearchConfig'
         'use_global_blocked_services':
           'type': 'boolean'
+        'blocked_services_schedule':
+          '$ref': '#/components/schemas/Schedule'
         'blocked_services':
           'type': 'array'
           'items':
@@ -2793,6 +2877,17 @@
       - 'name'
       - 'rules'
       'type': 'object'
+    'BlockedServicesSchedule':
+      'type': 'object'
+      'properties':
+        'schedule':
+          '$ref': '#/components/schemas/Schedule'
+        'ids':
+          'description': >
+            The names of the blocked services.
+          'type': 'array'
+          'items':
+            'type': 'string'
     'CheckConfigRequest':
       'type': 'object'
       'description': 'Configuration to be checked'