# B2B v2 Route Inventory

- **Audit date:** 2026-07-17
- **Documentation baseline:** `ab124d9e25df`
- **Backend baseline:** `81686b34e5bc` (`origin/master`)
- **Raw backend surface:** 60 operations
- **Published public surface:** 42 operations


## Classification summary

| Classification | Operations | Published |
|  --- | --- | --- |
| Public | 42 | 42 |
| Internal | 10 | 0 |
| Needs decision | 8 | 0 |
| **Total** | **60** | **42** |


Seven of the `needs decision` operations are the Waiting List group. The
available production Business User receives `403` for both collection reads,
so they remain unpublished until a positive production contract can be
verified with an authorized fixture.

The engineering ratio is therefore **42 of 60**. The approved public
denominator is currently 42, and all 42 approved operations are published.
This does not imply that all 60 registered routes are approved public APIs.

## Evidence legend

- **Auth `P+U+L`:** Partner token, Business User token, and active Location
license checks are required. Controllers may apply additional access checks.
- **Test `I`:** a route-name integration test exists at the backend baseline.
- **Test `N`:** no route-name or controller integration test was found.
- **Live:** positive production behavior was verified during the 2026-07-15
contract audit or the 2026-07-16 canonical URL check.
- **Route:** only route reachability, validation behavior, or a non-positive
response was verified in production.
- **Source:** controller, request, transformer, service, and/or UI consumer was
inspected at the backend baseline.


All canonical routes below are relative to `https://api.alteg.io/api/v2`.

## Published public operations

| Method | Canonical route | Backend route name | Controller | OpenAPI operation ID | Auth | Test | Evidence |
|  --- | --- | --- | --- | --- | --- | --- | --- |
| GET | `/locations/{location_id}/products` | `api_v2.company.goods.list` | `GoodsController::action_list` | `list_products` | P+U+L | I | Live |
| GET | `/locations/{location_id}/products/{product_id}` | `api_v2.company.good.read` | `GoodsController::action_read` | `get_product` | P+U+L | I | Live |
| GET | `/locations/{location_id}/products/{product_id}/storage_amounts` | `api_v2.company.good.storage_amounts.list` | `GoodStorageAmountsController::action_list` | `list_product_storage_amounts` | P+U+L | I | Live |
| GET | `/locations/{location_id}/products/autocomplete` | `api_v2.company.goods.autocomplete` | `GoodsAutocompleteController` | `autocomplete_products` | P+U+L | I | Live |
| GET | `/locations/{location_id}/units` | `api_v2.company.units.list` | `UnitsController::action_list` | `list_product_units` | P+U+L | I | Live |
| GET | `/locations/{location_id}/attendance_product_items/{attendance_product_item_id}` | `api_v2.company.attendance_good_item.read` | `AttendanceGoodItemsController::action_read` | `get_attendance_product_item` | P+U+L | I | Route + Source; no safe positive fixture |
| GET | `/locations/{location_id}/product_categories` | `api_v2.company.good_categories.list` | `GoodCategoriesController::action_list` | `list_product_categories` | P+U+L | I | Live |
| GET | `/locations/{location_id}/product_categories/{product_category_id}` | `api_v2.company.good_category.read` | `GoodCategoriesController::action_read` | `get_product_category` | P+U+L | I | Live |
| GET | `/locations/{location_id}/service_categories` | `api_v2.company.service_categories.list` | `ServiceCategoriesController::action_list` | `list_service_categories` | P+U+L | I | Live |
| GET | `/locations/{location_id}/services/{service_id}` | `api_v2.company.service.read` | `ServiceReadController` | `get_service` | P+U+L | I | Live |
| GET | `/locations/{location_id}/services` | `api_v2.company.services.list` | `ServicesListController` | `list_services` | P+U+L | I | Live |
| GET | `/locations/{location_id}/attendance_services` | `api_v2.company.attendance_services.list` | `AttendanceServicesController::action_list` | `list_attendance_services` | P+U+L | I | Live |
| GET | `/locations/{location_id}/composite_services/{service_id}/attendance_services` | `api_v2.company.composite_service.attendance_services_list` | `CompositeServiceAttendanceServicesListController` | `list_composite_service_attendance_services` | P+U+L | I | Live |
| GET | `/locations/{location_id}/attendance_service_suggestions` | `api_v2.company.attendance_service_suggestions.list` | `AttendanceServiceSuggestionsController::action_list` | `list_attendance_service_suggestions` | P+U+L | N | Live |
| GET | `/locations/{location_id}/resource_occupations` | `api_v2.company.resource_occupations.list` | `ResourceOccupationsListController` | `list_resource_occupations` | P+U+L | I | Live |
| GET | `/locations/{location_id}/custom_fields` | `api_v2.company.custom_fields.list` | `CustomFieldsController::action_list` | `list_custom_fields` | P+U+L | I | Live |
| GET | `/locations/{location_id}/team_members` | `api_v2.company.staff.list` | `StaffController::action_list` | `list_team_members` | P+U+L | I | Live |
| GET | `/locations/{location_id}/appointments` | `api_v2.company.records.list` | `RecordsListController` | `list_appointments` | P+U+L | N | Live |
| GET | `/locations/{location_id}/attendances/{attendance_id}/appointments` | `api_v2.company.attendance.records.list` | `AttendanceRecordsController::action_list` | `list_attendance_appointments` | P+U+L | N | Live with `record_` and `visit_` identifiers |
| DELETE | `/locations/{location_id}/appointments/{appointment_id}` | `api_v2.company.record.delete` | `RecordDeleteController` | `delete_appointment` | P+U+L | I | Route + Source |
| POST | `/locations/{location_id}/timetable/appointments/delete` | `api_v2.company.timetable.record.delete` | `TimetableRecordDeleteController` | `delete_timetable_appointment` | P+U+L | N | Route + Source |
| GET | `/locations/{location_id}/attendance/clients/{client_id}` | `api_v2.company.attendance.client.read` | `AttendanceClientsController::action_read` | `get_attendance_booking_user` | P+U+L | N | Live |
| GET | `/locations/{location_id}/clients/{client_id}/attendances_statistic` | `api_v2.company.client.attendances_statistic.read` | `SalonAttendancesStatisticController` | `get_booking_user_location_attendance_statistics` | P+U+L | I | Live |
| GET | `/locations/{location_id}/clients/{client_id}/chain_attendances_statistic/{chain_id}` | `api_v2.company.client.chain_attendances_statistic.read` | `ChainAttendancesStatisticController` | `get_booking_user_chain_attendance_statistics` | P+U+L | I | Live |
| POST | `/locations/{location_id}/events` | `api_v2.company.activities.create` | `ActivityController::action_create` | `create_event` | P+U+L | I | Live |
| GET | `/locations/{location_id}/events` | `api_v2.company.activities.list` | `ActivityController::action_list` | `list_events` | P+U+L | N | Live |
| GET | `/locations/{location_id}/events/{event_id}` | `api_v2.company.activities.read` | `ActivityController::action_read` | `get_event` | P+U+L | I | Live |
| PUT | `/locations/{location_id}/events/{event_id}` | `api_v2.company.activities.update` | `ActivityController::action_update` | `update_event` | P+U+L | I | Live |
| DELETE | `/locations/{location_id}/events/{event_id}` | `api_v2.company.activities.delete` | `ActivityController::action_delete` | `delete_event` | P+U+L | I | Live |
| POST | `/locations/{location_id}/events/{event_id}/appointments` | `api_v2.company.activities.records.create` | `ActivityRecordCreateController` | `create_event_appointment` | P+U+L | I | Validation + Source |
| PUT | `/locations/{location_id}/events/{event_id}/appointments/{appointment_id}` | `api_v2.company.activities.records.update` | `ActivityRecordUpdateController` | `update_event_appointment` | P+U+L | I | Validation + Source |
| PATCH | `/locations/{location_id}/events/{event_id}/appointments/{appointment_id}` | `api_v2.company.activities.records.reschedule` | `ActivityRecordRescheduleController` | `reschedule_event_appointment` | P+U+L | N | Validation + Source |
| GET | `/locations/{company_id}/tags` | `api_v2.company.tags.list` | `TagsController::action_list` | `list_tags` | P+U+L | I | Live; `/companies` response identical |
| GET | `/locations/{company_id}/tags/{tag_id}` | `api_v2.company.tags.read` | `TagsController::action_read` | `get_tag` | P+U+L | I | Live |
| POST | `/locations/{company_id}/tags` | `api_v2.company.tags.create` | `TagsController::action_create` | `create_tag` | P+U+L | I | Live |
| PUT | `/locations/{company_id}/tags/{tag_id}` | `api_v2.company.tags.update` | `TagsController::action_update` | `update_tag` | P+U+L | I | Live |
| DELETE | `/locations/{company_id}/tags/{tag_id}` | `api_v2.company.tags.delete` | `TagsController::action_delete` | `delete_tag` | P+U+L | I | Live |
| GET | `/locations/{company_id}/positions` | `api_v2.company.positions.list` | `PositionsController::action_list` | `list_positions` | P+U+L | I | Live; `/companies` response identical |
| GET | `/locations/{company_id}/positions/{position_id}` | `api_v2.company.positions.read` | `PositionsController::action_read` | `get_position` | P+U+L | I | Live |
| POST | `/locations/{company_id}/positions` | `api_v2.company.positions.create` | `PositionsController::action_create` | `create_position` | P+U+L | I | Live |
| PUT | `/locations/{company_id}/positions/{position_id}` | `api_v2.company.positions.update` | `PositionsController::action_update` | `update_position` | P+U+L | I | Live |
| DELETE | `/locations/{company_id}/positions/{position_id}` | `api_v2.company.positions.delete` | `PositionsController::action_delete` | `delete_position` | P+U+L | I | Live |


The literal `company_id` parameter remains in Tags and Positions for SDK
compatibility. It identifies a Location. The middleware also accepts the legacy
`/companies/{company_id}` path alias.

## Unpublished operations

| Method | Canonical route | Backend route name | Controller | Known consumer | Test | Classification and evidence |
|  --- | --- | --- | --- | --- | --- | --- |
| GET | `/locations/{location_id}/attendance_services/autocomplete` | `api_v2.company.attendance_services.autocomplete` | `AttendanceServicesAutocompleteController` | `serviceAutocompleteApi` | N | Needs decision; Source; backend marks route for deletion |
| POST | `/locations/{location_id}/attendance/appointments` | `api_v2.company.attendance.records.update` | `AttendanceController` | Backoffice attendance editor | I | Internal; UI-state mutation |
| GET | `/locations/{location_id}/attendance/field_pins` | `api_v2.company.attendance.field_pin.list` | `UserAttendanceFieldPinsController::action_list` | `attendancePinItemsApi` | I | Internal; per-user UI state |
| POST | `/locations/{location_id}/attendance/field_pins` | `api_v2.company.attendance.field_pin.pin` | `UserAttendanceFieldPinsController::action_pin` | `attendancePinItemsApi` | I | Internal; per-user UI state |
| DELETE | `/locations/{location_id}/attendance/field_pins/{slug}` | `api_v2.company.attendance.field_pin.unpin` | `UserAttendanceFieldPinsController::action_unpin` | `attendancePinItemsApi` | I | Internal; per-user UI state |
| PUT | `/locations/{location_id}/attendance/payment_method_pins` | `api_v2.company.attendance.payment_method_pins.update` | `UserAttendancePaymentMethodPinsController::action_update` | `attendancePinPaymentMethodsApi` | I | Internal; per-user UI state |
| GET | `/locations/{location_id}/attendance/payment_method_pins` | `api_v2.company.attendance.payment_method_pins.list` | `UserAttendancePaymentMethodPinsController::action_list` | `attendancePinPaymentMethodsApi` | I | Internal; per-user UI state |
| POST | `/locations/{location_id}/comers/{client_id}/vehicles` | `api_v2.company.comer.vehicles.create` | `ComerInfoVehiclesController::action_create` | `comerApiV2`, client and visit modals | N | Internal; backoffice-specific profile state |
| PATCH | `/locations/{location_id}/comers/{comer_id}/vehicles` | `api_v2.company.comer.vehicles.update` | `ComerInfoVehiclesController::action_update` | `comerApiV2`, client and visit modals | N | Internal; backoffice-specific profile state |
| GET | `/locations/{location_id}/comers/{client_id}/vehicles` | `api_v2.company.comer.vehicles.list` | `ComerInfoVehiclesController::action_list` | `comerApiV2`, client and visit modals | N | Internal; backoffice-specific profile state |
| DELETE | `/locations/{location_id}/comers/{comer_id}/vehicles` | `api_v2.company.comer.vehicles.delete` | `ComerInfoVehiclesController::action_delete` | `comerApiV2`, client and visit modals | N | Internal; backoffice-specific profile state |
| GET | `/locations/{location_id}/waiting_list` | `api_v2.company.waiting_list.search` | `SearchWaitingListEntryController` | Backoffice `WaitingListApi` | I | Needs decision; production `403`; unregistered draft `get_waiting_list_v2` exists |
| GET | `/locations/{location_id}/waiting_list/count` | `api_v2.company.waiting_list.count` | `CountWaitingListEntryController` | Backoffice Waiting List widget | I | Needs decision; production `403`; no OpenAPI draft |
| POST | `/locations/{location_id}/waiting_list` | `api_v2.company.waiting_list.create` | `CreateWaitingListEntryController` | Backoffice `WaitingListApi` | I | Needs decision; unsafe without controlled Booking User; unregistered draft exists |
| GET | `/locations/{location_id}/waiting_list/{waiting_list_entry_id}` | `api_v2.company.waiting_list.read` | `ReadWaitingListEntryController` | Backoffice Waiting List editor | I | Needs decision; no safe positive fixture |
| PUT | `/locations/{location_id}/waiting_list/{waiting_list_entry_id}` | `api_v2.company.waiting_list.update` | `UpdateWaitingListEntryController` | Backoffice `WaitingListApi` | I | Needs decision; unsafe without controlled Booking User; unregistered draft exists |
| POST | `/locations/{location_id}/waiting_list/{waiting_list_entry_id}/finish` | `api_v2.company.waiting_list.finish` | `FinishWaitingListEntryController` | Backoffice `WaitingListApi` | I | Needs decision; notification/workflow safety unverified |
| DELETE | `/locations/{location_id}/waiting_list/{waiting_list_entry_id}` | `api_v2.company.waiting_list.delete` | `DeleteWaitingListEntryController` | Backoffice `WaitingListApi` | I | Needs decision; cleanup route source-verified; unregistered draft exists |


## Production checks performed in this refresh

- Authenticated Tags list: canonical `/locations` and compatibility `/companies`
both returned `200` with identical response bodies.
- Authenticated Positions list: canonical `/locations` and compatibility
`/companies` both returned `200` with identical response bodies.
- Waiting List list and count: the only managed Location returned `403` for
both operations. No Waiting List write was attempted.
- Attendance Appointments: positive `200` collection responses were verified
with both `record_{appointment_id}` and `visit_{visit_id}` identifiers. An
invalid identifier returned `422`, and a missing attendance group returned
`404`.
- Attendance Booking User: positive `200` item responses were verified for
several Booking User profiles without retaining personal data. The base
response returned the `client` resource type and an empty `meta` array.
- Product Units returned `200` with 38 JSON:API `unit` resources.
- Attendance Services returned `200`; a composite Service fixture was then
used to verify its component Attendance Services with another `200`.
- Resource Occupations, Attendance Service Suggestions, and Appointment Custom
Fields returned `200` with deterministic empty collections for safe fixtures.
- Booking User Location and Chain attendance statistics returned `200` item
responses with the expected JSON:API resource types and deposit relationship.
- No production data was created, updated, deleted, or included in this file.


## Next decision gate

Waiting List coverage requires a Business User and Location with positive read
access. Publishing list and count then requires observed `200` shapes and a
passing read-only Arazzo workflow. Its remaining five operations additionally
require a controlled Booking User, notification safety, and guaranteed cleanup.

The other remaining read candidate requires a decision on the autocomplete
route that the backend marks for deletion.