К содержимому

Документация

Проблемы качества

GET /api/v1/quality/findings

Возвращает автоматически созданные замечания к завершённым сборам доступных сайтов.

Доступ

Bearer API-ключ активного пространства и право runs.read. Для записи нужны ключ с записью и sites.write на выбранном сайте. Общие определения изменяет только оператор платформы с settings.manage, не внешний ключ. Сайты и история ограничены текущей командой. На агенте POST/PATCH/DELETE запрещены: определения меняются в облаке.

Параметры

Имя Где Обязательно Описание
site_id query нет Идентификатор доступного сайта в пространстве API-ключа.
limit query нет Максимальное число элементов в одном ответе; верхний предел зависит от метода.
run_id query нет Идентификатор прогона, связанного с записью или событием.
severity query нет Важность находки качества: info, warning или critical.
since query нет Изменения не раньше указанного момента ISO 8601 с часовым поясом.
locale query нет Язык справочника, предложений и пояснений. Если не указан, учитывается cookie akmelo_locale; иначе ru. Имена полей пользователя не переводятся.

Тело не требуется. locale=ru|en выбирает язык подписей и пояснений; без явного параметра используется cookie akmelo_locale, затем русский. Код вида и имена полей не переводятся.

Ответ 200

Пример сокращён. Числовые ID условны; полные поля описаны ниже и в скачиваемой схеме OpenAPI.

{
  "data": [],
  "severities": []
}
Поле находки Значение
id, run_id, site Идентификаторы замечания, сбора и доступного сайта
kind, severity, message Вид, важность и локализованное описание
rule Живая ссылка на правило или null, если оно удалено
details Исторические kind, params, rule_name, action, field, field_name, измерения и покрытие
explanation, next_step Что означает проверка и что делать дальше
created_at Когда находка создана

details.checked является числом реально проверенных значений, affected числом нарушений, percent показанным процентом, threshold исходным порогом. Для объёма также доступны before, after, previous_run_id. Не все поля измерений присутствуют у каждого вида. details.origin_rule_id у исторической находки является справочным ID удалённого определения, а не обещанием живой ссылки; rule_deleted=true отмечает удаление. Проверенные записи не включаются в этот ответ.

Поведение и ограничения

Находки нельзя создать этим методом. details хранит исторические условия и измерения. Смотрите sample_limited, checked, affected, empty_skipped и oversized_skipped. Если правило удалено, rule=null, находка остаётся. Отсутствие находок не показывает, были ли проверки выполнены; состояние последней проверки смотрите в rules.last_check.

Правила действуют на следующие успешные полные сборы. Они не меняют старые данные и не исправляют сценарий. Проверки полей используют до 5 000 записей текущего сбора, с явной отметкой ограниченной выборки; пустые значения кроме проверки заполненности пропускаются. passed, breached, skipped различаются, а отсутствие находки не доказывает успех.

Числовые примеры сохраняют прежний контракт v1. Для публичных UUID используйте X-Identifier-Format: uuid либо тот же метод /api/v2. Это относится и к вложенным ссылкам на сайты, правила и сборы; preset_id, category_id и field остаются строковыми кодами. Форматы идентификаторов.

Если budget_truncated=true, результат пропущен с частичными измерениями. Превышение общего бюджета не считается успешно выполненной проверкой.

Покрытие и пропущенные проверки

До json_decode проверяются сложность и глубина JSON: общий предел 200000узлов и 16 МиБ оценённой распакованной структуры, не больше 4 МиБ на строку и 24уровней. Строковые скобки не считаются узлами. Дополнительно перед PDO-порцией и распаковкой проверяется свободная память PHP с резервом 8 МиБ. Новые причины пропуска: sample_structure_exceeded, sample_depth_exceeded, sample_decode_error, sample_memory_exceeded. Даже если первая строка слишком сложная, это пропуск по пределу, а не no_records. Все измерения до остановки частичные.

Проверяются только успешные полные обычные сборы. Пробные, частичные, неуспешные и отменённые сборы не проверяются. Продолжение или повтор прерванного сбора имеет skipped / continued_run. Сравнение объёма требует более раннего успешного полного сбора того же сайта и сценария, не помеченного для проверки; без него будет no_baseline, при слишком малом прошлом объёме baseline_too_small.

Счётчики объёма относятся ко всему сбору. Поля проверяются по первым 5000 действующим записям текущего сбора, по внутреннему ID. Это ограниченная выборка, не случайная оценка всего каталога. Общий предел: 32 различных поля, 100000 ячеек и 8 МиБ сырых данных; отдельное значение свыше 8 КиБ не читается в память. sample_limited, sample_size, sample_limit, budget_truncated, budget_bytes, byte_limit, cell_limit, field_limit, oversized_skipped раскрывают покрытие. При достижении общего бюджета результат skipped, причина sample_budget_exceeded, частичное измерение в observed_status не является итоговым успехом или находкой. Поля сверх лимита получают field_budget_exceeded.

Пустыми считаются null, пустая строка, строка только из пробельных символов Unicode и пустой массив. 0 и false заполнены. Только field_fill_rate считает пустоту нарушением. Другие виды исключают её из знаменателя и при отсутствии заполненных значений возвращают no_filled_values. Иные причины пропуска: no_records, field_missing, values_too_large, too_few_records, unsupported_kind, check_error. Ошибка проверки не превращает уже сохранённый успешный сбор в неуспешный.

Доли нарушают правило строго выше допуска: ровно 7 из 100 при допуске 7% проходят. Падение и рост объёма включают порог: 100 → 90 при пороге 10% вызывает находку. Сравнения точные, округляется только показанный процент. Свежесть измеряется относительно run.finished_at; дата без часового пояса считается UTC.

Подробные примеры всех 24 шаблонов и работа с интерфейсом: Качество данных. Схема параметров: справочник.

Как отправить запрос

В серверном окружении задайте BASE (адрес установки без завершающего слеша) и API_KEY. Секрет не вставляется в исходник. Подготовка клиентов и браузерный JavaScript.

curl

curl --fail-with-body -X GET "$BASE/api/v1/quality/findings?site_id=1&limit=100&locale=ru?site_id=1&limit=100" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Accept: application/json"

Node.js · сервер

// Node.js, сервер. Сохраните как request.mjs.
const response = await fetch(process.env.BASE + "/api/v1/quality/findings?site_id=1&limit=100&locale=ru?site_id=1&limit=100", {
  method: "GET",
  headers: {
    Authorization: `Bearer ${process.env.API_KEY}`,
    Accept: "application/json"
  },
  signal: AbortSignal.timeout(120000)
});
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);
console.log(await response.json());

Python

import json
import os
import urllib.request
import urllib.error

request = urllib.request.Request(
    os.environ["BASE"] + "/api/v1/quality/findings?site_id=1&limit=100&locale=ru?site_id=1&limit=100",
    method="GET",
    headers={"Authorization": "Bearer " + os.environ["API_KEY"],
             "Accept": "application/json"},
)
try:
    with urllib.request.urlopen(request, timeout=120) as response:
        print(json.load(response))
except urllib.error.HTTPError as error:
    print(error.code, error.read().decode("utf-8", errors="replace"))
    raise

PHP · внешний клиент

<?php
$handle = curl_init(getenv("BASE") . "/api/v1/quality/findings?site_id=1&limit=100&locale=ru?site_id=1&limit=100");
curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => "GET",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 120,
    CURLOPT_HTTPHEADER => [
        "Authorization: Bearer " . getenv("API_KEY"),
        "Accept: application/json",
    ],
]);
$content = curl_exec($handle);
$status = curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
if ($content === false) {
    throw new RuntimeException(curl_error($handle));
}
curl_close($handle);
if ($status < 200 || $status >= 300) {
    throw new RuntimeException("HTTP $status: $content");
}
print_r(json_decode($content, true, 512, JSON_THROW_ON_ERROR));

Go

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "time"
)

func main() {
    request, err := http.NewRequest("GET", os.Getenv("BASE") + "/api/v1/quality/findings?site_id=1&limit=100&locale=ru?site_id=1&limit=100", nil)
    if err != nil { panic(err) }
    request.Header.Set("Authorization", "Bearer " + os.Getenv("API_KEY"))
    request.Header.Set("Accept", "application/json")
    client := &http.Client{Timeout: 120 * time.Second}
    response, err := client.Do(request)
    if err != nil { panic(err) }
    defer response.Body.Close()
    content, err := io.ReadAll(response.Body)
    if err != nil { panic(err) }
    if response.StatusCode < 200 || response.StatusCode >= 300 {
        panic(fmt.Sprintf("HTTP %d: %s", response.StatusCode, content))
    }
    fmt.Println(string(content))
}

Если запрос не выполнен

401: проверьте ключ и срок. 403: права, режим чтения/записи и 2FA. 404: идентификатор и пространство. 422: поля запроса или состояние объекта. 429: подождите Retry-After. Подробно об ошибках и повторах.