freeze the current backend contract for frontend integration document the stabilized backend surface and handoff expectations mark the current state as the baseline for further frontend work
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