Statistics (Statistieken) ========================= Current version: 1.0.0 The statistics endpoints return **aggregated** figures over a period of time: totals per time bucket and, optionally, per grouping dimension (per product, per payment method, per company, and so on). They are the data source behind the statistics screens in Recras and are well suited to reporting, dashboards and periodic revenue or payment summaries. They are **not** a per-transaction feed. There is no grouping by an individual booking or invoice; every value in a response is a sum over the matching rows. If you need per-sale detail -- which products were sold, at which price, and how each sale was paid -- use the transactional endpoints instead (see `Choosing the right endpoint`_). .. warning:: :class: disclaimer Recras is an administration system, not a tax or accounting authority. These endpoints report *how much money moved through Recras and for which bookings*; they do not make fiscal determinations. Recras does not decide what counts as revenue for your business, whether you fall under a small-business scheme (in the Netherlands, the ``KOR``), whether an organisation is a charity (``ANBI``), or how amounts should be recognised for tax. Those settings are the customer's responsibility: Recras assumes they are configured correctly and reports accordingly. Always reconcile exported figures against your own accounting rules. Choosing the right endpoint --------------------------- For **aggregated** totals -- revenue, payments or quantities summed per period -- use the statistics endpoints on this page. .. note:: **Exporting individual sales to a POS or accounting system?** The statistics endpoints cannot produce a per-sale dataset. Combine the transactional endpoints instead: - :doc:`boekingen` -- the bookings themselves. - :doc:`factuurregels` -- the invoiced lines: which products were sold and at which price. - :doc:`facturen` -- the invoices the lines belong to. - :doc:`facturen_betalingen` -- the payments: the amount (``bedrag``) and the payment method (``betaalmethode_id``) of each individual payment. The aggregated counterparts, when you only need totals, are :http:get:`/api2/statistieken/verkoop` and :http:get:`/api2/omzet` for invoiced revenue, and :http:get:`/api2/statistieken/betalingen` for payments received. Note the difference between *invoiced* and *paid* amounts: :http:get:`/api2/statistieken/verkoop` and :http:get:`/api2/omzet` report amounts that have been **invoiced** (from sent, partially paid and paid invoices), which is not necessarily what has been received. :http:get:`/api2/statistieken/betalingen` reports the amounts that were actually **paid**. For cash-flow reconciliation, use the payment figures. Mapping to a general ledger --------------------------- To map money flows onto the accounts in your bookkeeping, Recras can store a **ledger number** (`grootboeknummer`) and, for product groups, a **cost center** (`kostenplaats`). These are read from the regular resource endpoints, not from the statistics endpoints: - :doc:`betaalmethoden` -- ``ledger_number`` per payment method (the payment side). - :doc:`productgroepen` -- ``ledger_number`` and ``cost_center`` per product group (the revenue side). Recras only stores and returns these values as configured by the customer; it does not validate them against any chart of accounts. Common request format --------------------- The conventions in this section apply to the ``/api2/statistieken/*`` endpoints. :http:get:`/api2/omzet` and :http:get:`/api2/vouchers/overview` use their own parameters and response shape and are documented with each. Response format ~~~~~~~~~~~~~~~~ Each ``/api2/statistieken/*`` endpoint returns a JSON array of objects with two members: - ``key`` -- the dimensions the row is grouped by: the time bucket (``period``) and any values from ``groepeer_gegevens``. - ``data`` -- the aggregated metric(s) for that combination of dimensions. .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "key": { "period": "2026-01", "product_id": 42 }, "data": { "gefactureerd_bedrag_inclusief_btw": 1210.00, "gefactureerd_bedrag_exclusief_btw": 1000.00 } } ] Grouping ~~~~~~~~ ``groepeer_tijd`` selects the length of the time bucket and determines the format of ``period`` in each ``key``: - ``jaar`` -- calendar year, e.g. ``"2026"``. - ``maand`` -- month number 1-12, e.g. ``"6"``. - ``week`` -- ISO week number, e.g. ``"22"``. - ``jaar-maand`` -- year and month, e.g. ``"2026-06"``. - ``jaar-week`` -- ISO year and week, e.g. ``"2026-W09"``. - ``datum`` -- a single date, e.g. ``"2026-06-01"``. ``groepeer_gegevens`` adds one or more extra dimensions to the ``key`` (for example ``product_id`` or ``betaalmethode_id``). It is a comma-separated list; the accepted values differ per endpoint. Date range ~~~~~~~~~~ The period is bounded by two parameters whose names literally contain a ``>`` and a ``<`` character: - ``datum>`` -- start of the period (inclusive), an ISO8601 date. - ``datum<`` -- end of the period (inclusive), an ISO8601 date. For example ``?datum>=2026-01-01&datum<=2026-12-31``. ``datum<`` must not be earlier than ``datum>``. Remember to URL-encode the parameter names where your HTTP client requires it (``datum%3E`` and ``datum%3C``). Default period and redirects ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If required parameters are omitted, the endpoint responds with a ``302`` redirect to the same URL with sensible defaults filled in (the date range defaults to the current calendar year, plus per-endpoint defaults noted below). Standard HTTP clients follow this redirect automatically. Supply all parameters yourself to avoid the extra round trip. Errors ~~~~~~ On a validation error the endpoint responds with ``406 Not Acceptable`` and a body listing the problems. Each entry has a ``message`` and, for a field-specific error, the ``field`` it applies to: .. sourcecode:: http HTTP/1.1 406 Not Acceptable Content-Type: application/json [ { "field": "datum<", "message": "\"datum<\" must be after \"datum>\"" } ] There is no pagination; every request returns the full result set. Sales -- invoiced revenue (verkoop) ----------------------------------- .. http:get:: /api2/statistieken/verkoop Invoiced revenue and booking figures, aggregated per period and optionally per dimension. Amounts are taken from sent, partially paid and paid invoices. **Example request**: .. sourcecode:: http GET /api2/statistieken/verkoop?referentiedatum=boeking&datum>=2026-01-01&datum<=2026-12-31&gegevens=gefactureerd_bedrag&groepeer_tijd=jaar-maand&groepeer_gegevens=product_id HTTP/1.1 Host: demo.recras.nl Authorization: Bearer Accept: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "key": { "period": "2026-01", "product_id": 42 }, "data": { "gefactureerd_bedrag_inclusief_btw": 1210.00, // (invoiced_amount_including_vat) "gefactureerd_bedrag_exclusief_btw": 1000.00 // (invoiced_amount_excluding_vat) } } ] :query referentiedatum: **Required.** Which date the rows are attributed to: ``boeking`` (booking date), ``factuur`` (invoice date), ``boeking_gemaakt`` (date the booking was created) or ``boekingsregel`` (per booking-line date). :query date datum>: **Required.** Start of the period (inclusive). The ``>`` is part of the name. :query date datum<: **Required.** End of the period (inclusive). The ``<`` is part of the name. :query gegevens: **Required.** The metric to return: ``gefactureerd_bedrag`` (invoiced amount), ``gefactureerd_bedrag_per_persoon`` (invoiced amount per person), ``nog_te_factureren`` (still to be invoiced), ``boekingswaarde`` (booking value), ``personen`` (number of people) or ``boekingen`` (number of bookings). :query groepeer_tijd: Time bucket for ``period`` (see `Grouping`_). At least one of ``groepeer_tijd`` or ``groepeer_gegevens`` is required. :query groepeer_gegevens: Comma-separated extra dimensions: ``arrangement_id``, ``bedrijf_id`` (company), ``soort_klant`` (customer type), ``product_id``, ``productgroep_id``, ``created_by``, ``ISO3166-1`` (country), ``ISO3166-2`` (region) or ``btw_percentage`` (VAT rate). :query bedrijf_id: *Optional filter.* Comma-separated company IDs. :query soort_klant: *Optional filter.* Comma-separated customer types. :query arrangement_id: *Optional filter.* Comma-separated package (`arrangement`) IDs. :query book_process_id: *Optional filter.* Comma-separated book-process IDs. :query boeking_status: *Optional filter.* Comma-separated booking statuses: ``informatie``, ``interesse``, ``optie``, ``reservering``, ``definitief``, ``gaande``, ``voltooid``, ``geannuleerd``. :query landcode: *Optional filter.* An ISO3166-1 country code. :query invoice_contexts: *Optional filter.* Comma-separated invoice contexts: ``invoice``, ``pos``, ``online_booking``. Requires ``gegevens=gefactureerd_bedrag``. :query product_id: *Optional filter.* Comma-separated product IDs (only combinable with ``groepeer_gegevens=product_id``). :query contains_product_id: *Optional filter.* Only bookings containing one of these product IDs. :query contains_productgroup_id: *Optional filter.* Only bookings containing a product from one of these product groups. :query contains_productlabel_id: *Optional filter.* Only bookings containing a product with one of these labels. :query boeking_created_by: *Optional filter.* Contact ID of the booking's creator. :query boeking_los_op_planning: *Optional filter.* Boolean; filters on whether products are planned loosely. :query include_voucher_sales: *Optional filter.* Boolean; whether to include voucher sales. :query locatie: *Optional filter.* Location filter. :query invoicing_setting: *Optional filter.* Boolean; the booking's "should be invoiced" setting. :json number gefactureerd_bedrag_inclusief_btw: For ``gegevens=gefactureerd_bedrag``: invoiced amount including VAT. ``gefactureerd_bedrag_exclusief_btw`` gives the amount excluding VAT. :json number gefactureerd_bedrag_per_persoon_inclusief_btw: For ``gegevens=gefactureerd_bedrag_per_persoon``: invoiced amount per person including VAT (plus ``..._exclusief_btw``). :json number nog_te_factureren_inclusief_btw: For ``gegevens=nog_te_factureren``: amount still to be invoiced including VAT (plus ``..._exclusief_btw``). :json number boekingswaarde_inclusief_btw: For ``gegevens=boekingswaarde``: booking value including VAT (plus ``..._exclusief_btw``). :json number personen: For ``gegevens=personen``: number of people. :json number boekingen: For ``gegevens=boekingen``: number of bookings. :reqheader Authorization: **Required.** Bearer token with your API key, see :ref:`authentication_chapter`. :statuscode 200: no error :statuscode 302: required parameters were missing; follow the redirect (defaults ``gegevens=gefactureerd_bedrag`` and ``referentiedatum=boeking``). :statuscode 401: not authenticated. :statuscode 403: user does not have both the ``viewStatistiek`` and ``viewFinancieleOverzichten`` permissions. :statuscode 406: error in the input. Some combinations are rejected, for example ``referentiedatum=factuur`` requires ``gegevens=gefactureerd_bedrag``; grouping by ``product_id``, ``productgroep_id`` or ``btw_percentage`` requires ``gegevens=gefactureerd_bedrag`` or ``boekingswaarde``. Revenue per product (omzet) --------------------------- .. http:get:: /api2/omzet Invoiced revenue for a date range, broken down per product and VAT rate. Unlike the ``/api2/statistieken/*`` endpoints, this returns a flat list (no ``key``/``data`` envelope) and uses ``begin``/``eind`` for the date range. **Example request**: .. sourcecode:: http GET /api2/omzet?begin=2026-01-01&eind=2026-12-31 HTTP/1.1 Host: demo.recras.nl Authorization: Bearer Accept: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "product_id": 42, "btw_percentage": 21, // (vat_percentage) "product_naam": "Kanovaren", // (product_name: "Canoeing") "productgroep_id": 3, // (product_group_id) "productgroep_naam": "Buitensport", // (product_group_name: "Outdoor sports") "bedrag_exc_btw": 1000.00, // (amount_excluding_vat) "bedrag_inc_btw": 1210.00 // (amount_including_vat) } ] :query date begin: **Required.** Start of the date range, an ISO8601 date. :query date eind: **Required.** End of the date range, an ISO8601 date. Must not be earlier than ``begin``. :json int product_id: The product, or ``null`` for revenue not tied to a product. :json number btw_percentage: The VAT percentage of this row. :json string product_naam: The product name, or ``null``. :json int productgroep_id: The product group, or ``null``. :json string productgroep_naam: The product group name, or ``null``. :json number bedrag_exc_btw: Invoiced amount excluding VAT. :json number bedrag_inc_btw: Invoiced amount including VAT. :reqheader Authorization: **Required.** Bearer token with your API key, see :ref:`authentication_chapter`. :statuscode 200: no error :statuscode 403: user has neither the ``viewFinancieleOverzichten`` nor the ``viewStatistiek`` permission. :statuscode 406: error in the input (for example ``eind`` before ``begin``). Payments received (betalingen) ------------------------------ .. http:get:: /api2/statistieken/betalingen The amounts actually paid, aggregated per period and optionally per payment method. This is the aggregated counterpart of :doc:`facturen_betalingen`. **Example request**: .. sourcecode:: http GET /api2/statistieken/betalingen?datum>=2026-01-01&datum<=2026-12-31&referentiedatum=betaling&gegevens=betaald_bedrag&groepeer_tijd=jaar-maand&groepeer_gegevens=betaalmethode_id HTTP/1.1 Host: demo.recras.nl Authorization: Bearer Accept: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "key": { "period": "2026-01", "betaalmethode_id": 1 }, "data": { "bedrag": 1210.00 } // (amount) } ] :query referentiedatum: **Required.** Only ``betaling`` (the payment date) is accepted. :query date datum>: **Required.** Start of the period (inclusive). :query date datum<: **Required.** End of the period (inclusive). :query gegevens: **Required.** Only ``betaald_bedrag`` (paid amount) is accepted. :query groepeer_tijd: **Required.** Time bucket for ``period`` (see `Grouping`_). :query groepeer_gegevens: *Optional.* Only ``betaalmethode_id`` (payment method) is accepted. :json number bedrag: The total amount paid in this period (and, if grouped, with this payment method). :reqheader Authorization: **Required.** Bearer token with your API key, see :ref:`authentication_chapter`. :statuscode 200: no error :statuscode 302: required parameters were missing; follow the redirect (defaults ``gegevens=betaald_bedrag``, ``referentiedatum=betaling`` and ``groepeer_tijd=jaar-maand``). :statuscode 403: user does not have the ``viewStatistiek`` permission. :statuscode 406: error in the input. Booked product quantities (geboekteproducten) ---------------------------------------------- .. http:get:: /api2/statistieken/geboekteproducten How many of each product were booked, aggregated per period and per product or product group. **Example request**: .. sourcecode:: http GET /api2/statistieken/geboekteproducten?datum>=2026-01-01&datum<=2026-12-31&groepeer_tijd=jaar-maand&groepeer_gegevens=product_id HTTP/1.1 Host: demo.recras.nl Authorization: Bearer Accept: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "key": { "period": "2026-01", "product_id": 42 }, "data": { "aantal": 87 } // (quantity) } ] :query date datum>: **Required.** Start of the period (inclusive). :query date datum<: **Required.** End of the period (inclusive). :query groepeer_tijd: **Required.** Time bucket for ``period`` (see `Grouping`_). :query groepeer_gegevens: **Required.** ``product_id`` or ``productgroep_id``. :query bedrijf_id: *Optional filter.* Comma-separated company IDs. :query soort_klant: *Optional filter.* Customer type. :query boeking_status: *Optional filter.* Comma-separated booking statuses. :query product_id: *Optional filter.* Comma-separated product IDs. :query productgroep_id: *Optional filter.* Comma-separated product-group IDs. :query invoicing_setting: *Optional filter.* Boolean; the booking's "should be invoiced" setting. :json number aantal: The total quantity booked. :reqheader Authorization: **Required.** Bearer token with your API key, see :ref:`authentication_chapter`. :statuscode 200: no error :statuscode 302: required parameters were missing; follow the redirect (defaults ``groepeer_gegevens=product_id`` and ``groepeer_tijd=jaar-maand``). :statuscode 403: user does not have the ``viewStatistiek`` permission. :statuscode 406: error in the input. Purchasing costs (inkoop) ------------------------- .. http:get:: /api2/statistieken/inkoop The purchasing cost (cost of goods) of booked products, aggregated per period and optionally per dimension. **Example request**: .. sourcecode:: http GET /api2/statistieken/inkoop?datum>=2026-01-01&datum<=2026-12-31&groepeer_tijd=jaar-maand&groepeer_gegevens=productgroep_id HTTP/1.1 Host: demo.recras.nl Authorization: Bearer Accept: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "key": { "period": "2026-01", "productgroep_id": 3 }, "data": { "inkoop": 320.50 } // (purchasing_cost) } ] :query date datum>: **Required.** Start of the period (inclusive). :query date datum<: **Required.** End of the period (inclusive). :query groepeer_tijd: **Required.** Time bucket for ``period`` (see `Grouping`_). :query groepeer_gegevens: *Optional.* ``product_id``, ``productgroep_id``, ``arrangement_id``, ``bedrijf_id`` or ``soort_klant``. :query soort_klant: *Optional filter.* Customer type. :query boeking_status: *Optional filter.* Comma-separated booking statuses. :query bedrijf_id: *Optional filter.* Comma-separated company IDs. :query invoicing_setting: *Optional filter.* Boolean; the booking's "should be invoiced" setting. :json number inkoop: The total purchasing cost. :reqheader Authorization: **Required.** Bearer token with your API key, see :ref:`authentication_chapter`. :statuscode 200: no error :statuscode 302: required parameters were missing; follow the redirect (default ``groepeer_tijd=jaar-maand``). :statuscode 403: user does not have the ``viewStatistiek`` permission. :statuscode 406: error in the input. Bookings overview (bookingsoverview) ------------------------------------ .. http:get:: /api2/statistieken/bookingsoverview The number of bookings or people, aggregated per period and optionally per dimension. This is the ``bookingen``/``personen`` subset of :http:get:`/api2/statistieken/verkoop`, available with a lighter permission. **Example request**: .. sourcecode:: http GET /api2/statistieken/bookingsoverview?datum>=2026-01-01&datum<=2026-12-31&referentiedatum=boeking&gegevens=boekingen&groepeer_tijd=datum HTTP/1.1 Host: demo.recras.nl Authorization: Bearer Accept: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "key": { "period": "2026-01-15" }, "data": { "boekingen": 4 } // (bookings) } ] :query referentiedatum: **Required.** ``boeking``, ``boeking_gemaakt`` or ``boekingsregel``. :query date datum>: **Required.** Start of the period (inclusive). :query date datum<: **Required.** End of the period (inclusive). :query gegevens: **Required.** ``boekingen`` (number of bookings) or ``personen`` (number of people). :query groepeer_tijd: Time bucket for ``period`` (see `Grouping`_). At least one of ``groepeer_tijd`` or ``groepeer_gegevens`` is required. :query groepeer_gegevens: *Optional.* Same dimensions as :http:get:`/api2/statistieken/verkoop`. :json number boekingen: For ``gegevens=boekingen``: number of bookings. :json number personen: For ``gegevens=personen``: number of people. :reqheader Authorization: **Required.** Bearer token with your API key, see :ref:`authentication_chapter`. :statuscode 200: no error :statuscode 302: required parameters were missing; follow the redirect (defaults ``gegevens=boekingen``, ``groepeer_tijd=datum`` and ``referentiedatum=boeking``). :statuscode 403: user does not have the ``zoekBoekingenOpDatum`` permission. :statuscode 406: error in the input. Staff costs (personeelskosten) ------------------------------ .. http:get:: /api2/statistieken/personeelskosten Planned and recorded staff hours and their cost, aggregated per period. Only confirmed shifts (`bevestigd`) are counted. **Example request**: .. sourcecode:: http GET /api2/statistieken/personeelskosten?datum>=2026-01-01&datum<=2026-12-31&groepeer_tijd=jaar-maand HTTP/1.1 Host: demo.recras.nl Authorization: Bearer Accept: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json [ { "key": { "period": "2026-01" }, "data": { "personeelskosten": 4200.00, // (staff_costs, from planned hours) "personeelskosten_geregistreerde_uren": 4050.00, // (staff_costs, from recorded hours) "geplande_uren": 240, // (planned_hours) "geregistreerde_uren": 231.5, // (recorded_hours) "geregistreerde_pauze": 12 // (recorded_break, hours) } } ] :query date datum>: **Required.** Start of the period (inclusive). :query date datum<: **Required.** End of the period (inclusive). :query groepeer_tijd: **Required.** Time bucket for ``period`` (see `Grouping`_). :query groepeer_gegevens: *Optional.* ``soort_dienst`` (shift type: ``boeking``, ``drukte``, ``locatie`` or ``los``) or ``locatie_id`` (location). :json number personeelskosten: Staff cost based on the planned hours. :json number personeelskosten_geregistreerde_uren: Staff cost based on the recorded hours (minus recorded breaks). :json number geplande_uren: Planned hours. :json number geregistreerde_uren: Recorded hours. :json number geregistreerde_pauze: Recorded break time, in hours. :reqheader Authorization: **Required.** Bearer token with your API key, see :ref:`authentication_chapter`. :statuscode 200: no error :statuscode 302: required parameters were missing; follow the redirect (default ``groepeer_tijd=jaar-maand``). :statuscode 403: user does not have the ``viewStatistiek`` permission. :statuscode 406: error in the input. Voucher overview (vouchers/overview) ------------------------------------ .. http:get:: /api2/vouchers/overview Vouchers created, redeemed and expired in a date range. Unlike the ``/api2/statistieken/*`` endpoints this returns a single object with three lists and uses ``date>``/``date<`` for the date range. Amounts are in the instance's currency; ``type`` is ``PRODUCT`` or ``CREDIT``. **Example request**: .. sourcecode:: http GET /api2/vouchers/overview?date>=2026-01-01&date<=2026-12-31 HTTP/1.1 Host: demo.recras.nl Authorization: Bearer Accept: application/json **Example response**: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "created": [ { "internal_reference": "KO-2026-0001", "number_of_vouchers": 5, "product_amount": 5, "credit": 0, "type": "PRODUCT", "sale_price": 250.00, "sale_tax_percentage": 21, "company_id": 1 } ], "redeemed": [ { "internal_reference": "KO-2026-0001", "product_id": 42, "product_name": "Kanovaren", "type": "PRODUCT", "number_of_vouchers": 2, "redeemed_product_amount": 2, "redeemed_credit": 0, "product_value": 100.00, "product_tax_percentage": 21, "sale_price_credit": 0, "sale_price": 100.00, "sale_tax_percentage": 21, "company_id": 1 } ], "expired": [ { "internal_reference": "KO-2025-0009", "number_of_vouchers": 1, "expired_credit": 25.00, "expired_product_amount": 0, "type": "CREDIT", "sale_price_credit": 25.00, "sale_price": 25.00, "sale_tax_percentage": 21, "company_id": 1 } ] } :query date date>: **Required.** Start of the date range (inclusive). The ``>`` is part of the name. :query date date<: **Required.** End of the date range (inclusive). The ``<`` is part of the name. Must not be earlier than ``date>``. :query int company_id: *Optional filter.* Restrict to a single company. :json array created: Vouchers created in the range, grouped by reference, VAT rate and company. Fields: ``internal_reference``, ``number_of_vouchers``, ``product_amount``, ``credit``, ``type``, ``sale_price``, ``sale_tax_percentage``, ``company_id``. :json array redeemed: Vouchers redeemed in the range. Fields include ``internal_reference``, ``product_id``, ``product_name``, ``type``, ``number_of_vouchers``, ``redeemed_product_amount``, ``redeemed_credit``, ``product_value``, ``product_tax_percentage``, ``sale_price_credit``, ``sale_price``, ``sale_tax_percentage``, ``company_id``. :json array expired: Vouchers that expired in the range. Fields: ``internal_reference``, ``number_of_vouchers``, ``expired_credit``, ``expired_product_amount``, ``type``, ``sale_price_credit``, ``sale_price``, ``sale_tax_percentage``, ``company_id``. :reqheader Authorization: **Required.** Bearer token with your API key, see :ref:`authentication_chapter`. :statuscode 200: no error :statuscode 403: user does not have the ``viewStatistiek`` permission. :statuscode 405: method not allowed (only ``GET`` is supported). :statuscode 406: error in the input.