- Status: Ready for implementation
- Audit snapshot: 2026-07-15
- Scope:
https://api.alteg.io/api/v2only - Primary specification:
docs/en/b2b-v2/openapi.yaml
Make the B2B v2 documentation describe the production API exactly, then make the production checks fail when that contract drifts.
The current v2 specification contains 22 paths and 31 operations. Production routes exist for all of them, but much of the documentation was written before the corresponding API implementation existed. The result is a mixture of real contracts and old mock contracts.
This plan is split into small, independent sessions so a new session can safely take one section, verify it against production, open a focused pull request, and stop without depending on chat history.
Audit and edit B2B v2 only.
V1 may be called only to authenticate a Business User and discover a test Location. Do not change or assess the V1 contract in these sessions.
Every v2 request must send:
Accept: application/vnd.api.v2+json Authorization: Bearer <partner_token>, User <user_token>The required
Acceptheader does not select between two successful response formats. Production returns400without the v2 media type and returns the JSON:API-style representation when the header is valid.Use production behavior as the source of truth. Old examples, old mocks, and endpoint names are not evidence.
Preserve wire names exactly, even when they use legacy terminology.
Use the documentation vocabulary in prose: Location, Team Member, Appointment, Booking User, and Business User.
Do not retain legacy terminology in prose merely because a canonical term or route is not implemented in v2 yet. Show a legacy name only as a code-formatted wire literal, then describe it with the approved vocabulary.
Do not refactor unrelated V1 paths, shared schemas, formatting, or navigation.
Do not commit credentials, tokens, customer data, raw production responses, or identifiable test data.
Read this entire plan and the repository
AGENTS.md.Inspect the current state before editing:
git status --short --branch git fetch origin git log --oneline --decorate -10Preserve any existing working-tree changes. Do not overwrite another session's edits.
Create a new branch directly from the latest remote default branch:
git switch -c docs/v2-<session-scope> origin/masterRead every target path file before changing it. Search for shared schemas and callers before creating new files.
Re-run the relevant live probes. The observations below are a dated snapshot, not permission to skip current verification.
Change only the endpoint group assigned to the session.
Run the required validation, commit, push, and open a focused PR.
The following behavior was verified on Services, Appointments, and Positions:
Request Accept value | Production result |
|---|---|
application/vnd.api.v2+json | Successful JSON:API-style response |
| Omitted | 400 |
*/* | 400 |
application/json | 400 |
application/vnd.api.v1+json | 400 |
The production error message explicitly requires Accept: application/vnd.api.v2+json. A charset parameter is accepted.
The public URL vocabulary is new, while several wire-level JSON:API resource types remain legacy values. Documentation must explain this mapping instead of inventing new wire values.
| Public documentation term | Production path term | Production type |
|---|---|---|
| Service | services | service |
| Product | products | good |
| Product Category | product_categories | good_category |
| Product Storage Amount | storage_amounts | good_storage_amount |
| Team Member | team_members | staff |
| Appointment | appointments | record |
| Event | events | activity |
| Position | positions | position |
| Tag | tags | tag |
IDs in successful resource objects are strings, as expected for JSON:API-style resource identifiers. Do not document them as integers inside the response resource object merely because the path parameter is an integer.
Successful collection responses use resource objects:
{
"data": [
{
"type": "service",
"id": "123",
"attributes": {}
}
]
}Some collections also return pagination metadata. Detail responses commonly add "meta": [].
Production errors consistently use:
{
"success": false,
"data": null,
"meta": {
"message": "...",
"errors": {}
}
}meta.errors is present only for field-level validation errors.
- Positive authenticated GET responses were captured for 17 of 18 GET operations.
- The positive Attendance Product Item response was not captured because the test Location had no safe fixture. Its route and
404behavior were verified. - Full temporary CRUD was completed for Tags, Positions, and Events.
- Event Appointment create/update routes were verified through validation responses only to avoid creating or notifying a Booking User.
- Appointment delete and timetable delete were verified using nonexistent IDs; no existing Appointment was modified.
- Every temporary Tag, Position, and Event was cleaned up after testing.
Target files:
docs/en/paths/services/v2/location.services.list.ymldocs/en/paths/services/v2/location.services.read.ymldocs/en/paths/services/v2/location.service_categories.list.ymldocs/en/schemas/services/v2/service_resource.ymldocs/en/schemas/services/v2/service_category_resource.ymldocs/en/schemas/services/v2/error_response.yml
Known production behavior:
- All success responses are JSON:API-style resources with string IDs.
- Service attributes observed:
title,category_id. - Service Category attributes observed:
title,salon_service_id. limit,page, andfilter[service_ids][]work for the Service list.- Production uses
page; the previously documentedoffsetparameter is ignored. - No pagination
metawas returned by the Service list. - The previously documented
categoryandstaffinclude values have no effect. Production supportsresources,service_composite,resource_links,company_links, andtrial_settings; effective includes add JSON:API relationships and may add top-levelincludedresources. - Service Categories use
filter[staff_id]. Revalidation with a nonexistent Team Member ID changed the collection, and current PHP passes the filter to the category provider.
Target files:
docs/en/paths/products/v2/location.products.list.ymldocs/en/paths/products/v2/location.products.autocomplete.ymldocs/en/paths/products/v2/location.products.read.ymldocs/en/paths/products/v2/location.products.storage_amounts.ymldocs/en/paths/products/v2/location.product_categories.list.ymldocs/en/paths/products/v2/location.product_categories.read.ymldocs/en/paths/products/v2/location.attendance_product_items.read.ymldocs/en/schemas/products/v2/product_resource.ymldocs/en/schemas/products/v2/product_category_resource.ymldocs/en/schemas/products/v2/storage_amount_resource.ymldocs/en/schemas/products/v2/attendance_product_item_resource.ymldocs/en/schemas/products/v2/json_api.ymldocs/en/schemas/products/v2/error_response.yml
Known production behavior:
- Product resources use
type: good. - Product Category resources use
type: good_category. - Storage Amount resources use
type: good_storage_amount. - Product attributes differ materially from the current flat mock. Observed fields include
actual_cost,cost,category_id,consumable_unit_id,sale_unit_id,sale_unit_to_consumable_unit_ratio,is_chain, and loyalty entity IDs. - Storage Amount attributes observed:
good_id,storage_id,sale_amount, andconsumable_amount. - Product Category attributes observed:
title,is_chain, andparent_category_id. - List and autocomplete pagination is
pagebased. Production returnsmeta.pagination.page,offset, andlimit. - The previously documented
offsetquery parameter on Product list is ignored. Production usespage. - Product ID filtering works.
- The previously documented Product and Product Category include values have no effect. Revalidation confirmed the PHP include keys for Products, Product Categories, Storage Amounts, and Appointment Product Items.
storage_amountsworks for Product detail but returns422for Product list and autocomplete.actual_costworks for Product list and detail only when the Business User has permission; autocomplete leaves itnull.- The Appointment Product Item positive shape is verified from the current PHP transformer. Its route and
404response are live-verified, but the test Location still has no safe positive fixture.
Target file:
docs/en/paths/team_members/v2/location.team_members.list.yml
Known production behavior:
- Resources use
type: staff. - Observed attributes:
name,specialization,image,position_id,is_available_as_assistant, andtt_markup. - Name, Position, and tri-state filters work.
- Invalid tri-state values return
422with field errors. - Current PHP exposes the literal include keys
positionandemployee.positionusestype: staff_position;employeeusestype: employeeand returns the employment profile fields defined by the transformer. - The previously documented
servicesandscheduleinclude values are not supported by the current transformer. - Production revalidation currently returns
403 Insufficient rightsfor every Location returned to the test Business User. The controller gates the operation throughhasSalonAccess. Positive resource and include shapes are therefore verified from the current controller, filter DTO, and transformers rather than a fresh positive fixture.
Target files:
docs/en/paths/appointments/v2/location.appointments.list.ymldocs/en/paths/appointments/v2/location.appointments.delete.ymldocs/en/paths/appointments/v2/location.appointments.bulk_delete.yml
Known production behavior:
- Appointment resources use
type: recordand contain substantially more attributes than the current mock schema. - Pagination is
pagebased.offsetis ignored; undocumentedpageworks. - Date filters require
YYYY-MM-DDTHH:MM:SS. - The current transformer exposes Booking User, Team Member, Service, Product, resource, label, attendance, payment, Custom Field Value, visitor, notification, schedule, and duration relationships through literal include keys. The deprecated Team Member include alias is intentionally omitted.
/timetable/appointments/deleteis not a bulk array endpoint in production. It requires one numericrecordId.- Sending the documented
appointment_idsreturns422and reports thatrecordIdis missing. - A nonexistent numeric
recordIdreaches the handler and returns404. - The timetable-delete controller returns the remaining Appointments related to the deleted Appointment as a JSON:API collection. An Appointment without related records therefore returns an empty
dataarray. - Current production revalidation returns
403 Insufficient rightsfor the Appointment list because the test Business User no longer has timetable access to any returned Location. No temporary Appointment was created or modified. Positive list and delete shapes are verified from the current controller, DTOs, transformer, and deletion service.
Target files:
docs/en/paths/positions/v2/company.positions.list.create.ymldocs/en/paths/positions/v2/company.positions.read.update.delete.yml
Known production behavior:
- Collection, detail, and create responses use JSON:API-style Position resources.
services_binding_typeis numeric in production. The current Position interface defines0(disabled),1(soft), and2(strict).- List responses omit
meta; detail and create responses returnmetaas an array. limitandoffseton the list appeared to be ignored.- Create succeeds with
title;descriptionis accepted. - Production rejects the documented create
salon_idsas unexpected. - Update succeeds with
titleand optionaldescription. - Production rejects the documented update
position_idandsalon_idsas unexpected. - Create returns
201; update and delete return204.
The pre-existing partial Position corrections were inspected and preserved. The numeric enum is now supported by current implementation evidence.
Current revalidation returns 403 Insufficient rights for every Location returned to the test Business User. No temporary Position was created or modified. The response and write contracts retain the earlier live audit evidence, with the binding enum additionally verified from the current PHP interface.
The Position resource schema uses a unique component filename. Reusing position_resource.yml conflicted with the Team Member include schema during Redocly bundling and caused the Cloud Project build to fail.
Target files:
docs/en/paths/tags/v2/company.tags.list.create.ymldocs/en/paths/tags/v2/company.tags.read.update.delete.ymldocs/en/schemas/tags/v2/tag_attributes.yml
Known production behavior:
- CRUD bodies, response resource fields, and status codes largely match the current documentation.
- The list response omits the documented
meta. - Alias and numeric entity filters return equivalent results.
- Invalid entity values return
400with a useful message.
Treat this session as validation and tightening, not a rewrite.
Current revalidation returns 403 Insufficient rights for every Location returned to the test Business User. No temporary Tag was created or modified. The list envelope, entity filters, validation behavior, and CRUD status codes retain the earlier live audit evidence.
Target files:
docs/en/b2b-v2/openapi.yamldocs/en/paths/events/v2/location.events.list.create.ymldocs/en/paths/events/v2/location.events.read.update.delete.ymldocs/en/paths/events/v2/location.events.records.create.ymldocs/en/paths/events/v2/location.events.records.update.yml
Known production behavior:
- The canonical new route works for list, create, read, update, and delete:
/locations/{location_id}/events. - The alias
/locations/{location_id}/activitiesalso works. - The alias
/companies/{location_id}/activitiesalso works. - The specification now publishes only the canonical route; working aliases are intentionally omitted from the primary reference.
- Event list responses use
type: activity, string IDs, and attributes. - Event create, read, and update JSON:API-style schemas are close to production.
- Event create returns
201, update returns200, and delete returns204. - After delete, the Event disappears from list results, but direct read still returned
200in the audit. Revalidate and document soft-delete semantics if stable. - Event filters require
YYYY-MM-DD HH:MM:SSwith a space. RFC 3339 values withTare rejected; the request schemas now use the production pattern. - Event Appointment create, update, and reschedule routes and required-field validation exist, but positive writes still need a controlled Booking User fixture and notification safety review.
Current revalidation on 2026-07-16 returns 403 Insufficient rights for the canonical Event route at all 25 Locations available to the test Business User; both working aliases return the same result for the representative Location. No Event or Event Appointment was created or modified. The sibling PHP implementation confirms the canonical alias mapping, page and limit pagination, the exact Event resource transformer, and soft deletion: deleted Events leave list results while direct retrieval does not reject the deleted state. It also confirms separate PUT update and PATCH reschedule operations for Event Appointments. Positive Event Appointment coverage remains blocked by the missing writable Location and controlled notification-safe Booking User fixture; the PHP integration suite is the current positive-write evidence.
Each session should result in one focused PR. Do not combine sessions unless the files and validation are genuinely inseparable.
Goal: establish reusable, production-shaped components without changing endpoint semantics.
Work:
- Inventory existing v2 schemas and response files.
- Add or reuse small shared components for JSON:API resource identifiers, pagination metadata, and the standard error envelope only when reuse reduces duplication across at least three endpoint groups.
- Document the required v2
Acceptheader once in the top-level description and keep operation-level required headers consistent. - Do not introduce a broad schema architecture refactor.
Acceptance:
- Components validate under OpenAPI 3.1.
- No endpoint behavior is invented.
- Existing references still resolve.
npm testintroduces no new warnings.
Goal: replace all three mock success schemas and examples with verified JSON:API-style contracts.
Work:
- Correct list, detail, and Service Category resource shapes.
- Correct string ID types and actual attributes.
- Replace
offsetwith the effectivepageparameter and correct the default and maximumlimitvalues. - Remove pagination metadata that production does not return.
- Replace ineffective include values with the verified Service include keys.
- Correct the Team Member filter to the literal
filter[staff_id]wire name. - Replace flat error schemas with the production error envelope.
- Add missing
404responses.
Acceptance:
- Live list/detail/category responses validate against the edited schemas.
- Limit, page, ID filtering, Team Member filtering, and Service includes are tested.
- No Service example contains fields absent from the live response.
Goal: correct all seven Product-area operations.
Work:
- Model
good,good_category, andgood_storage_amountresources. - Correct attribute names and types.
- Replace
offsetwithpagewhere production proves page-based pagination. - Correct pagination metadata.
- Replace ineffective include values with the operation-specific PHP keys that current production revalidation proves effective.
- Obtain a safe Appointment Product Item fixture or explicitly mark its positive schema as unverified rather than fabricating it.
- Correct standard errors and add missing
404responses.
Acceptance:
- Product list, autocomplete, detail, category list/detail, and storage amounts validate against live responses.
- Page 1 and page 2 return different first resources in the test fixture.
- Product ID filtering is verified.
- Appointment Product Item confidence is stated explicitly.
Goal: replace the old flat Team Member response with the live staff resource.
Work:
- Correct attributes, string IDs, filters, and errors.
- Remove ineffective include claims unless current behavior has changed.
- Add the missing
404response.
Acceptance:
- Base list and representative filters validate live.
- Invalid tri-state values validate against the documented
422envelope.
Goal: correct Appointment resources, pagination, and timetable deletion.
Work:
- Model the live
recordresource and its verified attributes. - Document
page, not the ignoredoffsetbehavior. - Correct date formats and examples.
- Remove ineffective includes unless revalidated.
- Replace the false bulk
appointment_ids[]contract with the productionrecordIdrequest. - Use a temporary Appointment fixture to verify the successful timetable-delete status and body, with cleanup in the same test flow.
- Correct errors and missing
404responses.
Acceptance:
- List pages and filters validate live.
- Single delete and timetable delete are verified using temporary fixtures only.
- No existing Appointment is modified.
- Test fixtures are absent after the run.
Goal: make Position reads and writes usable from the documentation.
Work:
- Revalidate and document the numeric
services_binding_typevalues. - Correct
metapresence and type. - Remove unsupported list pagination.
- Correct create and update request bodies.
- Update the Position section of the Arazzo workflow only after the OpenAPI contract is correct.
Acceptance:
- Temporary create, read, update, and delete all succeed from documented bodies.
- The temporary Position is removed.
- Production responses validate without schema warnings.
Goal: tighten the already mostly correct Tag documentation.
Work:
- Revalidate list, filters, CRUD,
meta, and errors. - Remove stale “not yet deployed” language and 404 skip behavior.
- Avoid unrelated Tag schema rewrites.
Acceptance:
- Full temporary CRUD succeeds and cleans up.
- Alias and numeric filters are both covered.
- List and detail responses validate exactly.
Goal: publish the canonical Event API and correct the remaining mock contracts.
Work:
- Register
/locations/{location_id}/eventsas the canonical documented path. - Decide, based on product compatibility policy, whether working aliases should be documented as deprecated aliases or omitted from the primary reference.
- Replace the flat Event list schema with the live
activityresource. - Correct the production date-time pattern and examples without falsely using RFC 3339
format: date-time. - Revalidate Event CRUD and soft-delete behavior.
- Build a controlled fixture for Event Appointment create/update only after confirming that Booking User notifications and Booking User creation are safe.
- Correct Event Appointment success schemas if the live fixture differs.
Acceptance:
- Canonical Event CRUD succeeds and cleans up.
- Event list/detail/create/update responses validate.
- The v2-specific Redocly date warnings are eliminated.
- Event Appointment positive coverage is either completed safely or explicitly documented as blocked with the exact missing fixture/safety requirement.
Goal: make automated checks detect future contract drift.
Work:
- Expand
docs/api-fast-check-b2b-v2.arazzo.yamlfrom Tags/Positions to safe read coverage for every v2 group. - Remove success criteria that accept
404because an endpoint may be undeployed. - Add explicit dependencies and cleanup for every created resource.
- Ensure cleanup runs on failure where the runner supports it. If it does not, split destructive workflows or add a deterministic cleanup workflow.
- Use unique test names to avoid collisions.
- Promote
schemaCheckfromwarntoerroronly after all live schemas are corrected. - Do not make destructive checks depend on pre-existing production data.
Important audit evidence: the existing smoke workflow left an Arazzo Test Tag after an incomplete run. It was deleted manually. Cleanup is therefore a required part of this session, not an optional improvement.
Implemented and production-validated on 2026-07-16:
The build workflow is read-only and covers collection responses for Services, Products, Team Members, Positions, Appointments, Events, and Tags. All seven groups require
200; no404response is accepted as success.Tag and Position mutations are isolated in a manual workflow. Each caller supplies a unique run ID, every post-create failure routes to deletion, and a failed deletion is retried twice.
The repository guard bundles both Arazzo descriptions and the v2 OpenAPI description. It rejects missing read groups, any v2 mutation in the build workflow, accepted
404criteria, missing cleanup routes, a registered destructive Reunite job, or a non-error v2schemaCheckseverity.The read-only production workflow passed 9 of 9 steps and 36 of 36 checks. The isolated Tag and Position workflows passed 12 of 12 steps and 40 of 40 checks, then a separate list audit found no remaining fixtures.
A forced Tag read failure ran the failure cleanup action successfully; a follow-up list audit again found no fixture. A deliberately corrupted Service response schema failed Respect at
listServices, confirming that schema drift is build-blocking.The final Redocly preview status reported
All Respect Monitoring checks passed. An earlierNo Respect Monitoring workflows foundstatus appeared only while the Project preview was still building and was replaced when the configured workflows completed.
Acceptance:
- All safe read checks run on every build.
- No “not deployed” 404 path can produce a green build.
- Intentional schema corruption makes the workflow fail.
- Failed CRUD testing leaves no Tag, Position, Appointment, or Event fixture.
Goal: verify the complete v2 reference as one production contract.
Work:
- Recount paths and operations; expected baseline is 22 paths and 32 operations. The additional operation is the production-supported Event Appointment reschedule operation documented in Session 8.
- Run Redocly lint for
en/b2b-v2/openapi.yamland the repository baseline. - Run the completed v2 Arazzo workflow.
- Manually inspect representative rendered pages for each tag.
- Verify terminology in prose while preserving wire names.
- Search for stale mock markers such as flat success resources, integer JSON:API IDs, “not yet deployed,” ineffective includes, and obsolete Event-only paths.
Implemented and verified on 2026-07-16:
- The bundled reference contains 22 paths and 32 operations: 18 GET, 5 POST, 4 PUT, 1 PATCH, and 4 DELETE. Every operation documents 401, 403, and 404.
- Every JSON:API-style resource schema uses a string
id. Source and bundled prose contain no superseded domain term outside an explicitly marked literal wire key, value, resource type, or URL parameter. No stale mock or deployment marker remains. - Redocly CLI 2.14.4 and 2.39.0 both lint the v2 description with no warnings. The repository baseline passes with 0 errors and 507 warnings against a budget of 510.
- The production read-only workflow passes 9 of 9 steps and 36 of 36 checks. Services, Products, Team Members, Positions, Appointments, Events, and Tags each return 200 and validate against the published schema.
- The published Services, Products, Team Members, Appointments, Events, and Tags sections were inspected in the rendered reference. The six sections, all 32 operations, and the canonical terminology render correctly.
- A separate production list audit found zero Arazzo Tag, Position, Appointment, or Event fixtures.
Final evidence classification:
- Documented: all 32 published operations, including their request, success, authentication, permission, and not-found contracts.
- Live-verified: 17 of 18 positive GET operations from the production audit; all seven representative collection reads were revalidated in this session. Tag and Position CRUD and Event CRUD also have temporary-fixture production evidence, with cleanup confirmed. Session 10 intentionally did not repeat destructive checks.
- Not safely live-verified: the positive Appointment Product Item response, Appointment deletion success bodies, and positive Event Appointment writes. These shapes are verified against the current PHP transformers, controllers, services, and integration tests. Repeating them in production still requires a controlled Appointment or Booking User fixture and notification-safety review.
Acceptance:
- Every positive live response used in the workflow validates against OpenAPI.
- Every operation contains 401, 403, and 404 responses as required by repository policy, plus actual validation statuses.
- The v2 spec has no v2-specific lint warnings.
- The repository warning baseline does not increase.
- No test fixtures remain.
- The final report distinguishes documented, live-verified, and not safely verified behavior.
Run from the repository root unless noted otherwise:
npm test
cd docs && ../node_modules/.bin/redocly lint en/b2b-v2/openapi.yamlAlso run the narrow live probes for the endpoint group. Do not run all destructive workflows merely to validate a read-only schema change.
For changes to Arazzo or CRUD schemas, run:
make test-api-b2b-v2After every live write test, query the relevant list endpoint and confirm that the temporary resource is gone.
- The published v2 paths use the approved canonical terminology.
- Wire-level literal values are documented accurately, not renamed.
- Success and error schemas validate current production responses.
- Request bodies can be copied from the documentation and used successfully.
- Pagination parameters and metadata match production behavior.
- Unsupported or ignored includes are not advertised as working features.
- Arazzo covers every v2 group and fails on schema drift.
- 404 is never treated as proof that a production v2 endpoint is healthy.
- Destructive tests are isolated, uniquely named, and self-cleaning.
- Redocly reports no v2-specific warnings and the repository warning budget does not increase.
- Each merged PR reports live verification separately from lint success.
Use this prompt with the session number filled in:
Implement Session N from
docs/plans/2026-07-15-b2b-v2-production-contract-remediation.md. Work only on the listed B2B v2 files. Revalidate production with the required v2 Accept header, preserve wire names, avoid V1 scope, clean up every temporary resource, run the required lint/tests, and open a focused PR. Report separately what was changed, live-verified, and not safely verified.