- add editor integration shell - add editor context read flow - add draft flow entry handling - add summary, structure, validation and compare-preview views - render backend read models for draft and published states - verify sample-contract entities: 3 seats, 1 group, 1 sector
12 KiB
Backend Integration Contract
This document is the frontend handoff contract for the svg-service backend. It is written as an integration baseline, not as an internal backend README.
1. Base URL and Auth
- Base URL:
http://<host>:9020 - API prefix:
/api/v1 - Auth header:
X-API-Key
All non-/healthz routes require an API key.
Auth failure contract:
- missing API key ->
401with string detail:Missing API key - invalid API key ->
403with string detail:Invalid API key - valid non-admin key on admin-only route ->
403with string detail:Admin role required
2. Roles and Access Boundaries
admin- full access to protected routes
- required for all
/api/v1/admin/...routes
operator- allowed on non-admin protected routes
- denied on admin-only routes
viewer- allowed on non-admin protected routes
- denied on admin-only routes
Frontend implication:
- admin UI must treat admin routes as optional capabilities gated by role
- frontend must not assume
operatororviewercan call cleanup, audit, backfill, or current-artifact admin routes
3. Core Entities
Upload
Represents one uploaded SVG source and its normalized/sanitized artifacts.
Important fields:
upload_idoriginal_filenamecontent_typesize_bytesoriginal_storage_pathsanitized_storage_pathnormalized_storage_pathnormalized_elements_countnormalized_seats_countnormalized_groups_countnormalized_sectors_count
Scheme
Top-level business object created from upload.
Important fields:
scheme_idsource_upload_idnamestatuscurrent_version_numberpublished_at
Scheme Version
Versioned snapshot of the scheme structure and publish state.
Important fields:
scheme_version_idscheme_idversion_numberstatusnormalized_storage_pathnormalized_*_count
Sector
Structure entity in a specific scheme_version.
Important fields:
sector_record_idsector_idelement_idname
Business identity priority:
- use
sector_idwhen present - fallback to
element_id - never treat
sector_record_idas business identity across versions
Group
Important fields:
group_record_idgroup_idelement_idname
Business identity priority:
- use
group_idwhen present - fallback to
element_id - never treat
group_record_idas business identity across versions
Seat
Important fields:
seat_record_idseat_idelement_idsector_idgroup_idrow_labelseat_number
Business identity priority:
- use
seat_idwhen present - fallback to
element_id - never treat
seat_record_idas business identity across versions
Pricing Category
Important fields:
pricing_category_idscheme_idnamecode
Price Rule
Important fields:
price_rule_idscheme_idpricing_category_idtarget_typetarget_refamountcurrency
Artifact
Artifact registry row for generated backend files.
Important fields:
artifact_idartifact_typeartifact_variantstorage_pathstatusmeta_json
Important artifact types currently exercised by regression:
sanitized_svgnormalized_jsondisplay_svgpublish_preview
4. Lifecycle State Machine
Fresh Upload
Flow:
POST /api/v1/schemes/upload- backend creates:
uploadscheme- initial
scheme_version - structure rows
- initial artifacts
Expected initial state:
scheme.status = draftscheme.current_version_number = 1- current version status =
draft
Current Draft
If current scheme/version is still draft:
- editor works directly against current version
draft/ensureis idempotentdraft/ensurereturnscreated=false
Ensure Draft From Published Current
If current scheme/version is published:
POST /api/v1/schemes/{scheme_id}/draft/ensure- backend creates a new draft version
- current pointer switches to the new draft
- version number increments
Publish
Preconditions:
- current scheme is draft
- current version is draft
- publish readiness must be satisfied
Publish path:
- optional
draft/pricing/snapshot GET draft/publish-readiness- optional
GET draft/publish-preview POST /api/v1/schemes/{scheme_id}/publish
Expected result:
- scheme becomes
published - current version becomes
published
Rollback
Path:
POST /api/v1/schemes/{scheme_id}/rollback
Effect:
- current pointer switches to requested historical
version_number - scheme returns to
draft - target version becomes current editable draft
Unpublish
Path:
POST /api/v1/schemes/{scheme_id}/unpublish
Effect:
- current scheme becomes
draft - current version becomes
draft
5. Editor Flow
Entry Point
GET /api/v1/schemes/{scheme_id}/editor/context
Use it first to decide whether:
- current draft can be edited directly
- or a new draft must be created from published current
Important response fields:
current_scheme_version_idcurrent_version_numberscheme_statusscheme_version_statuscurrent_is_draftcreate_draft_availablerecommended_action
Draft Read Models
POST /api/v1/schemes/{scheme_id}/draft/ensureGET /api/v1/schemes/{scheme_id}/draft/summaryGET /api/v1/schemes/{scheme_id}/draft/structureGET /api/v1/schemes/{scheme_id}/draft/validationGET /api/v1/schemes/{scheme_id}/draft/compare-preview
Frontend should treat draft/structure as the main editable read model.
Patch Operations
Supported flows:
- single seat patch
- bulk seat patch
- sector create/patch/delete
- group create/patch/delete
- repair references
- remap preview/apply
Frontend rule:
- always send
expected_scheme_version_idwhen mutating or reading draft state after editor entry
Stale Conflict Handling
If backend returns a stale or draft editability conflict:
- stop optimistic local mutation flow
- re-read:
editor/contextdraft/summarydraft/structure
Do not keep editing against stale cached scheme_version_id.
6. Pricing Flow
Categories
GET /api/v1/schemes/{scheme_id}/pricingPOST /api/v1/schemes/{scheme_id}/pricing/categoriesPUT /api/v1/schemes/{scheme_id}/pricing/categories/{pricing_category_id}DELETE /api/v1/schemes/{scheme_id}/pricing/categories/{pricing_category_id}
Rules
POST /api/v1/schemes/{scheme_id}/pricing/rulesPUT /api/v1/schemes/{scheme_id}/pricing/rules/{price_rule_id}DELETE /api/v1/schemes/{scheme_id}/pricing/rules/{price_rule_id}
Read Models
GET /api/v1/schemes/{scheme_id}/pricingGET /api/v1/schemes/{scheme_id}/pricing/coverageGET /api/v1/schemes/{scheme_id}/pricing/unpriced-seatsGET /api/v1/schemes/{scheme_id}/pricing/explain/{seat_id}GET /api/v1/schemes/{scheme_id}/pricing/rules/diagnosticsGET /api/v1/schemes/{scheme_id}/current/seats/{seat_id}/priceGET /api/v1/schemes/{scheme_id}/test/seats/{seat_id}
Frontend rule:
- empty pricing on a fresh upload is valid
- do not treat
categories=[]andrules=[]as backend failure
7. Publish Flow
Main endpoints:
POST /api/v1/schemes/{scheme_id}/draft/pricing/snapshotGET /api/v1/schemes/{scheme_id}/draft/publish-readinessGET /api/v1/schemes/{scheme_id}/draft/publish-previewPOST /api/v1/schemes/{scheme_id}/publish
Frontend sequencing rule:
- ensure draft
- mutate if needed
- create/refresh pricing
- build pricing snapshot
- read publish readiness
- read publish preview if UI needs preview surface
- publish
8. Admin/Ops Flow
Admin-only endpoints:
GET /api/v1/admin/schemes/{scheme_id}/current/artifactsGET /api/v1/admin/schemes/{scheme_id}/current/validationPOST /api/v1/admin/schemes/{scheme_id}/current/display/regeneratePOST /api/v1/admin/display/backfillGET /api/v1/admin/artifacts/publish-preview/auditPOST /api/v1/admin/artifacts/publish-preview/cleanupGET /api/v1/admin/schemes/{scheme_id}/pricing/categories/cleanup-previewPOST /api/v1/admin/schemes/{scheme_id}/pricing/categories/cleanup
Healthy publish-preview audit contract:
orphan_files_count = 0missing_files_for_db_rows_count = 0db_rows_count == disk_files_count
Frontend implication:
- admin tools must not be shown as generally available functionality
- admin cleanup/destructive flows must be role-gated on the client and still handle backend
403
9. Typed Error Catalog
Auth
401string detail:Missing API key403string detail:Invalid API key403string detail:Admin role required
Lifecycle / Draft / Publish
stale_draft_versionstale_current_versioncurrent_version_inconsistentdraft_not_editablepublish_not_ready
Editor Uniqueness / References
editor_uniqueness_erroreditor_reference_errorduplicate_seat_idduplicate_seat_id_in_payloadduplicate_sector_idduplicate_group_idduplicate_sector_element_idduplicate_group_element_idunknown_sector_idunknown_group_idunknown_sector_idsunknown_group_idsunknown_target_sector_idunknown_target_group_idbusiness_identifier_nullification_forbidden
Pricing / Remap / Test
invalid_amountremap_filter_requiredtest_preview_failed
Validation Report Codes
These appear inside validation report payloads rather than as top-level HTTP conflict codes:
duplicate_seat_idsmissing_seat_contractseats_without_sector_or_groupseats_without_price
Frontend rule:
- do not parse only HTTP status
- always inspect structured
detail.codewhendetailis an object
10. Frontend Obligations
- always handle auth failures
401and403 - always handle stale/conflict responses on draft, publish, and lifecycle operations
- never treat
*_record_idas stable cross-version business identity - always prefer business ids:
- seat ->
seat_id, fallbackelement_id - sector ->
sector_id, fallbackelement_id - group ->
group_id, fallbackelement_id
- seat ->
- re-read current/draft state after:
- any
409 - publish
- rollback
- unpublish
draft/ensurereturning a newly created draft
- any
- do not assume current version remains stable across concurrent operator sessions
- do not assume publish-preview artifacts or display artifacts are frontend-owned resources
11. Non-Persistent Assumptions Frontend Must Avoid
The frontend must not assume that these remain stable forever:
scheme_version_idseat_record_idsector_record_idgroup_record_id- artifact
storage_path - publish-preview cache artifacts
These are safe to treat as business-stable:
scheme_idversion_numberwithin one schemeseat_idwhen presentsector_idwhen presentgroup_idwhen present
12. Known Limitations / Deferred Tech Debt
- some lifecycle negative contracts still return mixed styles:
- typed object conflicts for
409 - plain string details for some
404and auth cases
- typed object conflicts for
- validation warnings and error code families are not yet unified into one single global error envelope
- admin/ops routes are backend-internal tools, not end-user product APIs
- corruption remediation smoke exists only for
publish_preview, not for every artifact type
13. Regression Baseline Frontend Can Rely On
The frontend can rely on the following regression-backed flows:
- fresh upload on clean DB
- current/draft/editor read flow
- editor mutations and stale draft protection
- pricing setup and publish flow
- version lifecycle:
- publish
- ensure draft from published current
- rollback
- unpublish
- admin ops:
- audit
- cleanup
- destructive pricing cleanup for safe fixture categories
- full admin permission matrix on implemented admin endpoints
- controlled
publish_previewcorruption detection and remediation - negative upload validation
- negative auth matrix
- negative lifecycle matrix
14. Recommended Frontend Integration Sequence
For normal editor work:
- authenticate
- upload or pick
scheme_id - read
editor/context - call
draft/ensureif needed - read
draft/structure - mutate using current
scheme_version_id - on
409, reload editor state before retry - configure pricing if needed
- create pricing snapshot
- read publish readiness / preview
- publish
For admin UI:
- verify admin role in client auth state
- call admin endpoints
- still handle backend
403 - treat cleanup and remediation as explicit operator actions, not background automation