Skip to content

Change Log

Below you will find patch notes for the API grouped by date.

10/13/2026

  • GET /role-rights - You can now filter by role and by right. roleIdentifiers takes role ids or names and rightIds takes right ids, in any combination with the existing roleRightIds, so one request answers which rights a given role was granted view, edit or execute on. A role you cannot access is silently excluded, as with every other read filter.
  • GET /reports/transactions - referenceNumber2 is now always an empty string. The gateway's second reference is withheld from the API for security. The field stays in creditInformation so the response shape is unchanged; a consumer that read it gets "".

09/29/2026

  • A request that runs too long now returns 408 REQUEST_TIMEOUT instead of a gateway error with no body. Every request runs under a time budget of roughly 25 seconds. Past it the API cancels the work in flight, rolls back anything half-written, and returns 408 with the exact budget in the message. The same request previously ran until API Gateway's own limit and came back as a 502 you could not act on. A 408 is never a partial success and never a partial write, and it is deterministic against the request shape, so retrying the same body fails the same way. Narrow the filter, shorten the date range, or lower the size header. The Concepts page has the full guidance under Request Time Budget.
  • POST /authenticate - You can now authenticate with a refresh_token in place of a username and password. Send one or the other: a body carrying both, or one that is not a JSON object, returns 422 VALIDATION_ERROR. A refresh token is obtained once through the Vendor Client site and reused on this route, and it is the only option for accounts whose sign-in is federated to a third-party identity provider, since those accounts have no password to send. A username that matches no account now returns 403 INVALID_LOGIN instead of 500, the same response as a wrong password.

09/22/2026

  • GET /alerts, GET /alerts/metadata - A page can now hold 10,000 rows, up from 1,000, so the same result set takes a tenth as many requests. The page size you get when you send no size header is the maximum, so a caller sending no header jumps from 1,000 rows a page to 10,000. A full page of alerts is roughly 4.3 MB of JSON at worst: size your buffers and client timeouts accordingly.
  • GET /alerts/metadata - total-pages is now counted separately from the page it arrives with. This endpoint reads a store that is continuously receiving new alerts, and the page and the count are now two reads rather than one, so on a busy result set total-pages can reflect a moment a fraction of a second apart from the rows beside it. The rows themselves are unaffected, and total-pages already moved between requests as new alerts arrived. In exchange the endpoint is substantially lighter on wide windows.
  • GET /alerts/metadata - If you send no dates, you now get the last seven days. This request used to ignore dates entirely and return every metadata entry you have access to, however old. It now returns metadata for alerts raised in the last seven days, which is what you already got when you sent only one of the two dates. To cover a different period, send raisedAtStart and raisedAtEnd.
  • GET /alerts - machineProse now returns null where it previously returned blank padding. Alerts raised by a machine with no prose set carry null; some previously carried fifteen space characters, an artifact of the old column's fixed width. This affects about 5% of alert rows, almost all of them on deleted machines. No alert's value changed, only the way "no prose" is represented. A caller treating a run of spaces as meaningful text should trim or null-check.

09/10/2026

  • GET /alerts, GET /alerts/metadata - The maximum page returns to 1,000 rows and the maximum date window to 7 days, reversing the increase to 10,000 and 365 published on 09/08/2026. Neither limit rejects a request. A size header above 1,000 is clamped down to 1,000, and a range wider than 7 days is clamped as it was before, with the response headers reporting the window actually used. The page size you get when you send no size header is the maximum, so a caller relying on the default drops from 10,000 rows a page to 1,000 and will page more times through the same result set. The default date window is unchanged at 7 days.
  • The Bulk Extraction section has been withdrawn from the concepts page. The approach it described depended on the larger page size.

09/08/2026

  • GET /alerts, GET /alerts/metadata - The maximum page is now 10,000 rows and the maximum date window 365 days, up from 1,000 and 7. These reads are served from a store built for this shape, which is what removed the limit. The default window is unchanged at 7 days: send neither bound of a range, or only one, and we still fill in the missing date using 7 days, so nothing changes for a caller who sends no dates. A full page of 10,000 alerts is roughly 4.3 MB of JSON at worst, so size client buffers and timeouts accordingly, and read Bulk Extraction in the concepts page before pulling alerts together with their metadata.
  • GET /alerts - machineProse now returns null where it previously returned blank padding. Alerts raised by a machine with no prose set carry null; some previously carried fifteen space characters, an artifact of the old column's fixed width. This affects about 5% of alert rows, almost all of them on deleted machines. No alert's value changed, only the way "no prose" is represented. A caller treating a run of spaces as meaningful text should trim or null-check.
  • Send Accept-Encoding: gzip and the gateway will compress the response. Compression is per request and only happens if you ask for it in that header. Most HTTP clients and every browser send it by default, so check yours rather than assume. Responses of 1,024 bytes or more are compressed and paged lists come back roughly 70 to 90% smaller; anything smaller, error bodies included, is returned uncompressed.
  • Browser clients can now read the API's custom response headers. Access-Control-Expose-Headers is sent on every response, so request-id, x-vn-session-identifier, the paging headers and the date-range echo headers are all readable from cross-origin JavaScript. Previously none of them were, so a browser-based consumer could not page a result set or quote a request id to support.
  • Error responses and 204s now carry the CORS headers and request-id. They were emitted only on a 200 with a body. Additive; nothing was removed from any response.
  • GET /reports/transactions, GET /reports/transactions/metadata, GET /machines/settings/rules, POST /roles/machine-access - An empty result is now 204 rather than 200 with an empty array, matching every other read. This also applies when you request a page past the end of the result set. The rules endpoint is affected only when applyHierarchicalFiltering is true.
  • A write request with an empty or absent body now answers 204 with no body. It previously answered 200 carrying a body, and on a warm connection that body could be the response to the previous request.
  • PUT /machines/settings/rules - A rule scoping to more than one of siteId, accountId, groupId or machineId now returns 422 instead of 400, matching the PATCH endpoint, which always returned 422 for the same violation. A caller pattern-matching on 400 for this case must switch to 422.
  • PUT /template-selections, PATCH /template-selections - Duplicate selection numbers now return 422 SELECTION_NUMBER_CONFLICT naming the clashing numbers, instead of 500. This covers a slot whose template and selection number pair already exists, an edit onto a taken number, and two rows in one batch claiming the same pair. Nothing persisted before and nothing persists now.
  • GET /logs/search - A machine identifier you cannot access is now filtered out instead of rejecting the request. One inaccessible id previously returned 403 and failed the whole call, so one unrecognised id in a list of fifty cost you the other forty-nine. Ids you cannot see are dropped and the rest return their logs; if none are accessible you get 204. The paging headers change with it, having previously counted log entries from machines you cannot access.
  • GET /logs/search - Requesting only organizations outside your access now returns 204 instead of 500.
  • GET /reports/product-sales-stats - requestFilter.organizationIds is now honoured. It was accepted and silently ignored, so every request was scoped to all of your accessible organizations regardless of what you asked for. Naming a subset now returns only that subset, and naming only organizations you cannot access returns 204 rather than 200 carrying your own data.
  • GET /reports/product-sales-stats - Vend counts are now consistent across filter shapes. Machine-scoped and site-scoped queries windowed on the vend's own timestamp while the organization-wide query windowed on the sales batch's reporting date, so an organization total would not reconcile with its own machine breakdown over the same range. All three now window on the batch's reporting date. Counts will shift for machine-scoped and site-scoped queries where a vend sits near a range boundary.
  • GET /reports/transactions/metadata - Filtering by a bare sale id now returns the whole transaction. The endpoint is transaction-grained and previously returned a transaction with its sibling line items pruned away. Filtering by a batch id is unchanged.
  • GET /reports/transactions/metadata - lineItemType returned the machine's time zone, duplicated from the sibling timeZone field. It now returns the line item's actual type.
  • GET /products - container always returned unknown. It now returns the product's real container.
  • PUT /products - unitsForSize was dropped on create and the vend size field was written from the wrong source. Both now persist.
  • PUT /selections - tax was accepted and not persisted. It now persists.
  • PUT /selections - productId now accepts all three documented values. null returned 500 and -1 returned 404; both now create an empty coil, and -1 remains the empty-coil value on read. PATCH /selections accepts an empty coil read back from a GET, which previously returned 404.
  • PUT /selections - An omitted isActive returned 500. It now creates an inactive slot, which is deliberate: a slot created without an explicit state cannot cause an unintended sale.
  • GET /accounts - timeZone always returned null. It now returns the stored zone.
  • GET /machines/models/custom-data-definitions - id returned 0 on every row, so definitions could not be correlated by id. Filtering by definitionIds was unaffected and still is.
  • PATCH /products/variants - Re-submitting a variant's own current description no longer returns a false description_conflict 422. Genuine conflicts against a different row on the same product still return 422.
  • POST /selections/apply-delta - Repeated selectionId values in one body are now summed. Only the first was applied and the rest were silently discarded.
  • All date-filtered reads - Three well-formed requests returned 500 and now return 200: a start date of 9999-12-31, an end date of 0001-01-01, and a maximal date pair.
  • Documentation - The date-range echo headers are documented under the names that actually ship, which use underscores. machineProse is documented as nullable, organizationIds is documented on the product sales stats endpoint, and the 403 DATA_ACCESS_VIOLATION row is removed from the log search endpoint, which no longer returns it.

07/22/2026

  • PUT /alerts - Malformed nested alertMetadata is now rejected with 422 VALIDATION_ERROR naming the offending entry, instead of failing the whole request with an internal error. Previously a missing or over-length key or value passed validation and failed at insert, which rolled back the alert as well and could silently drop the metadata.

07/17/2026

  • POST /templates/apply - Fixed a 500 when applying a template, caused by a missing filter on the operation's sub-steps.

07/14/2026

Authorization is now applied by one framework-enforced path rather than by each endpoint. Two changes are visible to every caller, and both are deliberate.

  • All GET endpoints - A filter you cannot access now narrows the result instead of returning an error. Naming an organization, account, site, machine, product or record that does not exist, or that belongs to another organization, yields an empty result and a 204 rather than a 403 or a 404. This applies to unresolvable machine identifiers too. If your integration treats 403 or 404 on a read as a distinct case, it will now see 204 instead.
  • All write endpoints - Verdicts are now uniform. A referenced record that does not exist returns 404, and one belonging to another organization returns 403 DATA_ACCESS_VIOLATION. Both are returned before anything is written. Previously this varied by endpoint: some returned 500, and some silently did nothing.
  • PATCH endpoints - An organizationId in a request body no longer has any effect on authorization. The record's real owner governs. Updating a record owned outside your organizations now returns 403 rather than silently succeeding.
  • GET /templates - Fixed a cross-tenant leak. Supplying a modelId filter discarded the organization restriction, so the response contained every organization's templates for that model.
  • GET /products - Fixed products being visible to an organization that did not own them.
  • GET /machines/settings, GET /machines/custom-data, GET /machines/models/custom-data-definitions - Fixed deleted machines' records being returned.
  • GET /products/variants - Fixed deleted variants being returned.
  • PUT /alerts, PUT /files - Fixed being able to attach a new record to another organization's machine.
  • PATCH /files - machineId can no longer be changed.
  • PATCH /machines - Fixed being able to move a machine to another organization's site.
  • PUT /selections, PATCH /selections, PUT /template-selections, PATCH /template-selections - A slot's product and variant must now belong to your organization.
  • All write endpoints - Soft-deleted records can now be written to. Applying a template to a deleted machine you own succeeds instead of returning 404. The exception is the deleted flag itself on machines, sites and products, which is now frozen against PATCH so the delete and undelete endpoints remain the only way to change it.
  • All PATCH endpoints - Fields that cannot be changed after creation are now silently preserved rather than accepted and ignored.
  • POST /roles/machine-access - Asking about a machine in another organization returns 200 with an answer of no access, rather than 403.
  • Documentation - 157 corrections across 86 pages: wrong paths, wrong verbs, wrong field names and error codes, and the removal of 403 DATA_ACCESS_VIOLATION from the error tables of read endpoints, which no longer return it.

07/07/2026

  • PUT /alerts - Fixed intermittent failures creating an alert with nested metadata, caused by a database trigger interacting with the bulk insert.

06/23/2026

  • POST /logs/search - New endpoint. Submit a filter (org IDs, machine identifiers, date range, log type) to search Polaris logs and receive metadata plus a presigned S3 download URL per result.

05/29/2026

  • PUT /alerts/metadata - Fixed a bug where Transaction.Commit() was called outside of the try/catch block, allowing a partially-failed batch to be silently committed. Additionally fixed a race condition where two SQL operations on the same transaction were awaited in parallel.
  • PUT /terminals - Fixed a bug where Transaction.Commit() could run after Transaction.Rollback() on error, potentially causing a double-commit exception or silently committing a partial batch. Additionally fixed a missing error response that caused failures to return a silent false success (200) instead of a 500.
  • PUT /templates - Fixed a missing error response in the exception handler that caused failures to return a silent false success (200) instead of a 500.
  • PUT /machines/custom-data - Duplicate custom data now returns a DATA_VALIDATION_ERROR (422) identifying the offending machine ID instead of an unhandled 500.

04/27/2026

  • GET /products/variants, PUT /products/variants, PATCH /products/variants, DELETE /products/variants - Fixed multiple bugs across the product variant routes including incorrect delete behavior, duplicate operations, and missing batch handling. Refactored shared logic into a common base.

04/24/2026

  • PUT /alerts/metadata - Fixed missing JSON serialization decorators that caused incorrect field names in responses for certain metadata fields.
  • PUT /machines/settings, PUT /machines/settings/pc, PUT /machines/settings/rules - Updated queries for compatibility with the Polaris platform.

04/20/2026

  • DELETE /machines/custom-data, PATCH /machines/custom-data, PUT /machines/custom-data - Creating, updating, or deleting machine custom data records now correctly sets the hardware settings change flag on the machine.
  • All routes - Improved handling of malformed requests. Unexpected top-level properties in the request body (outside requestFilter, routeFilter, and body) now return a 400 error rather than being silently ignored or causing unexpected failures.

04/09/2026

  • PUT /alerts - Fixed a bug where alert creation returned incorrect IDs when alerts were created with nested metadata.
  • PUT /alerts/metadata - Requests that reference a non-existent alertId now return an explicit error instead of failing with a generic 500. Requests with an alertId of 0 or less are rejected unless the record is part of a nested alert-creation request.
  • PUT /alerts/metadata - Performance improvements to bulk metadata creation.

03/31/2026

  • POST /machines/undelete - Fixed a bug where soft-deleted machines could not be resolved by machine identifier or PROSE during setup, preventing successful undelete operations.

03/10/2026

  • PATCH /roles - Fixed a table reference typo in the update query that caused all update operations to fail.
  • POST /roles/machine-links - Fixed an incorrect column reference in the SQL query.
  • PUT /products - Fixed an invalid type cast in org-scope validation that caused failures when creating products across multiple organizations.

03/02/2026

  • PUT /products - Fixed a bug where newly created products were hidden from subsequent GET requests when a variantIds filter was also applied.

02/18/2026

  • All routes - Fixed a null reference error that could occur when processing requests with certain organization filter combinations.

02/09/2026

  • PATCH /alerts/metadata, DELETE /alerts/metadata - Fixed a permission check that was incorrectly validating against alert IDs instead of alert metadata IDs, causing valid requests to be rejected with a 403.

01/06/2026

  • GET /alerts - Alert response objects now include local-time equivalents of all timestamp fields, converted to the machine's site timezone.
  • GET /reports/transactions, GET /reports/transactions/metadata, GET /reports/inventory-adjustments - Transaction and inventory adjustment records now include local-time equivalents of all timestamp fields.

12/18/2025

  • PUT /alerts/metadata - Fixed a permissions check that incorrectly blocked valid metadata creation in certain org configurations.

12/01/2025

  • PUT /alerts - Fixed a bug that could cause alert creation to fail when the request included nested alert metadata records.

11/12/2025

  • GET /alerts - Alert response objects now include raisedAtUtcAdjusted and related fields containing the alert time converted to the machine's site timezone.
  • PUT /alerts - Alert creation now accepts an optional timezone identifier to store alongside alert timestamps.
  • GET /reports/transactions, GET /reports/transactions/metadata, GET /reports/inventory-adjustments - Report records now include adjusted timezone fields.

11/11/2025

  • GET /templates - Fixed a bug where the templateGroupIds filter was not applied correctly, returning templates from unrelated groups.

11/05/2025

  • PATCH /selections - New endpoint for updating existing selections.
  • POST /selections/apply-delta - New endpoint for applying an incremental delta to a machine's current selection set.

10/07/2025

  • DELETE /selections - New endpoint for deleting selections.
  • PUT /selections - New endpoint for creating selections.
  • GET /alerts - Added debugOnly filter to return only alerts with the debug flag set.
  • PUT /alerts, PATCH /alerts - Added isDebug flag to alert creation and update.
  • GET /machines/settings/pc, PUT /machines/settings/pc - Endpoints were registered and are now active.

09/04/2025

  • PATCH /sessions/management - Fixed a broken SQL query that caused session update operations to fail.
  • GET /machines/custom-data - Fixed a column reference error caused by a DTO field rename.
  • File metadata fields - Relaxed overly strict required validation on createdAtUtc and site chargeKeyId fields that was rejecting valid existing data.

08/20/2025

  • PUT /sessions/management/events - New endpoint for creating session events.
  • All write endpoints - Added comprehensive input validation. Requests with missing required fields or invalid values now return descriptive 422 errors instead of 500 responses.
  • All routes - Added x-vn-session-identifier header support for session tracking.
  • GET /machines - Fixed current and pending firmware version fields being swapped in the response.
  • GET /products - Fixed the organizationId field not being populated in product responses. Fixed duplicate products being returned when a product has detached variants.
  • GET /alerts - Fixed a server error that occurred when the alertCodes filter was provided.
  • PATCH /alerts/metadata - Fixed a missing model validation call.

05/27/2025

  • PUT /products - New endpoint for creating and updating products.
  • DELETE /products - New endpoint for soft-deleting products.
  • POST /products/undelete - New endpoint for restoring deleted products.
  • GET /products - Updated response model with additional fields. Added includeDeleted filter support.
  • POST /products/variants - New endpoint for reading product variants.
  • PUT /products/variants - New endpoint for creating product variants.
  • PATCH /products/variants - New endpoint for updating product variants.
  • DELETE /products/variants - New endpoint for deleting product variants.

03/20/2025

  • GET /alerts/metadata - Added new filter fields: machineIds, siteIds, accountIds, alertIds, alertCodes, startDate, and endDate, bringing alert metadata filtering in line with alert filtering.
  • GET /machines/settings/rules - Added includeRuleTree filter option. When set to true, the full rule tree is included in the response.
  • GET /accounts - Fixed a SQL error in the accounts query that could return incorrect results when certain filter combinations were used.
  • All routes with date filters - Fixed multiple edge-case bugs in date/time parsing that could cause unexpected failures with certain input formats.
  • GET /sessions - Fixed invalid column references in session filter queries.

03/13/2025

  • GET /machines - Improved error handling when access token validation fails; errors are now returned cleanly rather than propagating as unhandled exceptions.

02/04/2025

  • GET /reports/product-sales-stats - New endpoint for retrieving product vend statistics.
  • GET /alerts/metadata - Performance improvements. Fixed a SQL column error that occurred when the alertIds filter was provided.

10/29/2024

  • GET /selections - Fixed a pagination bug that caused incorrect results when requesting pages beyond the first page.

08/09/2024

  • GET /reports/transactions - Fixed bug preventing return of data when Page > 1. Updated Page Size to be based on number of batches rather than number of line items

07/30/2024

  • All Routes - Add exception handling and an error message when the user sends an unparsable request so they know what happened.
  • PUT sites/ - Add exception handling and return a meaningful error when a site is created with a global charge key.
  • PATCH sites/ - Add exception handling and return a meaningful error when a site is created with a global charge key.

07/13/2024

  • GET /alerts - Fixed a bug that could cause the alertIds filter to be ignored.
  • PUT /alerts - Added support for respecting the alertCode.OnlyOneExists flag. Attempting to create a custom data record where one already exists will return a meaningful error instead of a generic 502 Bad Gateway. All of the alert timezone fields have a new duplicate field for its corresponding time in servertime. The time you send is still treated as UTC and stored as servertime. It is also converted to the machine's site's default timezone and stored as local time.
  • PATCH /alerts - You can now clear alerts that are configured to be UI clearable only.
  • PATCH /alerts - See PUT /alerts for timezone related update information.

  • POST /alerts/clear - You can now clear alerts that are configured to be UI clearable only.

  • POST /alerts/clear - See PUT /alerts for timezone related update information.
  • PUT /machines/custom-data - Attempting to create a custom data record where one already exists will return a meaningful error instead of a generic 502 Bad Gateway.