Skip to content

Documentation

Quality rules

GET /api/v1/quality/rules

Read rules for accessible sites and platform-wide rules.

Access

Use a Bearer API key for the active workspace and runs.read permission. Writes also require a writable key and sites.write on the selected site. Only a platform operator with settings.manage, not an external key, may change platform-wide definitions. Sites and history are scoped to the current team. An agent rejects POST/PATCH/DELETE; edit definitions in the cloud.

Parameters

Name In Required Description
locale query no Language of vocabulary, suggestions, and explanations. If omitted, akmelo_locale cookie is used, otherwise ru. User-defined field names are not translated.

No body is required. locale=ru|en selects labels and explanations; without an explicit parameter the akmelo_locale cookie is used, then Russian. Kind codes and field names are not translated.

Response 200

This is a shortened example with placeholder numeric IDs; full fields are documented below and in the downloadable OpenAPI schema.

{
  "data": [
    {
      "id": 1,
      "name": "Product count dropped",
      "kind": "row_count_drop",
      "params": {
        "drop_percent": 30,
        "min_previous": 100
      },
      "action": "warn",
      "is_active": true,
      "site": {
        "id": 1,
        "code": "parts"
      },
      "preset_id": "catalog-drop",
      "last_check": null,
      "created_at": "2026-10-21T12:00:00+00:00",
      "updated_at": "2026-10-21T12:00:00+00:00"
    }
  ],
  "kinds": [],
  "actions": []
}
Rule field Meaning
id, site Rule identifier and site reference; site=null for a platform-wide rule
name, kind, params Name, stable kind, and saved conditions
action, is_active Action on a breach and whether future checks are enabled
preset_id Original template code or null; multiple rules may use the same template
created_at, updated_at Definition timestamps
last_check null or the latest accessible result: status, reason, reason_label, checked_at, run_id, and details

last_check.status is passed when conditions were met, breached for a violation, or skipped when no conclusion was possible. last_check=null is not a pass. Historical details.params can differ from current params after editing. Even a platform-wide rule only returns a latest check from sites the caller can access. A different team's result must not be presented as a shared rule's result.

Behavior and limits

Platform-wide rules are visible but cannot be changed with an external key. last_check is scoped to accessible sites, even for a platform-wide rule. null means there is no recorded outcome. This method has no site_id filter.

Rules apply to future successful complete collections. They do not alter old data or fix scenario code. Field checks use up to 5,000 records from the current collection and explicitly report a limited sample; except for completeness checks, empty values are excluded. passed, breached, and skipped are distinct, and no finding does not prove success.

Numeric examples retain the legacy v1 contract. Use X-Identifier-Format: uuid or the same method under /api/v2 for public UUIDs, including nested site, rule, and collection references. preset_id, category_id, and field remain string codes. Identifier formats.

With budget_truncated=true, the check is skipped and measurements are partial. Reaching a shared budget is not reported as a successful check.

Coverage and skipped checks

Before json_decode, JSON complexity and depth are checked: 200000 nodes and 16 MiB estimated decoded structure overall, 4 MiB per row, and 24 levels. Brackets inside strings are not nodes. Remaining PHP memory is checked before each PDO batch and decode, with an 8 MiB reserve. Additional skip reasons are sample_structure_exceeded, sample_depth_exceeded, sample_decode_error, and sample_memory_exceeded. A first row that is too complex is a budget skip, not no_records. Measurements before stopping are partial.

Only successful complete normal collections are checked. Trials, partial, failed, and cancelled collections are excluded. Continuations or retries after an interruption receive skipped / continued_run. Volume comparisons require an earlier successful complete collection from the same site and scenario that is not flagged for review; otherwise the reason is no_baseline, or baseline_too_small when its volume is too low.

Volume counters cover the full collection. Field checks use the first 5000 active records from this collection, ordered by internal ID. This is a bounded sample, not a random estimate of the whole catalog. Shared limits are 32 distinct fields, 100000 cells, and 8 MiB of raw data; an individual value over 8 KiB is not loaded into memory. sample_limited, sample_size, sample_limit, budget_truncated, budget_bytes, byte_limit, cell_limit, field_limit, and oversized_skipped describe coverage. Reaching a shared budget produces skipped / sample_budget_exceeded; partial observed_status is not a final pass or finding. Fields beyond the field limit receive field_budget_exceeded.

Empty means null, an empty string, Unicode-whitespace-only text, or an empty array. 0 and false are filled. Only field_fill_rate treats emptiness as a violation. Other kinds exclude empty values from their denominator and return no_filled_values when no filled values remain. Other skip reasons are no_records, field_missing, values_too_large, too_few_records, unsupported_kind, and check_error. A check failure does not turn an already saved successful collection into a failed one.

Shares breach strictly above the allowance: exactly 7 of 100 at 7% passes. Volume drops and growth include the threshold: 100 → 90 at a 10% threshold creates a finding. Comparisons are exact; only the displayed percentage is rounded. Freshness is measured against run.finished_at; dates with no time zone are UTC.

See Data quality for all 24 template examples and the interface workflow. For parameter definitions, see the vocabulary.

Send the request

Set BASE to your installation URL without a trailing slash and API_KEY to a secret stored on your server. Client setup and browser JavaScript.

curl

curl --fail-with-body -X GET "$BASE/api/v1/quality/rules?locale=en" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Accept: application/json"

Node.js · server

// Node.js server example. Save as request.mjs.
const response = await fetch(process.env.BASE + "/api/v1/quality/rules?locale=en", {
  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/rules?locale=en",
    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 · external client

<?php
$handle = curl_init(getenv("BASE") . "/api/v1/quality/rules?locale=en");
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/rules?locale=en", 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))
}

If the request fails

401: check the key and its expiry. 403: check permissions, read/write scope, and 2FA. 404: check the ID and workspace. 422: check fields and resource state. 429: respect Retry-After. Errors and retries.