jeCheckDevelopers
Get an API key

HACCP

Food-safety journals (HACCP): today's checks per location, journal entries with automatic limit checks, deviations with corrective actions, registries (equipment, suppliers, verification, medical books) and the HACCP plan. Locations follow haccp.view; medical books require the sensitive haccp.medical permission.

GETList HACCP locations

GET /haccp/sites

HACCP status of the company (enabled, country profile, timezone) and the HACCP locations visible to the token.

Token scoperead
Permission haccp.view
MCP toolhaccp_sites · read-only

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.view
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/haccp/sites' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/sites', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/haccp/sites",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('GET', 'haccp/sites', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_sites",
        "arguments": {}
    }
}
Response 200
{
  "data": {
    "enabled": true,
    "profile": "kz",
    "timezone": "Asia/Almaty",
    "sites": [
      {
        "id": 4609,
        "name": "Abay Ave",
        "address": "10 Abay Ave",
        "kind": "cafe"
      }
    ]
  }
}

GETGet today's HACCP checks

GET /haccp/today

What must be recorded today: journals with time slots (status pending, overdue, done, late, missed, skipped), objects with limits, today's entries, staff on shift and open deviations. Without location_id — all HACCP locations visible to the token.

Token scoperead
Permission haccp.view
MCP toolhaccp_today · read-only

Query parameters

location_id integer
One location; omitted — all visible locations.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.view
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/haccp/today?location_id=12' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/today?location_id=12', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/haccp/today",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    params={"location_id":12},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('GET', 'haccp/today', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'query' => [
        'location_id' => 12,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_today",
        "arguments": {
            "location_id": 12
        }
    }
}
Response 200
{
  "data": {
    "day": "2026-11-03",
    "timezone": "Asia/Almaty",
    "late_minutes": 60,
    "sites": [
      {
        "location": {
          "id": 4609,
          "name": "Abay Ave"
        },
        "can_resolve": false,
        "summary": {
          "total": 9,
          "done": 4,
          "pending": 5,
          "overdue": 1,
          "deviations": 1
        },
        "journals": [
          {
            "id": 14,
            "uuid": "4c3b2a1f-0e9d-4c8b-9a7f-6e5d4c3b2a1f",
            "title": "Storage temperature",
            "category": "monitoring",
            "subject": "object",
            "event_based": false,
            "schedule_text": "Daily at 09:00, 17:00",
            "fields": [
              {
                "key": "temperature",
                "label": "Temperature",
                "type": "temperature",
                "unit": "°C",
                "required": true,
                "min": 2,
                "max": 6
              }
            ],
            "can_fill": true,
            "objects": [
              {
                "id": 301,
                "name": "Fridge #1",
                "type": "fridge",
                "type_label": "Fridge",
                "limits": {
                  "temperature": {
                    "min": 2,
                    "max": 6
                  }
                }
              }
            ],
            "slots": [
              {
                "id": 99120,
                "object_id": 301,
                "opens_at": "2026-11-03T08:00:00+05:00",
                "due_at": "2026-11-03T09:00:00+05:00",
                "status": "done",
                "open": true,
                "entry_id": 88231,
                "skip_reason": null
              }
            ],
            "entries": [
              {
                "id": 88231,
                "uuid": "6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a",
                "object_id": 301,
                "subject_user": null,
                "recorded_at": "2026-11-03T09:02:00+05:00",
                "values": {
                  "temperature": 8.4
                },
                "status": "deviation",
                "late": false
              }
            ],
            "done": 1,
            "pending": 1,
            "total": 2
          }
        ],
        "staff": [
          {
            "id": 311,
            "name": "Aigerim Sadykova",
            "on_shift": true
          }
        ],
        "deviations": [
          {
            "uuid": "0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
            "journal": {
              "id": 14,
              "title": "Storage temperature"
            },
            "object_id": 301,
            "severity": "major",
            "status": "open",
            "due_at": "2026-11-04T09:02:00+05:00",
            "overdue": false,
            "recheck": false
          }
        ]
      }
    ]
  }
}

GETList HACCP journals

GET /haccp/journals

Journals of the company with their field structure and schedule.

Token scoperead
Permission haccp.view
Plan featurehaccp
MCP toolhaccp_journals_list · read-only

Query parameters

active boolean
Only enabled (true) or disabled (false) journals.
location_id integer
Only journals kept at this location.
category string
monitoringprpreceivingpersonnelcleaningother

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.view, or the plan has no haccp
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/haccp/journals?active=true' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/journals?active=true', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/haccp/journals",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    params={"active":True},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('GET', 'haccp/journals', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'query' => [
        'active' => true,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_journals_list",
        "arguments": {
            "active": true
        }
    }
}
Response 200
{
  "data": [
    {
      "id": 14,
      "uuid": "4c3b2a1f-0e9d-4c8b-9a7f-6e5d4c3b2a1f",
      "title": "Storage temperature",
      "description": "Fridges and freezers, twice per shift",
      "category": "monitoring",
      "icon": "kitchen",
      "color": "#0e9aa7",
      "active": true,
      "subject": "object",
      "event_based": false,
      "schedule": {
        "type": "times",
        "times": [
          "09:00",
          "17:00"
        ]
      },
      "schedule_text": "Daily at 09:00, 17:00",
      "fields": [
        {
          "key": "temperature",
          "label": "Temperature",
          "type": "temperature",
          "unit": "°C",
          "required": true,
          "min": 2,
          "max": 6
        }
      ],
      "object_types": [
        "fridge",
        "freezer"
      ],
      "location_ids": null,
      "position_ids": [],
      "ccp_id": null,
      "requires_photo_on_deviation": false,
      "template_key": "storage_temperature",
      "updated_at": "2026-10-20T10:00:00+05:00"
    }
  ]
}

GETRetrieve a HACCP journal

GET /haccp/journals/{id}

Journal structure: fields (types, units, limits), schedule, subject (site, object, employee), object types, locations and positions, plus objects at the visible locations.

Token scoperead
Permission haccp.view
Plan featurehaccp
MCP toolhaccp_journals_get · read-only

Path parameters

id integer Required
Journal id.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.view, or the plan has no haccp
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/haccp/journals/42' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/journals/42', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/haccp/journals/42",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('GET', 'haccp/journals/42', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_journals_get",
        "arguments": {
            "id": 42
        }
    }
}
Response 200
{
  "data": {
    "id": 14,
    "uuid": "4c3b2a1f-0e9d-4c8b-9a7f-6e5d4c3b2a1f",
    "title": "Storage temperature",
    "description": "Fridges and freezers, twice per shift",
    "category": "monitoring",
    "icon": "kitchen",
    "color": "#0e9aa7",
    "active": true,
    "subject": "object",
    "event_based": false,
    "schedule": {
      "type": "times",
      "times": [
        "09:00",
        "17:00"
      ]
    },
    "schedule_text": "Daily at 09:00, 17:00",
    "fields": [
      {
        "key": "temperature",
        "label": "Temperature",
        "type": "temperature",
        "unit": "°C",
        "required": true,
        "min": 2,
        "max": 6
      }
    ],
    "object_types": [
      "fridge",
      "freezer"
    ],
    "location_ids": null,
    "position_ids": [],
    "ccp_id": null,
    "requires_photo_on_deviation": false,
    "template_key": "storage_temperature",
    "updated_at": "2026-10-20T10:00:00+05:00",
    "objects": [
      {
        "id": 301,
        "name": "Fridge #1",
        "type": "fridge",
        "location": {
          "id": 4609,
          "name": "Abay Ave"
        },
        "limits": {
          "temperature": {
            "min": 2,
            "max": 6
          }
        },
        "active": true
      }
    ]
  }
}

GETList HACCP journal entries

GET /haccp/entries

Entries at the visible locations, newest first. Archived entries (past the retention period) are not returned; voided entries are included only with voided=true.

Token scoperead
Permission haccp.view
Plan featurehaccp
MCP toolhaccp_entries_list · read-only

Query parameters

page integer
Page number, starting from 1. Default 1. Minimum 1.
per_page integer
Items per page (max 100). Default 25. Value 1–100.
journal_id integer
Only this journal.
location_ids array<integer>
Only these locations.
object_id integer
Only this object (fridge, freezer, …).
status string
okwarningdeviation
late boolean
Only entries recorded late.
voided boolean
true — only voided entries.
recorded_from date-time
Measurement time from.
recorded_to date-time
Measurement time to.
updated_since date-time
Only entries changed after this moment — use for incremental sync.

Responses

StatusDescription
200OK — a page of results with meta
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.view, or the plan has no haccp
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/haccp/entries?journal_id=42' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/entries?journal_id=42', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data, meta } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/haccp/entries",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    params={"journal_id":42},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('GET', 'haccp/entries', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'query' => [
        'journal_id' => 42,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_entries_list",
        "arguments": {
            "journal_id": 42
        }
    }
}
Response 200
{
  "data": [
    {
      "id": 88231,
      "uuid": "6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a",
      "journal_id": 14,
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      },
      "object_id": 301,
      "subject_user": null,
      "user": {
        "id": 311,
        "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
        "name": "Aigerim Sadykova"
      },
      "recorded_at": "2026-11-03T09:02:00+05:00",
      "values": {
        "temperature": 8.4
      },
      "status": "deviation",
      "flags": {
        "breaches": [
          {
            "key": "temperature",
            "label": "Temperature",
            "value": 8.4,
            "unit": "°C",
            "min": 2,
            "max": 6
          }
        ],
        "warnings": []
      },
      "late": false,
      "late_reason": null,
      "comment": "Door was left open",
      "photos": [
        {
          "path": "haccp/761/photos/2026/11/a1b2.jpg",
          "url": "https://storage.example.com/haccp/761/photos/2026/11/a1b2.jpg?X-Amz-Signature=…"
        }
      ],
      "deviation_uuid": "0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
      "source": "app",
      "revision": 0,
      "voided": false,
      "void_reason": null,
      "updated_at": "2026-11-03T09:02:05+05:00"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 1,
    "has_more": false
  }
}

POSTRecord a HACCP journal entry

POST /haccp/journals/{id}/entries

Records a measurement/check. Values are checked against the field and object limits: out of limits — the entry gets status deviation and a deviation is opened (notifications as in the app). Idempotent with uuid: repeating the same uuid returns the existing entry. With deviation_id — a re-check after correction.

Token scopewrite
Permission haccp.fill
MCP toolhaccp_entries_create

Path parameters

id integer Required
Journal id.

Request body

location_id integer Required
Location id.
values object Required
Field values by field key, e.g. {"temperature": 4.2}.
object_id integer
Object (journals with subject object).
subject_user_id integer
Employee the entry is about (journals with subject employee, e.g. health check).
recorded_at date-time
Measurement time (device time for offline sync), default — now.
uuid uuid
Client-generated UUID for safe retries.
comment string
Maximum length 2000.
photos array<string>
Paths from haccp.media.upload. Maximum items 10.
deviation_id integer
Re-check measurement for this deviation.
late_reason string
Why the entry is recorded later than the measurement. Maximum length 255.
latitude number
longitude number

Responses

StatusDescription
201Created
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.fill, is read-only
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/haccp/journals/42/entries' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "location_id": 12,
    "values": [],
    "object_id": 42,
    "subject_user_id": 42
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/journals/42/entries', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "location_id": 12,
      "values": [],
      "object_id": 42,
      "subject_user_id": 42
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/haccp/journals/42/entries",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"location_id":12,"values":[],"object_id":42,"subject_user_id":42},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'haccp/journals/42/entries', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'location_id' => 12,
        'values' => [
        ],
        'object_id' => 42,
        'subject_user_id' => 42,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_entries_create",
        "arguments": {
            "id": 42,
            "location_id": 12,
            "values": [],
            "object_id": 42,
            "subject_user_id": 42
        }
    }
}
Response 201
{
  "data": {
    "id": 88231,
    "uuid": "6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a",
    "journal_id": 14,
    "location": {
      "id": 4609,
      "name": "Abay Ave"
    },
    "object_id": 301,
    "subject_user": null,
    "user": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "recorded_at": "2026-11-03T09:02:00+05:00",
    "values": {
      "temperature": 8.4
    },
    "status": "deviation",
    "flags": {
      "breaches": [
        {
          "key": "temperature",
          "label": "Temperature",
          "value": 8.4,
          "unit": "°C",
          "min": 2,
          "max": 6
        }
      ],
      "warnings": []
    },
    "late": false,
    "late_reason": null,
    "comment": "Door was left open",
    "photos": [
      {
        "path": "haccp/761/photos/2026/11/a1b2.jpg",
        "url": "https://storage.example.com/haccp/761/photos/2026/11/a1b2.jpg?X-Amz-Signature=…"
      }
    ],
    "deviation_uuid": "0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
    "source": "app",
    "revision": 0,
    "voided": false,
    "void_reason": null,
    "updated_at": "2026-11-03T09:02:05+05:00"
  }
}

POSTCorrect a HACCP journal entry

POST /haccp/entries/{id}/revise

Corrects values of an entry with a reason; previous values stay in the revision history. Allowed to the author or with haccp.resolve at the location.

Token scopewrite
Permission haccp.fill or haccp.resolve
Plan featurehaccp
MCP toolhaccp_entries_revise

Path parameters

id integer Required
Entry id.

Request body

values object Required
reason string Required
Length 1–500.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.fill / haccp.resolve, is read-only, or the plan has no haccp
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/haccp/entries/42/revise' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "values": [],
    "reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/entries/42/revise', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "values": [],
      "reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/haccp/entries/42/revise",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"values":[],"reason":"Seal on fridge #2 is torn, temperature rises to +9 °C."},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'haccp/entries/42/revise', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'values' => [
        ],
        'reason' => 'Seal on fridge #2 is torn, temperature rises to +9 °C.',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_entries_revise",
        "arguments": {
            "id": 42,
            "values": [],
            "reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
        }
    }
}
Response 200
{
  "data": {
    "id": 88231,
    "uuid": "6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a",
    "journal_id": 14,
    "location": {
      "id": 4609,
      "name": "Abay Ave"
    },
    "object_id": 301,
    "subject_user": null,
    "user": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "recorded_at": "2026-11-03T09:02:00+05:00",
    "values": {
      "temperature": 8.4
    },
    "status": "deviation",
    "flags": {
      "breaches": [
        {
          "key": "temperature",
          "label": "Temperature",
          "value": 8.4,
          "unit": "°C",
          "min": 2,
          "max": 6
        }
      ],
      "warnings": []
    },
    "late": false,
    "late_reason": null,
    "comment": "Door was left open",
    "photos": [
      {
        "path": "haccp/761/photos/2026/11/a1b2.jpg",
        "url": "https://storage.example.com/haccp/761/photos/2026/11/a1b2.jpg?X-Amz-Signature=…"
      }
    ],
    "deviation_uuid": "0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
    "source": "app",
    "revision": 0,
    "voided": false,
    "void_reason": null,
    "updated_at": "2026-11-03T09:02:05+05:00"
  }
}

POSTVoid a HACCP journal entry

POST /haccp/entries/{id}/void

Marks an entry as void with a reason (haccp.resolve at the location). The entry stays in the journal for the record.

Token scopewrite
Permission haccp.resolve
Plan featurehaccp
MCP toolhaccp_entries_void

Path parameters

id integer Required
Entry id.

Request body

reason string Required
Length 1–500.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.resolve, is read-only, or the plan has no haccp
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/haccp/entries/42/void' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/entries/42/void', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/haccp/entries/42/void",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"reason":"Seal on fridge #2 is torn, temperature rises to +9 °C."},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'haccp/entries/42/void', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'reason' => 'Seal on fridge #2 is torn, temperature rises to +9 °C.',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_entries_void",
        "arguments": {
            "id": 42,
            "reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
        }
    }
}
Response 200
{
  "data": {
    "id": 88231,
    "uuid": "6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a",
    "journal_id": 14,
    "location": {
      "id": 4609,
      "name": "Abay Ave"
    },
    "object_id": 301,
    "subject_user": null,
    "user": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "recorded_at": "2026-11-03T09:02:00+05:00",
    "values": {
      "temperature": 8.4
    },
    "status": "deviation",
    "flags": {
      "breaches": [
        {
          "key": "temperature",
          "label": "Temperature",
          "value": 8.4,
          "unit": "°C",
          "min": 2,
          "max": 6
        }
      ],
      "warnings": []
    },
    "late": false,
    "late_reason": null,
    "comment": "Door was left open",
    "photos": [
      {
        "path": "haccp/761/photos/2026/11/a1b2.jpg",
        "url": "https://storage.example.com/haccp/761/photos/2026/11/a1b2.jpg?X-Amz-Signature=…"
      }
    ],
    "deviation_uuid": "0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
    "source": "app",
    "revision": 0,
    "voided": false,
    "void_reason": null,
    "updated_at": "2026-11-03T09:02:05+05:00"
  }
}

POSTSkip a scheduled HACCP check

POST /haccp/slots/{id}/skip

Marks a time slot as not required today (location closed, equipment off) with a reason. Requires haccp.resolve at the location.

Token scopewrite
Permission haccp.resolve
Plan featurehaccp
MCP toolhaccp_slots_skip

Path parameters

id integer Required
Slot id from haccp.today.

Request body

reason string Required
Length 1–255.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.resolve, is read-only, or the plan has no haccp
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/haccp/slots/42/skip' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/slots/42/skip', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/haccp/slots/42/skip",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"reason":"Seal on fridge #2 is torn, temperature rises to +9 °C."},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'haccp/slots/42/skip', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'reason' => 'Seal on fridge #2 is torn, temperature rises to +9 °C.',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_slots_skip",
        "arguments": {
            "id": 42,
            "reason": "Seal on fridge #2 is torn, temperature rises to +9 °C."
        }
    }
}
Response 200
{
  "data": {
    "skipped": true
  }
}

POSTUpload a HACCP photo or document

POST /haccp/media

Uploads an image (jpg, png, webp, heic) or PDF and returns its path for photos of an entry or deviation, photo fields, staff documents and verification records.

Token scopewrite
Permission haccp.view
MCP toolhaccp_media_upload
Content typemultipart/form-data application/json

Request body

file file Required
File (multipart; in MCP — object with name and content_base64).
folder string
Default photos.
photosdocs

Responses

StatusDescription
201Created
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.view, is read-only
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/haccp/media' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -F 'folder=photos' \
  -F '[email protected]'
const form = new FormData();
form.append('folder', "photos");
form.append('file', fileInput.files[0]);

const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/media', {
  method: 'POST',
  headers: { Authorization: `Bearer ${process.env.JECHECK_TOKEN}` },
  body: form,
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/haccp/media",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    data={"folder":"photos"},
    files=[("file", open("photo.jpg", "rb"))],
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'haccp/media', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'multipart' => [
        ['name' => 'folder', 'contents' => 'photos'],
        ['name' => 'file', 'contents' => fopen('photo.jpg', 'r')],
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_media_upload",
        "arguments": {
            "folder": "photos"
        }
    }
}
Response 201
{
  "data": {
    "path": "haccp/761/photos/2026/11/a1b2.jpg",
    "url": "https://storage.example.com/haccp/761/photos/2026/11/a1b2.jpg?X-Amz-Signature=…"
  }
}

GETList HACCP deviations

GET /haccp/deviations

Deviations at the visible locations: reviews first, then newest. status=open (default) — all unresolved (stages open, correcting, review); closed; all. Use stage for an exact stage.

Token scoperead
Permission haccp.view
Plan featurehaccp
MCP toolhaccp_deviations_list · read-only

Query parameters

page integer
Page number, starting from 1. Default 1. Minimum 1.
per_page integer
Items per page (max 100). Default 25. Value 1–100.
status string
Default open.
openclosedall
stage string
Exact stage.
opencorrectingreviewclosed
severity string
criticalmajorminor
journal_id integer
location_ids array<integer>
Only these locations.
overdue boolean
Only unresolved deviations past their due time.
date_from date
Opened from (inclusive).
date_to date
Opened to (inclusive).
updated_since date-time
Only deviations changed after this moment.

Responses

StatusDescription
200OK — a page of results with meta
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.view, or the plan has no haccp
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/haccp/deviations?status=open' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/deviations?status=open', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data, meta } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/haccp/deviations",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    params={"status":"open"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('GET', 'haccp/deviations', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'query' => [
        'status' => 'open',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_deviations_list",
        "arguments": {
            "status": "open"
        }
    }
}
Response 200
{
  "data": [
    {
      "id": 5120,
      "uuid": "0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
      "journal": {
        "id": 14,
        "title": "Storage temperature"
      },
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      },
      "object": {
        "id": 301,
        "name": "Fridge #1"
      },
      "severity": "major",
      "status": "correcting",
      "breaches": [
        {
          "key": "temperature",
          "label": "Temperature",
          "value": 8.4,
          "unit": "°C",
          "min": 2,
          "max": 6
        }
      ],
      "responsible": {
        "id": 311,
        "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
        "name": "Aigerim Sadykova"
      },
      "due_at": "2026-11-04T09:02:00+05:00",
      "overdue": false,
      "created_at": "2026-11-03T09:02:05+05:00",
      "closed_at": null,
      "updated_at": "2026-11-03T09:40:00+05:00",
      "url": "https://app.jecheck.io/ui/haccp/deviations?open=0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 25,
    "total": 1,
    "has_more": false
  }
}

GETRetrieve a HACCP deviation

GET /haccp/deviations/{uuid}

Deviation card: breached limits, the original entry and the re-check, corrective actions, required fields before submitting, the follow-up task and whether the token can work on / verify it.

Token scoperead
Permission haccp.view
Plan featurehaccp
MCP toolhaccp_deviations_get · read-only

Path parameters

uuid uuid Required
Deviation UUID.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.view, or the plan has no haccp
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('GET', 'haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_deviations_get",
        "arguments": {
            "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
        }
    }
}
Response 200
{
  "data": {
    "id": 5120,
    "uuid": "0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
    "journal": {
      "id": 14,
      "title": "Storage temperature"
    },
    "location": {
      "id": 4609,
      "name": "Abay Ave"
    },
    "object": {
      "id": 301,
      "name": "Fridge #1"
    },
    "severity": "major",
    "status": "correcting",
    "breaches": [
      {
        "key": "temperature",
        "label": "Temperature",
        "value": 8.4,
        "unit": "°C",
        "min": 2,
        "max": 6
      }
    ],
    "responsible": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "due_at": "2026-11-04T09:02:00+05:00",
    "overdue": false,
    "created_at": "2026-11-03T09:02:05+05:00",
    "closed_at": null,
    "updated_at": "2026-11-03T09:40:00+05:00",
    "url": "https://app.jecheck.io/ui/haccp/deviations?open=0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
    "correction": "Products moved to the backup fridge, door seal replaced",
    "product_disposition": "released",
    "product_note": null,
    "root_cause": null,
    "preventive_action": null,
    "photos": [
      {
        "path": "haccp/761/photos/2026/11/c3d4.jpg",
        "url": "https://storage.example.com/haccp/761/photos/2026/11/c3d4.jpg?X-Amz-Signature=…"
      }
    ],
    "required": [
      "correction",
      "product_disposition"
    ],
    "entry": {
      "uuid": "6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a",
      "values": {
        "temperature": 8.4
      },
      "recorded_at": "2026-11-03T09:02:00+05:00",
      "comment": "Door was left open"
    },
    "recheck": {
      "uuid": "7e6d5c4b-3a2f-4e1d-8c0b-9a8f7e6d5c4b",
      "status": "ok",
      "values": {
        "temperature": 4.1
      },
      "recorded_at": "2026-11-03T11:00:00+05:00"
    },
    "task": {
      "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
      "title": "Fix the deviation: Storage temperature",
      "status": "in_progress"
    },
    "closed_by": null,
    "verified_by": null,
    "verified_at": null,
    "verify_note": null,
    "can_work": true,
    "can_verify": false
  }
}

PATCHUpdate corrective actions

PATCH /haccp/deviations/{uuid}

Saves corrective actions as a draft (stage moves to correcting). Allowed to the responsible person or with haccp.resolve at the location; responsible_user_id — with haccp.resolve only.

Token scopewrite
Permission haccp.view
Plan featurehaccp
MCP toolhaccp_deviations_update

Path parameters

uuid uuid Required
Deviation UUID.

Request body

correction string | null
What was done to fix it. Maximum length 5000.
product_disposition string | null
What happened to the product.
releasedreworkeddestroyedreturnedquarantinenot_applicable
product_note string | null
Maximum length 500.
root_cause string | null
Root cause (required for critical deviations). Maximum length 5000.
preventive_action string | null
Preventive action (required for critical deviations). Maximum length 5000.
photos array<string>
Paths from haccp.media.upload. Maximum items 10.
responsible_user_id integer | null

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.view, is read-only, or the plan has no haccp
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X PATCH 'https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "correction": "string",
    "product_disposition": "released",
    "product_note": "string",
    "root_cause": "string"
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "correction": "string",
      "product_disposition": "released",
      "product_note": "string",
      "root_cause": "string"
  }),
});
const { data } = await res.json();
import os, requests

res = requests.patch(
    "https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"correction":"string","product_disposition":"released","product_note":"string","root_cause":"string"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('PATCH', 'haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'correction' => 'string',
        'product_disposition' => 'released',
        'product_note' => 'string',
        'root_cause' => 'string',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_deviations_update",
        "arguments": {
            "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "correction": "string",
            "product_disposition": "released",
            "product_note": "string",
            "root_cause": "string"
        }
    }
}
Response 200
{
  "data": {
    "id": 5120,
    "uuid": "0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
    "journal": {
      "id": 14,
      "title": "Storage temperature"
    },
    "location": {
      "id": 4609,
      "name": "Abay Ave"
    },
    "object": {
      "id": 301,
      "name": "Fridge #1"
    },
    "severity": "major",
    "status": "correcting",
    "breaches": [
      {
        "key": "temperature",
        "label": "Temperature",
        "value": 8.4,
        "unit": "°C",
        "min": 2,
        "max": 6
      }
    ],
    "responsible": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "due_at": "2026-11-04T09:02:00+05:00",
    "overdue": false,
    "created_at": "2026-11-03T09:02:05+05:00",
    "closed_at": null,
    "updated_at": "2026-11-03T09:40:00+05:00",
    "url": "https://app.jecheck.io/ui/haccp/deviations?open=0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
    "correction": "Products moved to the backup fridge, door seal replaced",
    "product_disposition": "released",
    "product_note": null,
    "root_cause": null,
    "preventive_action": null,
    "photos": [
      {
        "path": "haccp/761/photos/2026/11/c3d4.jpg",
        "url": "https://storage.example.com/haccp/761/photos/2026/11/c3d4.jpg?X-Amz-Signature=…"
      }
    ],
    "required": [
      "correction",
      "product_disposition"
    ],
    "entry": {
      "uuid": "6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a",
      "values": {
        "temperature": 8.4
      },
      "recorded_at": "2026-11-03T09:02:00+05:00",
      "comment": "Door was left open"
    },
    "recheck": {
      "uuid": "7e6d5c4b-3a2f-4e1d-8c0b-9a8f7e6d5c4b",
      "status": "ok",
      "values": {
        "temperature": 4.1
      },
      "recorded_at": "2026-11-03T11:00:00+05:00"
    },
    "task": {
      "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
      "title": "Fix the deviation: Storage temperature",
      "status": "in_progress"
    },
    "closed_by": null,
    "verified_by": null,
    "verified_at": null,
    "verify_note": null,
    "can_work": true,
    "can_verify": false
  }
}

POSTSubmit corrective actions for review

POST /haccp/deviations/{uuid}/submit

Sends the deviation to review when the required fields are filled. If the token itself may close it (haccp.resolve, or haccp.close_critical for critical ones), it is closed right away.

Token scopewrite
Permission haccp.view
Plan featurehaccp
MCP toolhaccp_deviations_submit

Path parameters

uuid uuid Required
Deviation UUID.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.view, is read-only, or the plan has no haccp
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/submit' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/submit', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/submit",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/submit', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_deviations_submit",
        "arguments": {
            "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b"
        }
    }
}
Response 200
{
  "data": {
    "id": 5120,
    "uuid": "0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
    "journal": {
      "id": 14,
      "title": "Storage temperature"
    },
    "location": {
      "id": 4609,
      "name": "Abay Ave"
    },
    "object": {
      "id": 301,
      "name": "Fridge #1"
    },
    "severity": "major",
    "status": "correcting",
    "breaches": [
      {
        "key": "temperature",
        "label": "Temperature",
        "value": 8.4,
        "unit": "°C",
        "min": 2,
        "max": 6
      }
    ],
    "responsible": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "due_at": "2026-11-04T09:02:00+05:00",
    "overdue": false,
    "created_at": "2026-11-03T09:02:05+05:00",
    "closed_at": null,
    "updated_at": "2026-11-03T09:40:00+05:00",
    "url": "https://app.jecheck.io/ui/haccp/deviations?open=0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
    "correction": "Products moved to the backup fridge, door seal replaced",
    "product_disposition": "released",
    "product_note": null,
    "root_cause": null,
    "preventive_action": null,
    "photos": [
      {
        "path": "haccp/761/photos/2026/11/c3d4.jpg",
        "url": "https://storage.example.com/haccp/761/photos/2026/11/c3d4.jpg?X-Amz-Signature=…"
      }
    ],
    "required": [
      "correction",
      "product_disposition"
    ],
    "entry": {
      "uuid": "6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a",
      "values": {
        "temperature": 8.4
      },
      "recorded_at": "2026-11-03T09:02:00+05:00",
      "comment": "Door was left open"
    },
    "recheck": {
      "uuid": "7e6d5c4b-3a2f-4e1d-8c0b-9a8f7e6d5c4b",
      "status": "ok",
      "values": {
        "temperature": 4.1
      },
      "recorded_at": "2026-11-03T11:00:00+05:00"
    },
    "task": {
      "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
      "title": "Fix the deviation: Storage temperature",
      "status": "in_progress"
    },
    "closed_by": null,
    "verified_by": null,
    "verified_at": null,
    "verify_note": null,
    "can_work": true,
    "can_verify": false
  }
}

POSTClose a HACCP deviation

POST /haccp/deviations/{uuid}/close

Verifies corrective actions and closes the deviation. Requires haccp.resolve at the location; critical deviations — haccp.close_critical.

Token scopewrite
Permission haccp.resolve or haccp.close_critical
Plan featurehaccp
MCP toolhaccp_deviations_close

Path parameters

uuid uuid Required
Deviation UUID.

Request body

note string | null
Verification note. Maximum length 2000.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.resolve / haccp.close_critical, is read-only, or the plan has no haccp
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/close' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "note": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/close', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "note": "Seal on fridge #2 is torn, temperature rises to +9 °C."
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/close",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"note":"Seal on fridge #2 is torn, temperature rises to +9 °C."},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/close', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'note' => 'Seal on fridge #2 is torn, temperature rises to +9 °C.',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_deviations_close",
        "arguments": {
            "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "note": "Seal on fridge #2 is torn, temperature rises to +9 °C."
        }
    }
}
Response 200
{
  "data": {
    "id": 5120,
    "uuid": "0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
    "journal": {
      "id": 14,
      "title": "Storage temperature"
    },
    "location": {
      "id": 4609,
      "name": "Abay Ave"
    },
    "object": {
      "id": 301,
      "name": "Fridge #1"
    },
    "severity": "major",
    "status": "correcting",
    "breaches": [
      {
        "key": "temperature",
        "label": "Temperature",
        "value": 8.4,
        "unit": "°C",
        "min": 2,
        "max": 6
      }
    ],
    "responsible": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "due_at": "2026-11-04T09:02:00+05:00",
    "overdue": false,
    "created_at": "2026-11-03T09:02:05+05:00",
    "closed_at": null,
    "updated_at": "2026-11-03T09:40:00+05:00",
    "url": "https://app.jecheck.io/ui/haccp/deviations?open=0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
    "correction": "Products moved to the backup fridge, door seal replaced",
    "product_disposition": "released",
    "product_note": null,
    "root_cause": null,
    "preventive_action": null,
    "photos": [
      {
        "path": "haccp/761/photos/2026/11/c3d4.jpg",
        "url": "https://storage.example.com/haccp/761/photos/2026/11/c3d4.jpg?X-Amz-Signature=…"
      }
    ],
    "required": [
      "correction",
      "product_disposition"
    ],
    "entry": {
      "uuid": "6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a",
      "values": {
        "temperature": 8.4
      },
      "recorded_at": "2026-11-03T09:02:00+05:00",
      "comment": "Door was left open"
    },
    "recheck": {
      "uuid": "7e6d5c4b-3a2f-4e1d-8c0b-9a8f7e6d5c4b",
      "status": "ok",
      "values": {
        "temperature": 4.1
      },
      "recorded_at": "2026-11-03T11:00:00+05:00"
    },
    "task": {
      "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
      "title": "Fix the deviation: Storage temperature",
      "status": "in_progress"
    },
    "closed_by": null,
    "verified_by": null,
    "verified_at": null,
    "verify_note": null,
    "can_work": true,
    "can_verify": false
  }
}

POSTReturn a HACCP deviation for rework

POST /haccp/deviations/{uuid}/reject

Returns a deviation in review back to correction with a note. Same permissions as closing.

Token scopewrite
Permission haccp.resolve or haccp.close_critical
Plan featurehaccp
MCP toolhaccp_deviations_reject

Path parameters

uuid uuid Required
Deviation UUID.

Request body

note string Required
Length 1–2000.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.resolve / haccp.close_critical, is read-only, or the plan has no haccp
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/reject' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "note": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/reject', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "note": "Seal on fridge #2 is torn, temperature rises to +9 °C."
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/reject",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"note":"Seal on fridge #2 is torn, temperature rises to +9 °C."},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/reject', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'note' => 'Seal on fridge #2 is torn, temperature rises to +9 °C.',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_deviations_reject",
        "arguments": {
            "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "note": "Seal on fridge #2 is torn, temperature rises to +9 °C."
        }
    }
}
Response 200
{
  "data": {
    "id": 5120,
    "uuid": "0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
    "journal": {
      "id": 14,
      "title": "Storage temperature"
    },
    "location": {
      "id": 4609,
      "name": "Abay Ave"
    },
    "object": {
      "id": 301,
      "name": "Fridge #1"
    },
    "severity": "major",
    "status": "correcting",
    "breaches": [
      {
        "key": "temperature",
        "label": "Temperature",
        "value": 8.4,
        "unit": "°C",
        "min": 2,
        "max": 6
      }
    ],
    "responsible": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "due_at": "2026-11-04T09:02:00+05:00",
    "overdue": false,
    "created_at": "2026-11-03T09:02:05+05:00",
    "closed_at": null,
    "updated_at": "2026-11-03T09:40:00+05:00",
    "url": "https://app.jecheck.io/ui/haccp/deviations?open=0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
    "correction": "Products moved to the backup fridge, door seal replaced",
    "product_disposition": "released",
    "product_note": null,
    "root_cause": null,
    "preventive_action": null,
    "photos": [
      {
        "path": "haccp/761/photos/2026/11/c3d4.jpg",
        "url": "https://storage.example.com/haccp/761/photos/2026/11/c3d4.jpg?X-Amz-Signature=…"
      }
    ],
    "required": [
      "correction",
      "product_disposition"
    ],
    "entry": {
      "uuid": "6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a",
      "values": {
        "temperature": 8.4
      },
      "recorded_at": "2026-11-03T09:02:00+05:00",
      "comment": "Door was left open"
    },
    "recheck": {
      "uuid": "7e6d5c4b-3a2f-4e1d-8c0b-9a8f7e6d5c4b",
      "status": "ok",
      "values": {
        "temperature": 4.1
      },
      "recorded_at": "2026-11-03T11:00:00+05:00"
    },
    "task": {
      "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
      "title": "Fix the deviation: Storage temperature",
      "status": "in_progress"
    },
    "closed_by": null,
    "verified_by": null,
    "verified_at": null,
    "verify_note": null,
    "can_work": true,
    "can_verify": false
  }
}

POSTCreate a task for a HACCP deviation

POST /haccp/deviations/{uuid}/task

Creates a task when the fix needs work (repair, deep cleaning). Assign to a user (assignee_id) or a team queue (team_id). Requires haccp.resolve at the location.

Token scopewrite
Permission haccp.resolve
Plan featurehaccp
MCP toolhaccp_deviations_task

Path parameters

uuid uuid Required
Deviation UUID.

Request body

assignee_id integer
Executor (user id).
team_id integer
Team id instead of a user.
title string
Default: "Fix the deviation: ". Maximum length 500.
description string
Maximum length 5000.
due_at date-time
Default: +4 h for critical, +24 h otherwise.

Responses

StatusDescription
201Created
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.resolve, is read-only, or the plan has no haccp
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/task' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "assignee_id": 42,
    "team_id": 42,
    "title": "Replace the broken fridge seal",
    "description": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/task', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "assignee_id": 42,
      "team_id": 42,
      "title": "Replace the broken fridge seal",
      "description": "Seal on fridge #2 is torn, temperature rises to +9 °C."
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/task",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"assignee_id":42,"team_id":42,"title":"Replace the broken fridge seal","description":"Seal on fridge #2 is torn, temperature rises to +9 °C."},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'haccp/deviations/3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b/task', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'assignee_id' => 42,
        'team_id' => 42,
        'title' => 'Replace the broken fridge seal',
        'description' => 'Seal on fridge #2 is torn, temperature rises to +9 °C.',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_deviations_task",
        "arguments": {
            "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
            "assignee_id": 42,
            "team_id": 42,
            "title": "Replace the broken fridge seal",
            "description": "Seal on fridge #2 is torn, temperature rises to +9 °C."
        }
    }
}
Response 201
{
  "data": {
    "id": 5120,
    "uuid": "0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
    "journal": {
      "id": 14,
      "title": "Storage temperature"
    },
    "location": {
      "id": 4609,
      "name": "Abay Ave"
    },
    "object": {
      "id": 301,
      "name": "Fridge #1"
    },
    "severity": "major",
    "status": "correcting",
    "breaches": [
      {
        "key": "temperature",
        "label": "Temperature",
        "value": 8.4,
        "unit": "°C",
        "min": 2,
        "max": 6
      }
    ],
    "responsible": {
      "id": 311,
      "uuid": "1e2d3c4b-5a69-4788-9a0b-1c2d3e4f5a6b",
      "name": "Aigerim Sadykova"
    },
    "due_at": "2026-11-04T09:02:00+05:00",
    "overdue": false,
    "created_at": "2026-11-03T09:02:05+05:00",
    "closed_at": null,
    "updated_at": "2026-11-03T09:40:00+05:00",
    "url": "https://app.jecheck.io/ui/haccp/deviations?open=0a9b8c7d-6e5f-4a3b-8c2d-1e0f9a8b7c6d",
    "correction": "Products moved to the backup fridge, door seal replaced",
    "product_disposition": "released",
    "product_note": null,
    "root_cause": null,
    "preventive_action": null,
    "photos": [
      {
        "path": "haccp/761/photos/2026/11/c3d4.jpg",
        "url": "https://storage.example.com/haccp/761/photos/2026/11/c3d4.jpg?X-Amz-Signature=…"
      }
    ],
    "required": [
      "correction",
      "product_disposition"
    ],
    "entry": {
      "uuid": "6d5c4b3a-2f1e-4d0c-9b8a-7f6e5d4c3b2a",
      "values": {
        "temperature": 8.4
      },
      "recorded_at": "2026-11-03T09:02:00+05:00",
      "comment": "Door was left open"
    },
    "recheck": {
      "uuid": "7e6d5c4b-3a2f-4e1d-8c0b-9a8f7e6d5c4b",
      "status": "ok",
      "values": {
        "temperature": 4.1
      },
      "recorded_at": "2026-11-03T11:00:00+05:00"
    },
    "task": {
      "uuid": "3f2b8c1e-7a4d-4e2f-9b1a-6c5d4e3f2a1b",
      "title": "Fix the deviation: Storage temperature",
      "status": "in_progress"
    },
    "closed_by": null,
    "verified_by": null,
    "verified_at": null,
    "verify_note": null,
    "can_work": true,
    "can_verify": false
  }
}

GETList HACCP plans

GET /haccp/plans

HACCP plans with status, version, review date and completion of the 12 Codex steps.

Token scoperead
Permission haccp.plan
Plan featurehaccp
MCP toolhaccp_plans_list · read-only

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.plan, or the plan has no haccp
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/haccp/plans' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/plans', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/haccp/plans",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('GET', 'haccp/plans', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_plans_list",
        "arguments": {}
    }
}
Response 200
{
  "data": [
    {
      "id": 3,
      "uuid": "8b7a6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d",
      "title": "Hot kitchen",
      "status": "approved",
      "version": 2,
      "review_due_at": "2027-05-01",
      "location_ids": [
        4609
      ],
      "progress": {
        "1": true,
        "2": true,
        "3": true,
        "4": true,
        "5": true,
        "6": true,
        "7": true,
        "8": true,
        "9": true,
        "10": true,
        "11": false,
        "12": true
      },
      "problems": 1,
      "has_changes": false,
      "counts": {
        "steps": 9,
        "hazards": 14,
        "ccps": 2
      },
      "updated_at": "2026-10-28T15:00:00+05:00"
    }
  ]
}

GETRetrieve a HACCP plan

GET /haccp/plans/{id}

Full plan by the 12 Codex steps: team, products, process flow steps, hazard analysis, critical control points with limits, monitoring and corrective actions; open problems per step.

Token scoperead
Permission haccp.plan
Plan featurehaccp
MCP toolhaccp_plans_get · read-only

Path parameters

id integer Required
Plan id.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.plan, or the plan has no haccp
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/haccp/plans/42' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/plans/42', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/haccp/plans/42",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('GET', 'haccp/plans/42', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_plans_get",
        "arguments": {
            "id": 42
        }
    }
}
Response 200
{
  "data": {
    "id": 3,
    "uuid": "8b7a6c5d-4e3f-4a2b-9c1d-0e9f8a7b6c5d",
    "title": "Hot kitchen",
    "scope": "Hot dishes of the main menu",
    "location_ids": [
      4609
    ],
    "status": "approved",
    "intended_use": "Served hot within 2 hours",
    "consumer_groups": [
      "general"
    ],
    "prerequisites": [],
    "flow_confirmed_at": "2026-10-10",
    "flow_confirmed_by": 120,
    "documentation": {
      "records": []
    },
    "validation": null,
    "risk_threshold": 6,
    "version": 2,
    "review_due_at": "2027-05-01",
    "updated_at": "2026-10-28T15:00:00+05:00",
    "team": [
      {
        "id": 1,
        "user_id": 120,
        "name": "Daniyar Omarov",
        "role": "leader",
        "job_title": "Chef",
        "competence": "HACCP course 2025",
        "responsibilities": "Plan owner"
      }
    ],
    "products": [
      {
        "id": 4,
        "name": "Chicken soup",
        "group": "hot",
        "composition": "Chicken, vegetables",
        "characteristics": null,
        "processing": "Boiling",
        "packaging": null,
        "storage": "+2…+6 °C",
        "shelf_life": "24 h",
        "distribution": null,
        "allergens": [
          "celery"
        ],
        "labeling": null,
        "intended_use": null
      }
    ],
    "steps": [
      {
        "id": 21,
        "name": "Cooking",
        "type": "process",
        "description": null,
        "parameters": "≥ 75 °C in the core",
        "inputs": null,
        "outsourced": false,
        "rework": false,
        "analyzed": true,
        "next": [
          22
        ],
        "products": [
          4
        ],
        "position": 3
      }
    ],
    "hazards": [
      {
        "id": 51,
        "step_id": 21,
        "type": "biological",
        "name": "Salmonella survival",
        "source": "Raw chicken",
        "likelihood": 3,
        "severity": 3,
        "significant": true,
        "justification": null,
        "control_measures": "Core temperature control",
        "decision": {
          "q1": true
        },
        "outcome": "ccp"
      }
    ],
    "ccps": [
      {
        "id": 7,
        "step_id": 21,
        "hazard_id": 51,
        "kind": "ccp",
        "code": "CCP-1",
        "name": "Core temperature",
        "limits": [
          {
            "field": "temperature",
            "min": 75
          }
        ],
        "limits_justification": null,
        "monitoring_what": "Core temperature",
        "monitoring_how": "Probe thermometer",
        "monitoring_frequency": "Each batch",
        "monitoring_positions": [],
        "journal_id": 15,
        "corrective_actions": "Continue cooking",
        "corrective_positions": [],
        "verification": "Weekly journal review",
        "verification_frequency": "weekly",
        "validation": null,
        "records": null
      }
    ],
    "problems": [
      {
        "step": 11,
        "message": "Describe verification procedures"
      }
    ]
  }
}

GETList HACCP equipment

GET /haccp/equipment

Controlled objects (fridges, freezers, thermometers, …) at the visible locations with limits, calibration dates and the last measurement.

Token scoperead
Permission haccp.view
Plan featurehaccp
MCP toolhaccp_equipment_list · read-only

Query parameters

location_id integer
Only this location.
type string
Object type key, e.g. fridge.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.view, or the plan has no haccp
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/haccp/equipment?location_id=12' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/equipment?location_id=12', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/haccp/equipment",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    params={"location_id":12},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('GET', 'haccp/equipment', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'query' => [
        'location_id' => 12,
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_equipment_list",
        "arguments": {
            "location_id": 12
        }
    }
}
Response 200
{
  "data": [
    {
      "id": 301,
      "location": {
        "id": 4609,
        "name": "Abay Ave"
      },
      "type": "fridge",
      "type_label": "Fridge",
      "name": "Fridge #1",
      "serial": "LG-2231",
      "limits": {
        "temperature": {
          "min": 2,
          "max": 6
        }
      },
      "calibrated_at": null,
      "calibration_due_at": null,
      "calibration_overdue": false,
      "note": null,
      "active": true,
      "last": {
        "values": {
          "temperature": 4.1
        },
        "status": "ok",
        "recorded_at": "2026-11-03T11:00:00+05:00"
      }
    }
  ]
}

POSTAdd HACCP equipment

POST /haccp/equipment

Adds a controlled object at a HACCP location. Without limits the country profile defaults for the type are used. Requires haccp.registries.

Token scopewrite
Permission haccp.registries
Plan featurehaccp
MCP toolhaccp_equipment_create

Request body

location_id integer Required
HACCP location id.
type string
Object type key of the country profile (fridge, freezer, …); unknown — other. Maximum length 60.
name string Required
Maximum length 190.
serial string | null
Maximum length 120.
limits object
{"temperature": {"min": 2, "max": 6}, "humidity": {"min": 40, "max": 70}}.
calibrated_at date | null
calibration_due_at date | null
note string | null
Maximum length 2000.
active boolean

Responses

StatusDescription
201Created
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.registries, is read-only, or the plan has no haccp
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/haccp/equipment' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "location_id": 12,
    "type": "string",
    "name": "Replace the broken fridge seal",
    "serial": "string"
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/equipment', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "location_id": 12,
      "type": "string",
      "name": "Replace the broken fridge seal",
      "serial": "string"
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/haccp/equipment",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"location_id":12,"type":"string","name":"Replace the broken fridge seal","serial":"string"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'haccp/equipment', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'location_id' => 12,
        'type' => 'string',
        'name' => 'Replace the broken fridge seal',
        'serial' => 'string',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_equipment_create",
        "arguments": {
            "location_id": 12,
            "type": "string",
            "name": "Replace the broken fridge seal",
            "serial": "string"
        }
    }
}
Response 201
{
  "data": {
    "id": 301,
    "location": {
      "id": 4609,
      "name": "Abay Ave"
    },
    "type": "fridge",
    "type_label": "Fridge",
    "name": "Fridge #1",
    "serial": "LG-2231",
    "limits": {
      "temperature": {
        "min": 2,
        "max": 6
      }
    },
    "calibrated_at": null,
    "calibration_due_at": null,
    "calibration_overdue": false,
    "note": null,
    "active": true,
    "last": {
      "values": {
        "temperature": 4.1
      },
      "status": "ok",
      "recorded_at": "2026-11-03T11:00:00+05:00"
    }
  }
}

PATCHUpdate HACCP equipment

PATCH /haccp/equipment/{id}

Partial update of a controlled object. Requires haccp.registries.

Token scopewrite
Permission haccp.registries
Plan featurehaccp
MCP toolhaccp_equipment_update

Path parameters

id integer Required
Object id.

Request body

location_id integer
HACCP location id.
type string
Object type key of the country profile (fridge, freezer, …); unknown — other. Maximum length 60.
name string
Maximum length 190.
serial string | null
Maximum length 120.
limits object
{"temperature": {"min": 2, "max": 6}, "humidity": {"min": 40, "max": 70}}.
calibrated_at date | null
calibration_due_at date | null
note string | null
Maximum length 2000.
active boolean

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.registries, is read-only, or the plan has no haccp
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X PATCH 'https://app.jecheck.io/api/ext/v1/haccp/equipment/42' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "location_id": 12,
    "type": "string",
    "name": "Replace the broken fridge seal",
    "serial": "string"
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/equipment/42', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "location_id": 12,
      "type": "string",
      "name": "Replace the broken fridge seal",
      "serial": "string"
  }),
});
const { data } = await res.json();
import os, requests

res = requests.patch(
    "https://app.jecheck.io/api/ext/v1/haccp/equipment/42",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"location_id":12,"type":"string","name":"Replace the broken fridge seal","serial":"string"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('PATCH', 'haccp/equipment/42', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'location_id' => 12,
        'type' => 'string',
        'name' => 'Replace the broken fridge seal',
        'serial' => 'string',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_equipment_update",
        "arguments": {
            "id": 42,
            "location_id": 12,
            "type": "string",
            "name": "Replace the broken fridge seal",
            "serial": "string"
        }
    }
}
Response 200
{
  "data": {
    "id": 301,
    "location": {
      "id": 4609,
      "name": "Abay Ave"
    },
    "type": "fridge",
    "type_label": "Fridge",
    "name": "Fridge #1",
    "serial": "LG-2231",
    "limits": {
      "temperature": {
        "min": 2,
        "max": 6
      }
    },
    "calibrated_at": null,
    "calibration_due_at": null,
    "calibration_overdue": false,
    "note": null,
    "active": true,
    "last": {
      "values": {
        "temperature": 4.1
      },
      "status": "ok",
      "recorded_at": "2026-11-03T11:00:00+05:00"
    }
  }
}

DELDelete HACCP equipment

DELETE /haccp/equipment/{id}

Removes a controlled object; its entries are kept. Requires haccp.registries.

Token scopewrite
Permission haccp.registries
Plan featurehaccp
MCP toolhaccp_equipment_delete · destructive

Path parameters

id integer Required
Object id.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.registries, is read-only, or the plan has no haccp
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X DELETE 'https://app.jecheck.io/api/ext/v1/haccp/equipment/42' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/equipment/42', {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.delete(
    "https://app.jecheck.io/api/ext/v1/haccp/equipment/42",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('DELETE', 'haccp/equipment/42', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_equipment_delete",
        "arguments": {
            "id": 42
        }
    }
}
Response 200
{
  "data": {
    "deleted": true
  }
}

GETList staff medical books

GET /haccp/medical-books

Employees of locations where the token has the sensitive haccp.medical permission, with the latest document of each type (medical book, hygiene training, …) and its status (valid, expiring, expired, missing). Each call is written to the access log.

Token scoperead
Permission haccp.medical
Plan featurehaccp
MCP toolhaccp_medical_list · read-only

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.medical, or the plan has no haccp
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/haccp/medical-books' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/medical-books', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/haccp/medical-books",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('GET', 'haccp/medical-books', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_medical_list",
        "arguments": {}
    }
}
Response 200
{
  "data": [
    {
      "id": 311,
      "name": "Aigerim Sadykova",
      "position": "Cook",
      "location_ids": [
        4609
      ],
      "problems": 0,
      "docs": {
        "medical_book": {
          "doc_id": 77,
          "number": "MB-1022",
          "issued_at": "2026-03-01",
          "expires_at": "2027-03-01",
          "status": "valid"
        }
      },
      "history": [
        {
          "id": 77,
          "type": "medical_book",
          "number": "MB-1022",
          "issued_at": "2026-03-01",
          "expires_at": "2027-03-01",
          "issuer": "City clinic #4",
          "note": null,
          "file": {
            "path": "haccp/761/docs/2026/03/mb.pdf",
            "url": "https://storage.example.com/haccp/761/docs/2026/03/mb.pdf?X-Amz-Signature=…"
          }
        }
      ]
    }
  ]
}

POSTAdd a staff medical document

POST /haccp/medical-books

Records a medical book / health document of an employee. Requires haccp.registries and haccp.medical for the employee's location. expires_at defaults from the document type validity.

Token scopewrite
Permission haccp.registries
Plan featurehaccp
MCP toolhaccp_medical_create

Request body

user_id integer Required
type string Required
Document type key of the country profile, e.g. medical_book. Maximum length 60.
number string
Maximum length 120.
issued_at date
expires_at date
issuer string
Maximum length 255.
note string
Maximum length 500.
file string
Scan path from haccp.media.upload (folder docs).

Responses

StatusDescription
201Created
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.registries, is read-only, or the plan has no haccp
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/haccp/medical-books' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "user_id": 42,
    "type": "string",
    "number": "string",
    "issued_at": "2026-11-01"
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/medical-books', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "user_id": 42,
      "type": "string",
      "number": "string",
      "issued_at": "2026-11-01"
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/haccp/medical-books",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"user_id":42,"type":"string","number":"string","issued_at":"2026-11-01"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'haccp/medical-books', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'user_id' => 42,
        'type' => 'string',
        'number' => 'string',
        'issued_at' => '2026-11-01',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_medical_create",
        "arguments": {
            "user_id": 42,
            "type": "string",
            "number": "string",
            "issued_at": "2026-11-01"
        }
    }
}
Response 201
{
  "data": {
    "id": 77,
    "user_id": 311,
    "type": "medical_book",
    "number": "MB-1022",
    "issued_at": "2026-03-01",
    "expires_at": "2027-03-01",
    "issuer": "City clinic #4",
    "note": null
  }
}

GETList HACCP suppliers

GET /haccp/suppliers

Approved suppliers with their documents (certificates, declarations) and expiry status.

Token scoperead
Permission haccp.view
Plan featurehaccp
MCP toolhaccp_suppliers_list · read-only

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.view, or the plan has no haccp
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/haccp/suppliers' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/suppliers', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/haccp/suppliers",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('GET', 'haccp/suppliers', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_suppliers_list",
        "arguments": {}
    }
}
Response 200
{
  "data": [
    {
      "id": 12,
      "name": "Fresh Farm LLP",
      "tax_id": "123456789012",
      "contact": "Arman",
      "phone": "+7 701 000 00 00",
      "email": "[email protected]",
      "products": "Poultry",
      "status": "approved",
      "rating": 5,
      "note": null,
      "evaluated_at": "2026-09-01",
      "expired": 0,
      "expiring": 1,
      "docs": [
        {
          "id": 30,
          "type": "certificate",
          "number": "KZ-77",
          "product": "Chicken",
          "issued_at": "2026-01-10",
          "valid_until": "2026-11-20",
          "status": "expiring",
          "file": null
        }
      ]
    }
  ]
}

GETList HACCP verification activities

GET /haccp/verifications

Scheduled verification activities (internal audits, lab tests, swabs, calibration) with next due date and the latest records.

Token scoperead
Permission haccp.view
Plan featurehaccp
MCP toolhaccp_verifications_list · read-only

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.view, or the plan has no haccp
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/haccp/verifications' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/verifications', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/haccp/verifications",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('GET', 'haccp/verifications', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_verifications_list",
        "arguments": {}
    }
}
Response 200
{
  "data": [
    {
      "id": 5,
      "type": "internal_audit",
      "title": "Quarterly HACCP audit",
      "description": null,
      "every_days": 90,
      "next_due_at": "2026-12-01",
      "last_done_at": "2026-09-01",
      "responsible": {
        "id": 120,
        "uuid": "7a6b5c4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d",
        "name": "Daniyar Omarov"
      },
      "plan_id": 3,
      "active": true,
      "overdue": false,
      "records": [
        {
          "id": 41,
          "done_at": "2026-09-01",
          "user": "Daniyar Omarov",
          "result": "ok",
          "findings": null,
          "actions": null,
          "files": []
        }
      ]
    }
  ]
}

POSTRecord a verification result

POST /haccp/verifications/{id}/records

Records that a verification activity was done; the next due date moves by its interval. Findings are required when result is issues. Requires haccp.registries.

Token scopewrite
Permission haccp.registries
Plan featurehaccp
MCP toolhaccp_verifications_record

Path parameters

id integer Required
Verification activity id.

Request body

done_at date
Default — today.
result string
Default ok.
okissues
findings string
Maximum length 5000.
actions string
Maximum length 5000.
files array<string>
Paths from haccp.media.upload. Maximum items 10.

Responses

StatusDescription
201Created
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.registries, is read-only, or the plan has no haccp
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl -X POST 'https://app.jecheck.io/api/ext/v1/haccp/verifications/42/records' \
  -H 'Authorization: Bearer $JECHECK_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "done_at": "2026-11-01",
    "result": "ok",
    "findings": "string",
    "actions": "string"
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/verifications/42/records', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
      "done_at": "2026-11-01",
      "result": "ok",
      "findings": "string",
      "actions": "string"
  }),
});
const { data } = await res.json();
import os, requests

res = requests.post(
    "https://app.jecheck.io/api/ext/v1/haccp/verifications/42/records",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
    json={"done_at":"2026-11-01","result":"ok","findings":"string","actions":"string"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('POST', 'haccp/verifications/42/records', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
    'json' => [
        'done_at' => '2026-11-01',
        'result' => 'ok',
        'findings' => 'string',
        'actions' => 'string',
    ],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_verifications_record",
        "arguments": {
            "id": 42,
            "done_at": "2026-11-01",
            "result": "ok",
            "findings": "string",
            "actions": "string"
        }
    }
}
Response 201
{
  "data": {
    "id": 42,
    "verification_id": 5,
    "done_at": "2026-11-03",
    "result": "ok",
    "findings": null,
    "actions": null
  }
}

GETGet HACCP settings

GET /haccp/settings

Module settings: country profile of norms, timezone, shift start, HACCP leader, retention and grace periods, options and HACCP locations. Requires haccp.settings.

Token scoperead
Permission haccp.settings
Plan featurehaccp
MCP toolhaccp_settings_get · read-only

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks haccp.settings, or the plan has no haccp
429rate_limited — Too many requests — retry after Retry-After seconds
Request
curl 'https://app.jecheck.io/api/ext/v1/haccp/settings' \
  -H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/haccp/settings', {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
  },
});
const { data } = await res.json();
import os, requests

res = requests.get(
    "https://app.jecheck.io/api/ext/v1/haccp/settings",
    headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);

$res = $client->request('GET', 'haccp/settings', [
    'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
        "name": "haccp_settings_get",
        "arguments": {}
    }
}
Response 200
{
  "data": {
    "profile": "kz",
    "profile_name": "Kazakhstan",
    "timezone": "Asia/Almaty",
    "shift_start": "08:00",
    "leader": {
      "id": 120,
      "uuid": "7a6b5c4d-3e2f-4a1b-9c8d-7e6f5a4b3c2d",
      "name": "Daniyar Omarov"
    },
    "retention_months": 12,
    "grace_minutes": 30,
    "options": {
      "late_minutes": 60,
      "expiry_warning_days": 30
    },
    "onboarded_at": "2026-10-17T12:00:00+05:00",
    "sites": [
      {
        "location": {
          "id": 4609,
          "name": "Abay Ave"
        },
        "kind": "cafe",
        "seats": 40,
        "shift_start": null,
        "work_days": [
          1,
          2,
          3,
          4,
          5,
          6,
          7
        ],
        "active": true
      }
    ]
  }
}