Skip to content
Last updated

B2B v2 Production Contract Remediation Plan

  • Status: Ready for implementation
  • Audit snapshot: 2026-07-15
  • Scope: https://api.alteg.io/api/v2 only
  • Primary specification: docs/en/b2b-v2/openapi.yaml

Outcome

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.

Non-negotiable scope rules

  • 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 Accept header does not select between two successful response formats. Production returns 400 without 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.

Starting a new implementation session

  1. Read this entire plan and the repository AGENTS.md.

  2. Inspect the current state before editing:

    git status --short --branch
    git fetch origin
    git log --oneline --decorate -10
  3. Preserve any existing working-tree changes. Do not overwrite another session's edits.

  4. Create a new branch directly from the latest remote default branch:

    git switch -c docs/v2-<session-scope> origin/master
  5. Read every target path file before changing it. Search for shared schemas and callers before creating new files.

  6. Re-run the relevant live probes. The observations below are a dated snapshot, not permission to skip current verification.

  7. Change only the endpoint group assigned to the session.

  8. Run the required validation, commit, push, and open a focused PR.

Verified production baseline

Authentication and media type

The following behavior was verified on Services, Appointments, and Positions:

Request Accept valueProduction result
application/vnd.api.v2+jsonSuccessful JSON:API-style response
Omitted400
*/*400
application/json400
application/vnd.api.v1+json400

The production error message explicitly requires Accept: application/vnd.api.v2+json. A charset parameter is accepted.

Response vocabulary

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 termProduction path termProduction type
Serviceservicesservice
Productproductsgood
Product Categoryproduct_categoriesgood_category
Product Storage Amountstorage_amountsgood_storage_amount
Team Memberteam_membersstaff
Appointmentappointmentsrecord
Eventeventsactivity
Positionpositionsposition
Tagtagstag

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.

Common success and error shapes

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.

Live coverage completed in the audit

  • 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 404 behavior 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.

Known contract gaps by area

Services

Target files:

  • docs/en/paths/services/v2/location.services.list.yml
  • docs/en/paths/services/v2/location.services.read.yml
  • docs/en/paths/services/v2/location.service_categories.list.yml
  • docs/en/schemas/services/v2/service_resource.yml
  • docs/en/schemas/services/v2/service_category_resource.yml
  • docs/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, and filter[service_ids][] work for the Service list.
  • Production uses page; the previously documented offset parameter is ignored.
  • No pagination meta was returned by the Service list.
  • The previously documented category and staff include values have no effect. Production supports resources, service_composite, resource_links, company_links, and trial_settings; effective includes add JSON:API relationships and may add top-level included resources.
  • 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.

Products

Target files:

  • docs/en/paths/products/v2/location.products.list.yml
  • docs/en/paths/products/v2/location.products.autocomplete.yml
  • docs/en/paths/products/v2/location.products.read.yml
  • docs/en/paths/products/v2/location.products.storage_amounts.yml
  • docs/en/paths/products/v2/location.product_categories.list.yml
  • docs/en/paths/products/v2/location.product_categories.read.yml
  • docs/en/paths/products/v2/location.attendance_product_items.read.yml
  • docs/en/schemas/products/v2/product_resource.yml
  • docs/en/schemas/products/v2/product_category_resource.yml
  • docs/en/schemas/products/v2/storage_amount_resource.yml
  • docs/en/schemas/products/v2/attendance_product_item_resource.yml
  • docs/en/schemas/products/v2/json_api.yml
  • docs/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, and consumable_amount.
  • Product Category attributes observed: title, is_chain, and parent_category_id.
  • List and autocomplete pagination is page based. Production returns meta.pagination.page, offset, and limit.
  • The previously documented offset query parameter on Product list is ignored. Production uses page.
  • 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_amounts works for Product detail but returns 422 for Product list and autocomplete. actual_cost works for Product list and detail only when the Business User has permission; autocomplete leaves it null.
  • The Appointment Product Item positive shape is verified from the current PHP transformer. Its route and 404 response are live-verified, but the test Location still has no safe positive fixture.

Team Members

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, and tt_markup.
  • Name, Position, and tri-state filters work.
  • Invalid tri-state values return 422 with field errors.
  • Current PHP exposes the literal include keys position and employee. position uses type: staff_position; employee uses type: employee and returns the employment profile fields defined by the transformer.
  • The previously documented services and schedule include values are not supported by the current transformer.
  • Production revalidation currently returns 403 Insufficient rights for every Location returned to the test Business User. The controller gates the operation through hasSalonAccess. Positive resource and include shapes are therefore verified from the current controller, filter DTO, and transformers rather than a fresh positive fixture.

Appointments

Target files:

  • docs/en/paths/appointments/v2/location.appointments.list.yml
  • docs/en/paths/appointments/v2/location.appointments.delete.yml
  • docs/en/paths/appointments/v2/location.appointments.bulk_delete.yml

Known production behavior:

  • Appointment resources use type: record and contain substantially more attributes than the current mock schema.
  • Pagination is page based. offset is ignored; undocumented page works.
  • 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/delete is not a bulk array endpoint in production. It requires one numeric recordId.
  • Sending the documented appointment_ids returns 422 and reports that recordId is missing.
  • A nonexistent numeric recordId reaches the handler and returns 404.
  • 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 data array.
  • Current production revalidation returns 403 Insufficient rights for 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.

Positions

Target files:

  • docs/en/paths/positions/v2/company.positions.list.create.yml
  • docs/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_type is numeric in production. The current Position interface defines 0 (disabled), 1 (soft), and 2 (strict).
  • List responses omit meta; detail and create responses return meta as an array.
  • limit and offset on the list appeared to be ignored.
  • Create succeeds with title; description is accepted.
  • Production rejects the documented create salon_ids as unexpected.
  • Update succeeds with title and optional description.
  • Production rejects the documented update position_id and salon_ids as unexpected.
  • Create returns 201; update and delete return 204.

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.

Tags

Target files:

  • docs/en/paths/tags/v2/company.tags.list.create.yml
  • docs/en/paths/tags/v2/company.tags.read.update.delete.yml
  • docs/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 400 with 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.

Events and Event Appointments

Target files:

  • docs/en/b2b-v2/openapi.yaml
  • docs/en/paths/events/v2/location.events.list.create.yml
  • docs/en/paths/events/v2/location.events.read.update.delete.yml
  • docs/en/paths/events/v2/location.events.records.create.yml
  • docs/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}/activities also works.
  • The alias /companies/{location_id}/activities also 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 returns 200, and delete returns 204.
  • After delete, the Event disappears from list results, but direct read still returned 200 in the audit. Revalidate and document soft-delete semantics if stable.
  • Event filters require YYYY-MM-DD HH:MM:SS with a space. RFC 3339 values with T are 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.

Implementation sessions

Each session should result in one focused PR. Do not combine sessions unless the files and validation are genuinely inseparable.

Session 1: Contract primitives and guardrails

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 Accept header 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 test introduces no new warnings.

Session 2: Services

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 offset with the effective page parameter and correct the default and maximum limit values.
  • 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 404 responses.

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.

Session 3: Products

Goal: correct all seven Product-area operations.

Work:

  • Model good, good_category, and good_storage_amount resources.
  • Correct attribute names and types.
  • Replace offset with page where 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 404 responses.

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.

Session 4: Team Members

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 404 response.

Acceptance:

  • Base list and representative filters validate live.
  • Invalid tri-state values validate against the documented 422 envelope.

Session 5: Appointments

Goal: correct Appointment resources, pagination, and timetable deletion.

Work:

  • Model the live record resource and its verified attributes.
  • Document page, not the ignored offset behavior.
  • Correct date formats and examples.
  • Remove ineffective includes unless revalidated.
  • Replace the false bulk appointment_ids[] contract with the production recordId request.
  • 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 404 responses.

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.

Session 6: Positions

Goal: make Position reads and writes usable from the documentation.

Work:

  • Revalidate and document the numeric services_binding_type values.
  • Correct meta presence 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.

Session 7: Tags

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.

Session 8: Events and Event Appointments

Goal: publish the canonical Event API and correct the remaining mock contracts.

Work:

  • Register /locations/{location_id}/events as 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 activity resource.
  • 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.

Session 9: Arazzo and CI hardening

Goal: make automated checks detect future contract drift.

Work:

  • Expand docs/api-fast-check-b2b-v2.arazzo.yaml from Tags/Positions to safe read coverage for every v2 group.
  • Remove success criteria that accept 404 because 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 schemaCheck from warn to error only 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; no 404 response 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 404 criteria, missing cleanup routes, a registered destructive Reunite job, or a non-error v2 schemaCheck severity.

  • 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 earlier No Respect Monitoring workflows found status 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.

Session 10: Final cross-group verification

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.yaml and 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.

Required validation for every PR

Run from the repository root unless noted otherwise:

npm test
cd docs && ../node_modules/.bin/redocly lint en/b2b-v2/openapi.yaml

Also 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-v2

After every live write test, query the relevant list endpoint and confirm that the temporary resource is gone.

Definition of done for the remediation program

  • 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.

Handoff template for a future session

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.