Skip to content

Register Reports

Hyvä POS is in closed beta

Hyvä POS is currently in a closed beta (pilot phase) with a small group of merchants. It is not yet generally available: the App Store release follows the pilot, and features and configuration may still change, possibly in backwards-incompatible ways, before the general release. Want to take part? Sign up at hyva.io/pos.

Every register closing (the Z report of one register session) that a PRO register issues is uploaded to Magento, where it is stored once in hyva_pos_register_report and read by the admin and by connectors. Module 1.4.0; see the feature page for what a closing holds and how the admin shows it.

Method Route Purpose
POST /V1/pos/register-reports Store the closing of a register session, schema version 1
GET /V1/pos/register-reports List closings via searchCriteria, or the closings changed after a revision
GET /V1/pos/register-reports/:reportId One closing with its document and annotations
ACL resource Routes
Hyva_Pos::register_reports_submit POST. Granted on upgrade to every role and integration that has Hyva_Pos::receipts
Hyva_Pos::register_reports Both GET routes, the Register Reports listing and Register Totals in the admin
Hyva_Pos::register_reports_annotate Adding an annotation in the admin; there is no annotation route

Submit a Closing

POST /V1/pos/register-reports

Called by the register right after the close, and again on later sync passes while Magento has not taken the closing. The body wraps the closing as report. Magento stores the document byte for byte as received and copies the indexed fields into columns.

{
  "report": {
    "schema_version": 1,
    "report_id": "2F8D1C8E-5B1A-4F0E-9C3D-7A6B5C4D3E2F",
    "session_id": "0B3C9A7E-1D2F-4E5A-8B6C-9D0E1F2A3B4C",
    "terminal_id": 3,
    "location_id": 1,
    "terminal_name": "Front Desk 01",
    "location_name": "Amsterdam Store",
    "device_identifier": "A1B2C3D4-…",
    "series_id": "C4A1F0E9-…",
    "series_code": "C4A1",
    "closing_number": 41,
    "currency": "EUR",
    "timezone": "Europe/Amsterdam",
    "business_date": "2026-10-12",
    "opened_at": "2026-10-12T08:02:11+02:00",
    "closed_at": "2026-10-12T18:31:40+02:00",
    "opened_by": { "local_user_id": "…", "name": "Anna", "staff_id": 7 },
    "closed_by": { "local_user_id": "…", "name": "Anna", "staff_id": 7 },
    "cash_drawer": true,
    "opening_float": "150.00",
    "cash_expected": "612.35",
    "cash_counted": "612.00",
    "cash_difference": "-0.35",
    "cash_left_in_drawer": "150.00",
    "order_count": 23,
    "sales": { "excl_tax": "1810.45", "tax": "380.19", "incl_tax": "2190.64" },
    "refunds": { "excl_tax": "40.50", "tax": "8.51", "incl_tax": "49.01" },
    "tenders": { "cash": "462.35", "card": "1600.00", "split_cash": "0.00", "split_card": "0.00", "invoice": "128.29", "...": "…" },
    "tax_rates": [ { "rate": "21.00", "treatment": "standard", "sales_net": "1810.45", "sales_tax": "380.19", "refund_net": "40.50", "refund_tax": "8.51" } ],
    "tax_treatments": { "reverse_charge": { "sales_net": "0.00", "refund_net": "0.00" }, "exempt": { "sales_net": "0.00", "refund_net": "0.00" } },
    "providers": [ { "provider": "mollie", "name": "Mollie", "collected": "1600.00", "refunded": "0.00", "count": 19 } ],
    "movements": [ { "type": "cash_out", "amount": "50.00", "note": "Bank run", "at": "2026-10-12T13:10:02+02:00", "by": { "local_user_id": "…", "name": "Anna", "staff_id": 7 } } ],
    "documents": [ { "sale_id": "9A9E4AF3-…", "type": "sale", "amount": "95.00", "receipt_number": "R-000412" } ],
    "first_receipt_number": "R-000390",
    "last_receipt_number": "R-000412",
    "closing_note": ""
  }
}
Field Type Notes
schema_version int 1
report_id UUID the closing's id, the key of the stored row
session_id UUID the register session; one closing per session
terminal_id, location_id int Magento ids at the close
terminal_name, location_name string the names at the close
device_identifier string the device, as on the heartbeat; must be a device the terminal is or was bound to
series_id, series_code, closing_number UUID, string, int the numbering series of the install, its first four characters in upper case, and the number from 1 within it
currency string ISO 4217; every amount is in it
timezone, business_date string, date IANA zone of the location at open; the opening date in that zone
opened_at, closed_at datetime with offset
opened_by, closed_by object local_user_id, name, staff_id (null without a staff account)
cash_drawer bool false: the five drawer amounts are absent
opening_float, cash_expected, cash_counted, cash_difference, cash_left_in_drawer amount only with a drawer
order_count int
sales, refunds object excl_tax, tax, incl_tax
tenders object cash, card, split_cash, split_card, invoice, credit_redeemed, card_refunds, manual_card_refunds, credits_issued, credits_returned, invoice_credit_notes, layaway_receivable, layaway_payments, credit_due_collections, cash_rounding
tax_rates array rate, treatment (standard, unknown), sales_net, sales_tax, refund_net, refund_tax
tax_treatments object reverse_charge and exempt, each sales_net and refund_net
providers array provider, name, collected, refunded, count
movements array type (cash_in, cash_out, cash_refund), amount, note, at, by
documents array sale_id, type, amount, receipt_number; the receipts Magento matches for Documents Received
first_receipt_number, last_receipt_number string display only
closing_note string

Amounts are decimal strings. A closing is stored once: the answer carries the stored row's report_id, its revision and the series_ordinal Magento gave the series on the terminal (1 for the first series of a terminal, 2 for the next).

{ "report_id": "2F8D1C8E-5B1A-4F0E-9C3D-7A6B5C4D3E2F", "revision": 1187, "series_ordinal": 1 }

Sending the same document again answers the stored row with 200. A document that is not a valid schema 1 closing is refused with 400; a terminal that does not exist with 404. A document that collides with a stored one is refused with 409 and parameters.outcome naming the collision:

outcome Meaning
report_payload_differs a different document under a stored report_id
session_already_closed Magento already holds a closing for this session_id
number_taken this closing_number is already stored in this series
device_mismatch the device is not and never was bound to the terminal

The register lists a refused closing under Reports → Sync Issues and retries it from there. A closing is never updated or deleted through the API.

List Closings

GET /V1/pos/register-reports?searchCriteria[...]

Standard searchCriteria over the indexed columns: terminal_id, location_id, business_date, closed_at, currency, series_id, closing_number, the four states, and the amounts. Each item carries the indexed fields below; the document itself (report_json) and the annotations come with the single read.

Field Notes
report_id, session_id, terminal_id, location_id ids
series_id, series_code, series_ordinal, closing_number the numbering
terminal_name, location_name the names at the close
business_date, opened_at, closed_at, timezone, currency
opened_by_staff_id, opened_by_name, closed_by_staff_id, closed_by_name
order_count, has_cash_drawer
sales_excl_tax, sales_tax, sales_incl_tax, refunds_incl_tax decimal strings
cash_expected, cash_counted, cash_difference null without a drawer
tender_cash, tender_card, tender_split_cash, tender_split_card, tender_invoice
state_closing_received, state_documents_received, state_orders_processed, state_figures_reconciled not_evaluated, received, complete, incomplete, matched, mismatched
corrects_report_id reserved; null in this release
revision from one module-wide sequence; see below
synced_at, updated_at when the closing arrived and when it last changed

Reading changes

Every closing carries a revision drawn from one module-wide sequence: once when the closing arrives and again whenever something about it changes later (a state, an annotation). A connector keeps the highest revision it has seen and asks for everything above it:

GET /V1/pos/register-reports?changedAfter=1187&searchCriteria[pageSize]=100

The answer lists the closings whose revision is above the cursor, oldest change first, and highest_revision is the cursor to ask from next. A closing that changed comes back again with its new revision; start from 0 for everything.

{ "items": [ { "report_id": "…", "revision": 1188, "...": "…" } ], "total_count": 1, "highest_revision": 1188 }

Read One Closing

GET /V1/pos/register-reports/:reportId

The indexed fields above, plus report_json (the document exactly as the register sent it) and annotations, oldest first: each with annotation_id, report_id, author, author_id (the admin user, null when the account is gone), created_at and text.

Event

hyva_pos_register_report_saved is dispatched after a closing is stored or changed, once the database transaction has committed, with report_id in the event data. Read the closing through the repository (Hyva\Pos\Api\RegisterReportRepositoryInterface::get) in the observer; an accounting or ERP connector can also poll with changedAfter instead.

Receipts

Each receipt a register from 0.3.0 uploads to a module from 1.4.0 carries register_session_id, the session it was booked into. Documents Received on a closing is complete when Magento holds a receipt for every sale_id the closing lists, and a receipt that arrives after its closing sets the state again. See Receipts.